+ These actions apply to the current listed statements after filters and pagination.
+
-
+
+
+
+ <%= render partial: "trace_step",
+ collection: steps,
+ as: :step,
+ locals: {
+ steps: steps,
+ visible_indices: visible_indices,
+ hidden_count: hidden_count,
+ first_error_index: first_error_index,
+ first_error_step: first_error_step
+ } %>
+
+<% end %>
+
+
+
+
+
Change status:
@@ -124,5 +402,3 @@ Manual update:
<%= button_to 'Refresh', refresh_statement_path(@statement), method: :patch, form_class: 'inline', class: 'as-link' %> |
<%= link_to 'Edit', edit_statement_path(@statement) %> |
<%= link_to 'Back', statements_path %>
-
-<%= render partial: "trace_table", locals: { trace: @trace, result: @result } if @trace.present? %>
\ No newline at end of file
diff --git a/app/views/statements/trace_demo.html.erb b/app/views/statements/trace_demo.html.erb
deleted file mode 100644
index 927836e5..00000000
--- a/app/views/statements/trace_demo.html.erb
+++ /dev/null
@@ -1,66 +0,0 @@
-
- <%= button_to 'Destroy all statements', delete_all_statements_websites_path(id: @website.id), method: :delete, data: { confirm: 'Are you sure to delete all #{@website.seedurl} statements ?' } %>
- <%= button_to 'Destroy all webpages', delete_all_webpages_websites_path(id: @website.id), method: :delete, data: { confirm: 'Are you sure to delete all #{@website.seedurl} statements ?' } %>
- <%= button_to 'Destroy all EVENT webpages', delete_all_event_webpages_websites_path(id: @website.id), method: :delete, data: { confirm: 'Are you sure to delete all #{@website.seedurl} statements ?' } %>
+
+ Danger zone
+
+ <%= button_to "Destroy website", website_path(@website), method: :delete, data: { confirm: "Are you sure you want to destroy #{@website.seedurl}?", turbo: false } %>
+ <%= button_to "Destroy all statements", delete_all_statements_websites_path(id: @website.id), method: :delete, data: { confirm: "Are you sure to delete all #{@website.seedurl} statements ?" } %>
+ <%= button_to "Destroy all webpages", delete_all_webpages_websites_path(id: @website.id), method: :delete, data: { confirm: "Are you sure to delete all #{@website.seedurl} statements ?" } %>
+ <%= button_to "Destroy all EVENT webpages", delete_all_event_webpages_websites_path(id: @website.id), method: :delete, data: { confirm: "Are you sure to delete all #{@website.seedurl} statements ?" } %>
+
+
diff --git a/config/distillator_cohorts.yml b/config/distillator_cohorts.yml
new file mode 100644
index 00000000..50c0407c
--- /dev/null
+++ b/config/distillator_cohorts.yml
@@ -0,0 +1,35 @@
+lavitrine_pipeline:
+ label: "La Vitrine pipeline"
+ source_url: "https://raw.githubusercontent.com/artsdata-stewards/artsdata-actions/main/queries/lavitrine_pipeline.sparql"
+ match_fields:
+ - seedurl
+ - name
+ - code
+ feed_names:
+ - gatineau-cloud
+ - ccat-qc-ca
+ - theatredumarais-com
+ - ptitbonheur-org
+ - centredesartsbc-com
+ - culturegaspesie-org
+ - diffusion-saguenay-ca
+ - theatregillesvigneault-com
+ - quoivivrerimouski-ca
+ - hector-charland-com
+ - diffusionmordicus-tuxedobillet-com
+ - theatrepatriote-com
+ - amphitheatrecogeco-com
+ - spectaclesjoliette-com
+ - stprime-tuxedobillet-com
+ - petittheatre-org
+ - lecarre150-com
+ - centredecreationdiffusiondegaspe-com
+ - placedesarts-com
+ - maisondelaculture-ca
+ - chasse-galerie-ca
+ - minotaure-ca
+ - derived-grandtheatre-qc-ca
+ - signe-laval
+ - theatregranada-com
+ - tout-culture
+ - culture-mauricie
diff --git a/config/environments/development.rb b/config/environments/development.rb
index cc79e756..84a0f079 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -55,4 +55,12 @@
# Use the bult-in queue adapter in DEV to simplify environment
# switch to :sidekiq if needed
config.active_job.queue_adapter = :async
+
+ # Canonical compatibility endpoint: DISTILLATOR_COMPAT_BASE_URL.
+ # DISTILLATOR_COMPATIBILITY_BASE_URL remains a temporary backward-compatible alias.
+ config.x.distillator.compatibility_base_url =
+ ENV["DISTILLATOR_COMPAT_BASE_URL"].presence ||
+ ENV["DISTILLATOR_COMPATIBILITY_BASE_URL"].presence
+ config.x.distillator.legacy_wringer_base_url = ENV["LEGACY_WRINGER_BASE_URL"].presence
+ config.x.distillator.allow_localhost_compatibility = true
end
diff --git a/config/environments/production.rb b/config/environments/production.rb
index 113f434c..02081d72 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -91,4 +91,14 @@
# Do not dump schema after migrations.
config.active_record.dump_schema_after_migration = false
+ # Canonical compatibility endpoint: DISTILLATOR_COMPAT_BASE_URL.
+ # DISTILLATOR_COMPATIBILITY_BASE_URL remains a temporary backward-compatible alias.
+ config.x.distillator.compatibility_base_url =
+ ENV["DISTILLATOR_COMPAT_BASE_URL"].presence ||
+ ENV["DISTILLATOR_COMPATIBILITY_BASE_URL"].presence
+ config.x.distillator.legacy_wringer_base_url =
+ ENV["LEGACY_WRINGER_BASE_URL"].presence ||
+ ENV["WRINGER_URL"].presence
+ config.x.distillator.allow_localhost_compatibility = false
+
end
diff --git a/config/environments/staging.rb b/config/environments/staging.rb
new file mode 100644
index 00000000..ddd28207
--- /dev/null
+++ b/config/environments/staging.rb
@@ -0,0 +1,103 @@
+Rails.application.configure do
+ # Settings specified here will take precedence over those in config/application.rb.
+
+ # Code is not reloaded between requests.
+ config.cache_classes = true
+
+ # Eager load code on boot. This eager loads most of Rails and
+ # your application in memory, allowing both threaded web servers
+ # and those relying on copy on write to perform better.
+ # Rake tasks automatically ignore this option for performance.
+ config.eager_load = true
+
+ # Full error reports are disabled and caching is turned on.
+ config.consider_all_requests_local = false
+ config.action_controller.perform_caching = true
+
+ # Attempt to read encrypted secrets from `config/secrets.yml.enc`.
+ # Requires an encryption key in `ENV["RAILS_MASTER_KEY"]` or
+ # `config/secrets.yml.key`.
+
+ config.require_master_key = true
+ config.read_encrypted_secrets = true
+
+ # Disable serving static files from the `/public` folder by default since
+ # Apache or NGINX already handles this.
+ config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
+
+ # Compress JavaScripts and CSS.
+ # config.assets.js_compressor = :uglifier
+ # config.assets.css_compressor = :sass
+
+ # Do not fallback to assets pipeline if a precompiled asset is missed.
+ config.assets.compile = false
+
+ # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
+
+ # Enable serving of images, stylesheets, and JavaScripts from an asset server.
+ # config.action_controller.asset_host = 'http://assets.example.com'
+
+ # Specifies the header that your server uses for sending files.
+ # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
+ # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
+
+ # Mount Action Cable outside main process or domain
+ # config.action_cable.mount_path = nil
+ # config.action_cable.url = 'wss://example.com/cable'
+ # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
+
+ # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
+ # config.force_ssl = true
+
+ # Use the lowest log level to ensure availability of diagnostic information
+ # when problems arise.
+ config.log_level = :info
+
+ # Prepend all log lines with the following tags.
+ config.log_tags = [:request_id]
+
+ # Use a different cache store in production.
+ # config.cache_store = :mem_cache_store
+
+ # Use a real queuing backend for Active Job (and separate queues per environment)
+ # config.active_job.queue_adapter = :resque
+ # config.active_job.queue_name_prefix = "footlight-condenser_#{Rails.env}"
+ config.action_mailer.perform_caching = false
+
+ # Ignore bad email addresses and do not raise email delivery errors.
+ # Set this to true and configure the email server for immediate delivery to raise delivery errors.
+ # config.action_mailer.raise_delivery_errors = false
+
+ # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
+ # the I18n.default_locale when a translation cannot be found).
+ config.i18n.fallbacks = true
+
+ # Send deprecation notices to registered listeners.
+ config.active_support.deprecation = :notify
+
+ # Use default logging formatter so that PID and timestamp are not suppressed.
+ config.log_formatter = ::Logger::Formatter.new
+
+ # Use a different logger for distributed setups.
+ # require 'syslog/logger'
+ # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
+
+ if ENV["RAILS_LOG_TO_STDOUT"].present?
+ logger = ActiveSupport::Logger.new(STDOUT)
+ logger.formatter = config.log_formatter
+ config.logger = ActiveSupport::TaggedLogging.new(logger)
+ end
+
+ # Do not dump schema after migrations.
+ config.active_record.dump_schema_after_migration = false
+
+ # Canonical compatibility endpoint: DISTILLATOR_COMPAT_BASE_URL.
+ # DISTILLATOR_COMPATIBILITY_BASE_URL remains a temporary backward-compatible alias.
+ config.x.distillator.compatibility_base_url =
+ ENV["DISTILLATOR_COMPAT_BASE_URL"].presence ||
+ ENV["DISTILLATOR_COMPATIBILITY_BASE_URL"].presence
+ config.x.distillator.legacy_wringer_base_url =
+ ENV["LEGACY_WRINGER_BASE_URL"].presence ||
+ ENV["WRINGER_URL"].presence
+ config.x.distillator.allow_localhost_compatibility = false
+end
diff --git a/config/initializers/source_identity.rb b/config/initializers/source_identity.rb
new file mode 100644
index 00000000..b50fb877
--- /dev/null
+++ b/config/initializers/source_identity.rb
@@ -0,0 +1 @@
+require Rails.root.join("shared/source_identity")
diff --git a/config/routes.rb b/config/routes.rb
index 2f78c04b..2a8006b1 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -8,19 +8,49 @@
require 'sidekiq/web'
mount Sidekiq::Web => '/sidekiq'
+ get "websites/wring", to: "wringer_compatibility#show", as: :wring_websites
+
resources :websites do
# API: get /websites
+ member do
+ post :activate_after_review
+ post :activate_anyway
+ end
collection do
get 'events' # Internal Webpages Only
get 'places' # Internal Webpages Only
get 'test_api' # Internal Webpages Only
-
delete 'delete_all_statements' # Internal Webpages Only
delete 'delete_all_webpages' # Internal Webpages Only
delete 'delete_all_event_webpages' # Internal Webpages Only
end
end
+ namespace :distillator do
+ get :capabilities, to: "capabilities#index", as: :capabilities
+ get :shadow_report, to: "shadow_reports#index", as: :shadow_report
+ get "shadow_report/:id", to: "shadow_reports#show", as: :shadow_report_site
+ resources :transition_checks, only: [:create]
+
+ resources :cache, only: [:index, :show], controller: "cache" do
+ collection do
+ get :preview
+ get :compare
+ post :fetch
+ end
+
+ member do
+ get :raw
+ get :raw_view
+ get :wring_json
+ get :wring_json_view
+ end
+ end
+ end
+
+ get "/condenser/cache", to: "distillator/cache#index", as: :condenser_cache_index
+ get "/condenser/cache/compare", to: "distillator/cache#compare", as: :condenser_cache_compare
+
get 'websites/:seedurl/resources',
to: "resources#index",
as: :website_all_resources
@@ -33,6 +63,10 @@
to: "events#index_by_property",
as: :website_events_by_property
+ get 'events/:id/pipeline_health',
+ to: "events#pipeline_health",
+ as: :event_pipeline_health
+
get 'resources/:rdf_uri',
to: "resources#show",
as: :show_resources
@@ -74,6 +108,7 @@
patch 'refresh' # Internal Webpages Only
end
collection do
+ get 'compare_extracted' # Internal Webpages Only
get 'webpage' # Internal Webpages Only
get 'search_name' # When manually adding links in Console
patch 'refresh_webpage' # Internal Webpages Only
@@ -100,6 +135,24 @@
get 'options', to: 'options#index', as: :options
get 'options/wringer/:target', to: 'options#wringer', as: :set_wringer
get 'options/set_dsl_trace/:state', to: 'options#set_dsl_trace', as: :set_dsl_trace_options
+ get 'options/set_trace_visibility/:state',
+ to: 'options#set_trace_visibility',
+ as: :set_trace_visibility_options
+ get 'options/set_trace_code_length/:length',
+ to: 'options#set_trace_code_length',
+ as: :set_trace_code_length_options
+ get 'options/set_trace_output_length/:length',
+ to: 'options#set_trace_output_length',
+ as: :set_trace_output_length_options
+ post "options/trace_view_mode/:mode",
+ to: "options#set_trace_view_mode",
+ as: :set_trace_view_mode
+ post "options/trace_preset/:preset",
+ to: "options#set_trace_preset",
+ as: :set_trace_preset
+ post "options/staging_rollout_repair",
+ to: "options#repair_staging_rollout",
+ as: :repair_staging_rollout_options
post 'options', to: 'options#update'
patch 'options', to: 'options#update'
diff --git a/config/wringer.yml b/config/wringer.yml
index 9af7dc82..ec364efd 100644
--- a/config/wringer.yml
+++ b/config/wringer.yml
@@ -1,20 +1,511 @@
system_exceptions:
+
+ # -------------------------
+ # HIGH-SPECIFICITY (FIRST)
+ # -------------------------
+
+ cloudflare:
+ label: Cloudflare interstitial
+ severity: blocked
+ category: anti_bot
+ expose_in_ui: true
+ export_to_apify: true
+ hints:
+ - system_cloudflare
+ - cloudflare
+ match:
+ body_contains:
+ - "Attention Required"
+ - "Cloudflare"
+ policy:
+ action: abort_update
+ retry: true
+ cache: false
+ error_code: system_cloudflare
+
queue_it:
+ label: Queue-it waiting room
+ severity: warning
+ category: anti_bot
+ expose_in_ui: true
+ export_to_apify: true
+ hints:
+ - system_queue
+ - queue_it
+ - waiting_room
match:
- page_name: "Queue-it"
+ body_contains:
+ - "Queue-it"
+ - "Please wait while we redirect you"
policy:
action: abort_update
- retry: false
+ retry: true
cache: false
error_code: system_queue
- cloudflare:
+ akamai_bot_protection:
+ label: Akamai bot protection
+ severity: blocked
+ category: anti_bot
+ expose_in_ui: true
+ export_to_apify: true
+ hints:
+ - system_akamai
+ - akamai_bot_protection
match:
body_contains:
- - "Attention Required"
- - "Cloudflare"
+ - "Access Denied"
+ - "Reference #"
+ policy:
+ action: abort_update
+ retry: true
+ cache: false
+ error_code: system_akamai
+
+ reservatech_waiting_room:
+ label: Reservatech waiting room
+ severity: warning
+ category: anti_bot
+ expose_in_ui: true
+ export_to_apify: true
+ hints:
+ - system_reservatech_waiting_room
+ - reservatech_waiting_room
+ - waiting_room
+ match:
+ body_contains:
+ - "Reservatech"
+ - "Waiting room"
+ policy:
+ action: abort_update
+ retry: true
+ cache: false
+ error_code: system_reservatech_waiting_room
+
+ salle_attente:
+ label: Salle d'attente waiting room
+ severity: warning
+ category: anti_bot
+ expose_in_ui: true
+ export_to_apify: true
+ hints:
+ - system_salle_attente
+ - salle_attente
+ - waiting_room
+ match:
+ body_contains:
+ - "Salle d'attente"
+ policy:
+ action: abort_update
+ retry: true
+ cache: false
+ error_code: system_salle_attente
+
+ captcha:
+ label: Captcha challenge
+ severity: blocked
+ category: anti_bot
+ expose_in_ui: true
+ export_to_apify: true
+ hints:
+ - system_captcha
+ - captcha
+ match:
+ body_contains:
+ - "captcha"
+ - "Captcha"
+ - "CAPTCHA"
+ policy:
+ action: abort_update
+ retry: true
+ cache: false
+ error_code: system_captcha
+
+ # -------------------------
+ # CONTENT EDGE CASES
+ # -------------------------
+
+ empty_body:
+ label: Empty body
+ severity: warning
+ category: content
+ expose_in_ui: true
+ export_to_apify: false
+ hints:
+ - empty_body
+ match:
+ body_blank: true
+ policy:
+ action: abort_update
+ retry: true
+ cache: false
+ error_code: empty_body
+
+ html_error_page:
+ label: HTML error page
+ severity: failed
+ category: content
+ expose_in_ui: true
+ export_to_apify: true
+ hints:
+ - html_error_page
+ - generic_error_text
+ match:
+ body_contains:
+ - "Internal Server Error"
+ - "Something went wrong"
+ policy:
+ action: abort_update
+ retry: true
+ cache: false
+ error_code: html_error_page
+
+ forbidden_text:
+ label: Forbidden text observed
+ severity: blocked
+ category: security
+ expose_in_ui: true
+ export_to_apify: true
+ hints:
+ - forbidden_text
+ - blocked_or_forbidden
+ match:
+ body_contains:
+ - "Forbidden"
+ policy:
+ action: abort_update
+ retry: true
+ cache: false
+ error_code: forbidden_text
+
+ post_call_observed:
+ label: POST call observed
+ severity: info
+ category: fetch_mode
+ expose_in_ui: true
+ export_to_apify: true
+ hints:
+ - post_call_observed
+ match:
+ body_contains:
+ - "POST call"
+ policy:
+ action: observe
+ retry: false
+ cache: true
+ error_code: post_call_observed
+
+ json_post:
+ label: JSON POST request
+ severity: info
+ category: fetch_mode
+ expose_in_ui: true
+ export_to_apify: false
+ hints:
+ - json_post
+ match:
+ signals:
+ request_method: POST
+ policy:
+ action: observe
+ retry: false
+ cache: true
+ delete: false
+ error_code: json_post
+
+ legacy_phantomjs:
+ label: Legacy PhantomJS renderer
+ severity: info
+ category: renderer
+ expose_in_ui: true
+ export_to_apify: false
+ hints:
+ - legacy_phantomjs
+ match:
+ hints:
+ - legacy_phantomjs
+ policy:
+ action: observe
+ retry: false
+ cache: true
+ delete: false
+ error_code: legacy_phantomjs
+
+ phantomjs_unavailable:
+ label: PhantomJS unavailable
+ severity: failed
+ category: network
+ expose_in_ui: true
+ export_to_apify: false
+ hints:
+ - phantomjs_unavailable
+ - legacy_phantomjs
+ match:
+ hints:
+ - phantomjs_unavailable
+ signals:
+ renderer_unavailable: true
+ policy:
+ action: abort_update
+ retry: true
+ cache: false
+ delete: false
+ error_code: phantomjs_unavailable
+
+ ssl_verify_none_fallback:
+ label: SSL verify none fallback
+ severity: warning
+ category: security
+ expose_in_ui: true
+ export_to_apify: false
+ hints:
+ - ssl_verify_none_fallback
+ match:
+ hints:
+ - ssl_verify_none_fallback
+ policy:
+ action: observe
+ retry: false
+ cache: true
+ delete: false
+ error_code: ssl_verify_none_fallback
+
+ timeout:
+ label: Fetch timeout
+ severity: failed
+ category: network
+ expose_in_ui: true
+ export_to_apify: false
+ hints:
+ - timeout
+ match:
+ hints:
+ - timeout
+ policy:
+ action: abort_update
+ retry: true
+ cache: false
+ delete: false
+ error_code: timeout
+
+ blocked_url:
+ label: Blocked URL
+ severity: blocked
+ category: network
+ expose_in_ui: true
+ export_to_apify: false
+ hints:
+ - blocked_url
+ - blocked
+ match:
+ signals:
+ network_status: blocked
+ hints:
+ - blocked_url
policy:
action: abort_update
retry: false
cache: false
- error_code: system_cloudflare
+ delete: false
+ error_code: blocked_url
+
+ phantomjs_iframe_malformed_json:
+ label: PhantomJS iframe malformed JSON
+ severity: failed
+ category: renderer
+ expose_in_ui: true
+ export_to_apify: false
+ hints:
+ - phantomjs_iframe_malformed_json
+ match:
+ hints:
+ - phantomjs_iframe_malformed_json
+ policy:
+ action: abort_update
+ retry: true
+ cache: false
+ delete: false
+ error_code: phantomjs_iframe_malformed_json
+
+ phantomjs_iframe_missing_child_content:
+ label: PhantomJS iframe missing child content
+ severity: failed
+ category: renderer
+ expose_in_ui: true
+ export_to_apify: false
+ hints:
+ - phantomjs_iframe_missing_child_content
+ match:
+ hints:
+ - phantomjs_iframe_missing_child_content
+ policy:
+ action: abort_update
+ retry: true
+ cache: false
+ delete: false
+ error_code: phantomjs_iframe_missing_child_content
+
+ phantomjs_iframe_missing_response_body:
+ label: PhantomJS iframe missing response body
+ severity: failed
+ category: renderer
+ expose_in_ui: true
+ export_to_apify: false
+ hints:
+ - phantomjs_iframe_missing_response_body
+ match:
+ hints:
+ - phantomjs_iframe_missing_response_body
+ policy:
+ action: abort_update
+ retry: true
+ cache: false
+ delete: false
+ error_code: phantomjs_iframe_missing_response_body
+
+ last_good_preserved_failure:
+ label: Last good content preserved after failure
+ severity: warning
+ category: content
+ expose_in_ui: true
+ export_to_apify: false
+ hints:
+ - last_good_preserved_failure
+ match:
+ signals:
+ last_good_preserved_failure: true
+ policy:
+ action: observe
+ retry: false
+ cache: true
+ delete: false
+ error_code: last_good_preserved_failure
+
+ # -------------------------
+ # REDIRECT / SEMANTIC CASES
+ # -------------------------
+
+ redirect_to_listing:
+ label: Redirect to listing
+ severity: failed
+ category: redirect
+ expose_in_ui: true
+ export_to_apify: false
+ hints:
+ - redirect_to_listing
+ match:
+ signals:
+ redirect_type: normal
+ final_url_patterns:
+ - '/events$'
+ - '/evenements$'
+ - '/programmation$'
+ - '/$'
+ policy:
+ action: abort_update
+ retry: false
+ cache: false
+ delete: true
+ error_code: redirect_to_listing
+
+ redirect_to_listing_legacy_body:
+ label: Redirect to listing
+ severity: failed
+ category: redirect
+ expose_in_ui: true
+ export_to_apify: false
+ hints:
+ - redirect_to_listing
+ match:
+ body_contains:
+ - "jet-listing-dynamic-field"
+ - "listing-grid"
+ body_not_contains:
+ - "single-evenements"
+ - "elementor-location-single"
+ - "postid-"
+ final_url_patterns:
+ - '/events/?$'
+ - '/evenements/?$'
+ - '/programmation/?$'
+ policy:
+ action: abort_update
+ retry: false
+ cache: false
+ delete: true
+ error_code: redirect_to_listing
+
+ # -------------------------
+ # GENERIC HTTP (LAST)
+ # -------------------------
+
+ http_404:
+ label: HTTP 404 Not Found
+ severity: failed
+ category: http
+ expose_in_ui: true
+ export_to_apify: false
+ hints:
+ - http_404
+ match:
+ http_code: 404
+ policy:
+ action: abort_update
+ retry: false
+ cache: false
+ error_code: http_404
+
+ http_403:
+ label: HTTP 403 Forbidden
+ severity: failed
+ category: http
+ expose_in_ui: true
+ export_to_apify: true
+ hints:
+ - http_403
+ - blocked_or_forbidden
+ match:
+ http_code: 403
+ policy:
+ action: abort_update
+ retry: true
+ cache: false
+ error_code: http_403
+
+ http_5xx:
+ label: HTTP 5xx server error
+ severity: failed
+ category: http
+ expose_in_ui: true
+ export_to_apify: true
+ hints:
+ - http_server_error
+ - server_error
+ match:
+ http_code: [500, 502, 503, 504]
+ policy:
+ action: abort_update
+ retry: true
+ cache: false
+ error_code: http_server_error
+
+ generic_error_text:
+ label: Generic error text observed
+ severity: failed
+ category: content
+ expose_in_ui: true
+ export_to_apify: true
+ hints:
+ - generic_error_text
+ match:
+ body_text_contains:
+ - "An error occurred"
+ - "A critical error has occurred"
+ - "Une erreur est survenue"
+ - "Erreur 500"
+ - "Error 500"
+ policy:
+ action: abort_update
+ retry: true
+ cache: false
+ error_code: generic_error_text
diff --git a/data/migration_baseline/co_motion_ca.jsonld b/data/migration_baseline/co_motion_ca.jsonld
new file mode 100644
index 00000000..8006b39a
--- /dev/null
+++ b/data/migration_baseline/co_motion_ca.jsonld
@@ -0,0 +1 @@
+[{"@id":"http://kg.footlight.io/resource/co-motion-ca_retrouvailles-johnson-et-lemelin-un-dialogue-musical","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/OSL/GC-2025-26/GC/OSL_COMOTION_SITE_WEB5.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K10-533"},{"@id":"http://kg.artsdata.ca/resource/K5-431"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/retrouvailles-johnson-et-lemelin-un-dialogue-musical/"}],"http://schema.org/name":[{"@language":"fr","@value":"Les Grands Concerts"}],"http://schema.org/keywords":[{"@value":"[\"Musique classique\", \"Spectacle en salle\"]"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/retrouvailles-johnson-et-lemelin-un-dialogue-musical/#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/f56c4b49-240a-447d-8212-5f0c6333e42a"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-11T19:30:00-04:00"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003c/p\u003eEn début de programme, l’OSL vous fera entendre l’Ouverture de la compositrice et pianiste française Louise Farrenc, grande figure du romantisme. Suivra le Concerto n°4 de Beethoven, interprété par le pianiste Stéphane Lemelin. Ce concert sera l’occasion de touchantes retrouvailles avec notre chef Adam Johnson, dont la toute première expérience en salle de concert fut profondément marquée par l’écoute d’un pianiste professionnel sur scène, M. Lemelin, alors soliste invité et devenu plus tard son professeur à l’Université de l’Alberta.\u003cbr /\u003e\u003cbr /\u003e En seconde partie, place à la Symphonie n°9 de Chostakovitch. Composée en 1945, elle était attendue comme un hymne triomphal à la victoire soviétique, mais le compositeur livra plutôt une partition pleine de légèreté, d’ironie et de fausse insouciance. Jamais encore interprétées par l’OSL, ces œuvres font de ce concert un moment inédit, façonné par la signature artistique d’Adam Johnson, et qui ouvre une nouvelle page prometteuse pour l’avenir de l’Orchestre.\u003cp\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e\u003cstrong\u003e\u003c/strong\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eChef d’orchestre \u003c/strong\u003e: Adam Johnson\u003cstrong\u003e\u003cbr /\u003eSoliste\u003c/strong\u003e : Stéphane Lemelin, piano\u003c/p\u003e\n\n\n\u003cp\u003e\u003cstrong\u003eRépertoire\u003c/strong\u003e :\u003c/p\u003e\n\u003cp\u003eLouise Farrenc, Ouverture n° 1 en mi mineur, op. 23\u003cbr /\u003eLudwig van Beethoven, Concerto n° 4 pour piano en sol majeur, op. 58\u003cbr /\u003eDmitri Chostakovitch, Symphonie n° 9 en mi bémol majeur, op. 70\u003c/p\u003e"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_retrouvailles-johnson-et-lemelin-un-dialogue-musical#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/retrouvailles-johnson-et-lemelin-un-dialogue-musical/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:16:43.726-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/retrouvailles-johnson-et-lemelin-un-dialogue-musical/"}]},{"@id":"http://kg.footlight.io/resource/f56c4b49-240a-447d-8212-5f0c6333e42a","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Musique classique"}],"http://www.w3.org/2004/02/skos/core#closeMatch":[{"@id":"http://kg.artsdata.ca/resource/ClassicalMusicPerformance"}]},{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place","@type":["http://schema.org/Place"],"http://schema.org/name":[{"@language":"fr","@value":"Salle André-Mathieu"}],"http://schema.org/address":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place-PostalAddress"}],"http://schema.org/sameAs":[{"@value":"http://kg.artsdata.ca/resource/K11-211"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_retrouvailles-johnson-et-lemelin-un-dialogue-musical#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"65.00"}]},{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place-PostalAddress","@type":["http://schema.org/PostalAddress"],"http://schema.org/addressLocality":[{"@language":"fr","@value":"Laval"}],"http://schema.org/addressCountry":[{"@language":"fr","@value":"CA"}],"http://schema.org/streetAddress":[{"@language":"fr","@value":"475 Bd de l'Avenir"}],"http://schema.org/postalCode":[{"@value":" H7N 5H9"}],"http://schema.org/addressRegion":[{"@language":"fr","@value":"Québec"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_alexandre-forest-en-rodage","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/alexandre-forest-en-rodage/"}],"http://schema.org/name":[{"@language":"fr","@value":"En rodage"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Alexandre-Forest.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/alexandre-forest-en-rodage/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-07-24T20:00:00-04:00"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eAlexandre Forest entame le rodage de son premier one man show.\u003c/p\u003e\n\u003cp\u003eC’est l’occasion parfaite de le découvrir et de vous laisser emporter dans son univers. Grâce à un alliage subtil entre autodérision et finesse d’esprit, l’humoriste réussit à capturer avec brio l’essence des petites et grandes incongruences de la vie quotidienne.\u003cbr /\u003e\u003ca href=\"https://www.youtube.com/@Alexandre0Forest\" target=\"_blank\" rel=\"noreferrer noopener\"\u003e\u003c/a\u003e\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_alexandre-forest-en-rodage#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/alexandre-forest-en-rodage/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:16:27.824-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/alexandre-forest-en-rodage/"}]},{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Humour"}],"http://www.w3.org/2004/02/skos/core#closeMatch":[{"@id":"http://kg.artsdata.ca/resource/ComedyPerformance"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_alexandre-forest-en-rodage#Offer_0","http://schema.org/price":[{"@value":"32.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_cest-quoi-ta-toune","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/CEST-QUOI-TA-TOUNE-3.jpg"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/cest-quoi-ta-toune/"}],"http://schema.org/name":[{"@language":"fr","@value":"C'est quoi ta toune?"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-24T19:30:00-04:00"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/cest-quoi-ta-toune/#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cstrong\u003eC’est quoi ta toune? est LE spectacle interactif où le public est maître de la soirée! \u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eRevivez les grands succès des années 60 à aujourd’hui qui ont fait chanter et danser toutes les générations! De Céline Dion à Tina Turner, en passant par Queen, vivez une soirée festive qui plaira à tous les goûts! À chaque représentation, l’expérience est unique : c’est vous qui choisissez les blocs musicaux qui façonneront la soirée! Les artistes découvriront en même temps que vous le spectacle qu’ils interpréteront. Grâce à une plateforme immersive, suivez en direct les résultats des votes et voyez l’impact de vos choix sur scène. Près de 150 chansons sont prêtes: à vous de décider ce que vous voulez entendre! Sur scène, Suzie Villeneuve, Jordan Lévesque, Jason Mcnally, mis en scène et animé par Joel Legendre. Ce spectacle vous plongera dans une ambiance festive et vous fera chanter et danser du début à la fin! Laissez-vous emporter par l’énergie contagieuse de ce spectacle original où chaque soir réserve son lot de surprises et d’émotions. \u003cstrong\u003eALORS, C’EST QUOI TA TOUNE?\u003c/strong\u003e\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_cest-quoi-ta-toune#Offer_0"}]},{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Musique"}],"http://www.w3.org/2004/02/skos/core#closeMatch":[{"@id":"http://kg.artsdata.ca/resource/MusicPerformance"}]},{"@id":"https://co-motion.ca/spectacles/cest-quoi-ta-toune/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:16:33.230-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/cest-quoi-ta-toune/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_cest-quoi-ta-toune#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"68.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_quest-quon-a-fait-au-bon-dieu","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/quest-quon-a-fait-au-bon-dieu/"}],"http://schema.org/name":[{"@language":"fr","@value":"Qu'est-ce qu'on a fait au bon dieu?"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Quest-ce-quOn-a-fait-au-bon-dieu.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/quest-quon-a-fait-au-bon-dieu/#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"[\"Théâtre\", \"Spectacle en salle\"]"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-07T19:30:00-04:00"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cbr /\u003e\u003cstrong\u003eLa comédie qui rit de nos préjugés !\u003cbr /\u003e\u003c/strong\u003e\u003cstrong\u003e« Plus 40 000 spectateurs au Québec ! 19 millions d'entrée dans le monde ! »\u003c/strong\u003e \u003c/p\u003e\n\n\n\u003cp\u003eAlain Bouchard, un fier Québécois, a toujours élevé ses filles dans un esprit de tolérance et d’ouverture. Cependant, il se retrouve face à une réalité qui le déstabilise : trois de ses quatre filles sont en couple avec des hommes d’origines différentes. Chaque célébration familiale devient un véritable défi, rempli de malentendus et de tensions qui mettent à l’épreuve l’harmonie familiale. \u003c/p\u003e\n\u003cp\u003ePour restaurer la paix et la joie dans leur foyer, Marie, la femme d’Alain, propose une grande réunion de Noël. Alain y voit une dernière chance de présenter un Québécois \"pure laine\" à sa quatrième fille, Karine. Mais alors que les préparatifs s’intensifient, il réalise que cette fête pourrait lui réserver bien des surprises ! \u003c/p\u003e\n\u003cp\u003eVenez découvrir une comédie pleine de chaleur, de rires et de réflexions sur la diversité, où les liens du cœur transcendent les différences. Préparez-vous à une aventure touchante et hilarante, où chaque membre de la famille a quelque chose à apprendre et à partager. Un spectacle incontournable pour célébrer la richesse de notre monde moderne ! \u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e\u003csup\u003eDe Philippe de Chauveron et Guy Laurent \u003cbr /\u003eAdaptation : Emmanuel Reichenbach \u003cbr /\u003eMise en scène : Michel-Maxime Legault \u003cbr /\u003eUne coproduction de Juste pour rire et Encore\u003c/sup\u003e\u003c/strong\u003e\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_quest-quon-a-fait-au-bon-dieu#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/quest-quon-a-fait-au-bon-dieu/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:16:49.297-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/quest-quon-a-fait-au-bon-dieu/"}]},{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Théâtre"}],"http://www.w3.org/2004/02/skos/core#closeMatch":[{"@id":"http://kg.artsdata.ca/resource/TheatrePerformance"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_quest-quon-a-fait-au-bon-dieu#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"70.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_souldia-nouvelle-vie","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Souldia.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-14T20:00:00-04:00"}],"http://schema.org/name":[{"@language":"fr","@value":"Nouvelle vie"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/souldia-nouvelle-vie/"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/souldia-nouvelle-vie/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Rap / Hip hop / R\u0026B\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"},{"@id":"http://kg.footlight.io/resource/a72d7b0a-b597-43c4-b2c3-2be59adfb0e0"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003csub\u003eSpectacle festif : les gens seront debout dès la première chanson! Choisissez les premières rangées si vous aimez vous lever, chanter et danser, sinon optez pour un siège plus reculé.\u003c/sub\u003e\u003c/p\u003e\n\u003cp\u003eAprès une année non conventionnelle et chargée en émotions, \u003cstrong\u003eSouldia\u003c/strong\u003e est plus déterminé que jamais à présenter son nouveau show. Haut en couleurs et plus percutant que jamais, le rappeur ose en nous proposant un spectacle énergique complètement hors du commun, passant par des sonorités rock’n’roll, voire même punk, bien agencées à son style cru. Souldia est au sommet de sa forme et sûr de lui pour un nouveau départ sur scène ahurissant!\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_souldia-nouvelle-vie#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/souldia-nouvelle-vie/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:23:07.576-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/souldia-nouvelle-vie/"}]},{"@id":"http://kg.footlight.io/resource/a72d7b0a-b597-43c4-b2c3-2be59adfb0e0","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Rap / Hip hop / R\u0026B"}],"http://www.w3.org/2004/02/skos/core#broadMatch":[{"@id":"http://kg.artsdata.ca/resource/MusicPerformance"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_souldia-nouvelle-vie#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"48.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_bleu-jeans-bleu-record-nov","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K10-580"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/bleu-jeans-bleu.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2027-02-19T20:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/bleu-jeans-bleu-record-nov/#WebPage"}],"http://schema.org/name":[{"@language":"fr","@value":"Bleu Jeans Bleu Record NºV"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/bleu-jeans-bleu-record-nov/"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Spectacle en salle\"]"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003ch3\u003e\u003cstrong\u003eLes gars de Bleu Jeans Bleu font du bien.\u003c/strong\u003e\u003c/h3\u003e\n\u003cp\u003eIls l’ont toujours fait, et ils poursuivent sur leur lancée avec un tout nouveau spectacle, la tournée \u003cstrong\u003e\u003cem\u003eRecord NºV\u003c/em\u003e\u003c/strong\u003e.\u003c/p\u003e\n\u003cp\u003eL’élégance d’un parfum haut de gamme, le réconfort d’un macaroni à la viande.\u003cbr /\u003eL’énergie d’un Monster Truck, la finesse d’une Mercedes vintage.\u003c/p\u003e\n\u003cp\u003eCe nouveau spectacle allie irrésistiblement leur musique efficace à leur humour décalé, dans une parfaite complicité avec le public — une signature qui fait de \u003cstrong\u003eBJB\u003c/strong\u003e un des groupes chouchous de la scène québécoise.\u003c/p\u003e\n\u003cp\u003eAmbiance feutrée, décor italo-romantique… laissez-vous envoûter par \u003cstrong\u003ela fragrance du bonheur pur!\u003c/strong\u003e\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_bleu-jeans-bleu-record-nov#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/bleu-jeans-bleu-record-nov/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:24:11.422-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/bleu-jeans-bleu-record-nov/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_bleu-jeans-bleu-record-nov#Offer_0","http://schema.org/price":[{"@value":"47.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_generation-musiqueplus-86-96","http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/9475525c-10fa-4a53-b89b-8c7d9ba68726"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Musique-Plus-2.jpg"}],"http://schema.org/name":[{"@language":"fr","@value":"Génération MusiquePlus – 86-96"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/generation-musiqueplus-86-96/#WebPage"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-07-10T20:00:00-04:00"}],"http://schema.org/keywords":[{"@value":"[\"Variétés\", \"Spectacle en salle\"]"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/generation-musiqueplus-86-96/"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003ch5\u003ePour célébrer ses 40 ans, le phénomène télévisuel MusiquePlus est de retour… sur scène! \u003c/h5\u003e\n\u003cp\u003eRevivez la frénésie de la meilleure des époques avec le spectacle \u003cstrong\u003eGénération MusiquePlus – 86-96\u003c/strong\u003e qui propose les meilleurs hits, vidéoclips, entrevues et anecdotes de cette décennie.\u003c/p\u003e\n\u003cp\u003eAlors que MusiquePlus entrait dans le salon des gens, c’est maintenant à votre tour d’entrer dans l’univers de MusiquePlus pour une soirée des plus festives. Animé par les VJs vedettes \u003cstrong\u003eGeneviève Borne\u003c/strong\u003e et \u003cstrong\u003ePaul Sarrasin\u003c/strong\u003e, ce spectacle vous permet de replonger dans vos souvenirs grâce à des extraits iconiques, des confidences inédites, des expériences interactives et, surtout, les plus grands hits de l’époque, livrés par \u003cstrong\u003eRafaëlle Roy\u003c/strong\u003e et \u003cstrong\u003eJonas Tomalty\u003c/strong\u003e, accompagnés de talentueux musiciens. De Nirvana aux BB, du Combat des clips à SolidRok, de Madonna à Jean Leloup, du Cimetière des CD à Bouge de là… vous pourrez revivre un condensé de votre programmation préférée le temps d’une soirée.\u003c/p\u003e\n\u003cp\u003eSi aujourd’hui la chaîne télé n’est plus, elle est encore bien ancrée dans la mémoire collective de toute une génération et occupe une place de choix dans son cœur. Préparez vos jeans délavés et votre perfecto parce qu’avec ce spectacle, c’est l’heure de rallumer les néons, de remonter le son et de rassembler la génération MusiquePlus!\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_generation-musiqueplus-86-96#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-07-25T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_generation-musiqueplus-86-96#2026-07-10T200000-0400"},{"@id":"http://kg.footlight.io/resource/co-motion-ca_generation-musiqueplus-86-96#2026-07-11T200000-0400"},{"@id":"http://kg.footlight.io/resource/co-motion-ca_generation-musiqueplus-86-96#2026-07-17T200000-0400"},{"@id":"http://kg.footlight.io/resource/co-motion-ca_generation-musiqueplus-86-96#2026-07-18T200000-0400"},{"@id":"http://kg.footlight.io/resource/co-motion-ca_generation-musiqueplus-86-96#2026-07-24T200000-0400"},{"@id":"http://kg.footlight.io/resource/co-motion-ca_generation-musiqueplus-86-96#2026-07-25T200000-0400"}]},{"@id":"http://kg.footlight.io/resource/9475525c-10fa-4a53-b89b-8c7d9ba68726","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Variétés"}],"http://www.w3.org/2004/02/skos/core#closeMatch":[{"@id":"http://kg.artsdata.ca/resource/VarietyShow"}]},{"@id":"https://co-motion.ca/spectacles/generation-musiqueplus-86-96/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:24:17.103-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/generation-musiqueplus-86-96/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_generation-musiqueplus-86-96#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"55.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_generation-musiqueplus-86-96#2026-07-10T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Génération MusiquePlus – 86-96"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-07-10T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_generation-musiqueplus-86-96#2026-07-11T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Génération MusiquePlus – 86-96"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-07-11T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_generation-musiqueplus-86-96#2026-07-17T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Génération MusiquePlus – 86-96"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-07-17T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_generation-musiqueplus-86-96#2026-07-18T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Génération MusiquePlus – 86-96"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-07-18T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_generation-musiqueplus-86-96#2026-07-24T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Génération MusiquePlus – 86-96"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-07-24T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_generation-musiqueplus-86-96#2026-07-25T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Génération MusiquePlus – 86-96"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-07-25T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_fils-manques","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/MDA/Programmation-ville/Th%C3%A9atre/2025-2026/Fils-manqu%C3%A9s.jpg"}],"http://schema.org/name":[{"@language":"fr","@value":"Fils manqués?"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-05T19:30:00-05:00"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-77"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003csub\u003eLa Tourbière\u003c/sub\u003e\u003cbr /\u003e\u003c/p\u003e\n\u003ch3\u003eUn conte poignant et hilarant incarné par deux grands artistes circassiens qui transforment l’abandon paternel en un voyage inoubliable.\u003c/h3\u003e\n\u003cp\u003eDeux clowns règlent leurs comptes avec leur père absent en jouant leur vie. Clin d’œil au livre de psycho-pop culte \u003cem\u003ePère manquant, fils manqué\u003c/em\u003e, le spectacle magnifie la surprésence du clown et la surabsence du père, oscillant constamment entre l’hommage et le règlement de compte.\u003c/p\u003e\n\u003cp\u003eEst-ce qu’un acteur orphelin de père peut devenir autre chose qu’un clown? Bien sûr que oui! René Bazinet et David-Alexandre Després (DAD), qui ont travaillé avec le Cirque du Soleil, nous plongent dans un monde chimérique où le père est absent de l’évolution d’un fils. Enfermés dans une prison, ils observent leur souffrance de l’abandon et leur désir de plaire pour pallier ce manque profond. Ce conte tragique tout aussi hilarant que triste est l’une des belles surprises de la saison dernière.\u003c/p\u003e\n\u003cp\u003eDepuis 2011, La Tourbière produit des spectacles intimes et révolutionnaires. Ses relectures d’œuvres du répertoire comme ses créations sont toutes reliées par une recherche formelle, une poésie tragicomique et un militantisme.\u003c/p\u003e\n\u003cp\u003eCe spectacle est présenté avec la collaboration des\u003cstrong\u003e Voyagements – Théâtre de création en tournée\u003c/strong\u003e.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eDurée : 70 minute\u003c/strong\u003e\u003cbr /\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e\u003cstrong\u003eProfitez de votre venue à la Maison des arts pour visiter les expositions en cours à la \u003ca href=\"https://www.laval.ca/culture/maison-des-arts/expositions-salle-alfred-pellan/expositions/\" target=\"_blank\" rel=\"noreferrer noopener\"\u003eSalle Alfred Pellan\u003c/a\u003e.\u003c/strong\u003e\u003cbr /\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"http://latourbiere.com\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eSite Web du Théâtre La tourbière\u003c/a\u003e \u003ca href=\"https://www.laval.ca/calendrier-activites/fils-manques-mda-theatre/\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eMaison des arts de Laval\u003c/a\u003e\u003c/p\u003e"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/fils-manques/"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/fils-manques/#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/keywords":[{"@value":"[\"Théâtre\", \"Spectacle en salle\"]"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/1110006626?share=copy"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_fils-manques#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/fils-manques/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:17:27.958-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/fils-manques/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_fils-manques#Offer_0","http://schema.org/price":[{"@value":"45.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_gab-bouchard-nouveau-spectacle","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Nouveau spectacle"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Gab-Bouchard.jpg"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-212"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-12T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/gab-bouchard-nouveau-spectacle/#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eCommencer et finir sur un gros « high » et se laisser de la manœuvre pour des pièces plus douces tout au long, sans jamais négliger les sentiments : \u003cstrong\u003eGab Bouchard\u003c/strong\u003e sait comment ça marche, un bon show. Fort du succès de la tournée panquébécoise de Grafignes, Gab Bouchard revient avec un spectacle élaboré autour de son troisième album, un disque un peu plus lumineux que son prédécesseur. On y retrouve la confluence de country-folk, pop-rock et soul qui fait sa marque distinctive – on y découvre aussi des arrangements plus rentre-dedans que sur les enregistrements. Sillonnant les routes du Québec, Gab Bouchard s’en vient vers vous accompagné de ses fidèles musiciens Pierre-Olivier Gagnon (basse), Mathieu Quenneville (claviers), Zachary Boileau (guitare) et Victor Tremblay-Desrosiers (batterie).\u003c/p\u003e"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/gab-bouchard-nouveau-spectacle/"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Spectacle en salle\"]"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_gab-bouchard-nouveau-spectacle#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/gab-bouchard-nouveau-spectacle/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:20:57.686-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/gab-bouchard-nouveau-spectacle/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_gab-bouchard-nouveau-spectacle#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"36.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_chantal-lamarre-steppettes-et-cornemuse","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Chantal-Lamarre.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/name":[{"@language":"fr","@value":"Steppettes et cornemuse"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"},{"@id":"http://kg.footlight.io/resource/9475525c-10fa-4a53-b89b-8c7d9ba68726"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/chantal-lamarre-steppettes-et-cornemuse/"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Variétés\", \"Spectacle en salle\"]"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/chantal-lamarre-steppettes-et-cornemuse/#WebPage"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-19T20:00:00-04:00"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eAprès 40 ans dans le métier, c’est un retour aux sources, une salle, une scène, un spot; \u003cstrong\u003eChantal Lamarre\u003c/strong\u003e convie le public à une soirée comme dans les bons vieux cabarets d’antan où on pousse la chansonnette et on ose la steppette.\u003c/p\u003e\n\n\u003cp\u003eObservatrice du monde, oiseau moqueur, animatrice et fille de scène qui, jusqu’à 60 ans, n’a jamais pensé la moindre seconde s’y aventurer seule, « elle chante, elle danse, elle joue la comédie », les « oooo » devraient succéder à des « aaa », avec promesse de rires et, peut-être même, l’indispensable petit \"motton\" d’émotion; bref, en toute prétention, une formule gagnante pour un variétés aux codes vintages qui exploite des enjeux bien actuels.\u003c/p\u003e\n\n\u003cp\u003eLe regard dans le rétroviseur pour envisager l’avenir avec moins de lourdeur; la pipelette qui commente les grands évènements avec son sens critique et sa drôlerie, la fille qui parle de société et de culture avec impétuosité et la femme pitre qui n’a jamais eu peur du ridicule se retrouveront, le temps d’une belle soirée, pour sortir de la grisaille, avoir la banane et l’envie de voir un peu la vie autrement… Mesdames, mesdemoiselles, messieurs, et tout ce qui se situe entre les deux, jeunes et nouveaux vieux ; Steppettes et cornemuses, est définitivement à inscrire à votre carnet de sorties!\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eSpectacle de variété/humoristique style Cabaret \u003c/strong\u003e\u003c/p\u003e"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_chantal-lamarre-steppettes-et-cornemuse#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/chantal-lamarre-steppettes-et-cornemuse/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:23:22.404-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/chantal-lamarre-steppettes-et-cornemuse/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_chantal-lamarre-steppettes-et-cornemuse#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"48.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_verdict-2","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/verdict-2.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-14T19:30:00-04:00"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/verdict-2/#WebPage"}],"http://schema.org/keywords":[{"@value":"[\"Théâtre\", \"Spectacle en salle\"]"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eAprès l’immense succès de Verdict I présenté en 2022, nous sommes ravis de revenir sur scène pour un deuxième volet mettant en vedette Paul Doucet et Sonia Vachon dans de nouvelles plaidoiries abordant divers débats de société. \u003c/p\u003e\n\u003cp\u003e Que l’on pense à la détresse des proches aidants, aux cas d’atteinte à la réputation ou encore à la violence conjugale, la société québécoise a débattu lorsque ces causes ont été entendues en justice.\u003c/p\u003e\n\u003cp\u003e Ce spectacle offre une expérience interactive, enrichie par une mise en contexte multimédia, où le public est invité à se positionner lors du dernier procès en rendant un verdict, comme le feraient les membres d’un jury. \u003c/p\u003e\n\u003cp\u003eÉtape ultime d’un procès, les plaidoiries sont imagées, percutantes et touchantes. Un regard poignant et éclairé sur ce qui nous définit comme société. \u003c/p\u003e"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/verdict-2/"}],"http://schema.org/name":[{"@language":"fr","@value":"Verdict 2"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_verdict-2#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/verdict-2/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:23:52.633-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/verdict-2/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_verdict-2#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"60.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_angel-forrest","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Angel-FORREST.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-28T20:00:00-04:00"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Chanson anglophone\", \"Jazz / Blues\", \"Spectacle en salle\"]"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/angel-forrest/"}],"http://schema.org/name":[{"@language":"fr","@value":"Get It On"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eEn 1988, \u003cstrong\u003eAngel Forrest\u003c/strong\u003e entame une carrière musicale prolifique qui s'étend désormais sur plus de trois décennies. Son premier album, \u003cem\u003eSecondhand Blues\u003c/em\u003e, présente plusieurs classiques. C’est toutefois son spectacle hommage à la légendaire \u003cem\u003eJanis Joplin\u003c/em\u003e qui la fait connaître aux yeux du grand public. Son premier album de composition originale, \u003cem\u003eHere For You\u003c/em\u003e, s'est ensuite vendu à plus de 30 000 exemplaires et a été suivi par de plusieurs albums récompensés dont \u003cem\u003eCome Alive\u003c/em\u003e, \u003cem\u003eMother Tongue Blues\u003c/em\u003e et \u003cem\u003eAngel's 11\u003c/em\u003e. Son dernier opus \u003cem\u003elive\u003c/em\u003e, \u003cem\u003eElectric Love\u003c/em\u003e, a quant a lui dominé les palmarès blues à travers le monde.\u003c/p\u003e\n\u003cp\u003eComme si ce n ‘était pas assez, l’artiste montréalaise a été nommée \u003cem\u003echanteuse de l'année\u003c/em\u003e à sept reprises par la \u003cem\u003eToronto Blues Society\u003c/em\u003e entre 2013 et 2020, a\u003cem\u003euteure-compositrice de l'année\u003c/em\u003e en 2014 et 2016 et finaliste du \u003cem\u003eMemphis International Blues Challenge 2018\u003c/em\u003e.\u003c/p\u003e\n\u003cp\u003eAprès 2 longues années loin de la scène, Angel est maintenant de retour et, plus forte que jamais! Accompagnée de complices musiciens, Angel présentera des chansons de son plus récent album album \u003cem\u003eAngel's 11 Volume II\u003c/em\u003e en plus de revisiter Hell Bent With Grace, nominé récemment aux Juno Awards, sans toutefois délaisser les classiques demandés maintes et maintes fois tout au long de sa carrière.\u003c/p\u003e\n\u003ch4\u003eLe spectacle \u003cem\u003eGet it On\u003c/em\u003e vous laissera émerveillée et vous en demanderez encore et encore!\u003c/h4\u003e"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/angel-forrest/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"},{"@id":"http://kg.artsdata.ca/resource/Chansonanglophone"},{"@id":"http://kg.footlight.io/resource/9da661fe-089b-4061-a274-ac91dfdc45c8"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_angel-forrest#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/angel-forrest/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:10:36.245-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/angel-forrest/"}]},{"@id":"http://kg.footlight.io/resource/9da661fe-089b-4061-a274-ac91dfdc45c8","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Jazz / Blues"}],"http://www.w3.org/2004/02/skos/core#broadMatch":[{"@id":"http://kg.artsdata.ca/resource/MusicPerformance"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_angel-forrest#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"42.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_jf-otis-avec-plaisir","@type":["http://schema.org/Event"],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/J-F-Otis.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-13T20:00:00-05:00"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/jf-otis-avec-plaisir/"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Avec plaisir"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/jf-otis-avec-plaisir/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cstrong\u003eJF\u003c/strong\u003e est un gars de Rimouski qui prend d’excellentes mauvaises décisions. Bachelier en administration des affaires devenu humoriste, star du web, il dit que ce fut une décision qui l’a rendu heureux mais qui a rendu ses parents… nerveux. La meilleure décision qu’il a prise selon lui après avoir commencé à faire des vidéos sur internet et s’acheter un lave vaisselle. \u003c/p\u003e\n\u003cp\u003eL'improvisation est un élément important de son stand-up. Chaque représentation devient alors unique, mais toujours aussi efficace. Lorsqu'il est sur scène, on a envie d’écouter et de rire avec ce « gros nounours ». \u003c/p\u003e\n\u003cp\u003eL’assistance est placée dans une ambiance festive, comme si l’on était tous autour d’un feu de camp en compagnie d’un vieil ami racontant des anecdotes sans queue ni tête à nous en faire renverser notre verre ! \u003c/p\u003e\n\u003cp\u003eEn entendant ses histoires vous allez être découragés ou encore vous allez le juger mais une chose est sûre, vous allez rire.\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_jf-otis-avec-plaisir#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/jf-otis-avec-plaisir/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:15:48.959-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/jf-otis-avec-plaisir/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_jf-otis-avec-plaisir#Offer_0","http://schema.org/price":[{"@value":"36.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_christian-marc-gendron-trilogie","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Christian-marc-Gendron-2.jpg"}],"http://schema.org/name":[{"@language":"fr","@value":"Trilogie"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-21T20:00:00-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/christian-marc-gendron-trilogie/"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eAprès le succès phénoménal de la série de concerts \u003cem\u003ePianoMan\u003c/em\u003e, qui a rassemblé plus de 150 000 spectateurs à travers le Québec au cours des dernières années, \u003cstrong\u003eChristian Marc Gendron\u003c/strong\u003e revient sur scène dès avril 2026 avec un tout nouveau spectacle : \u003cem\u003e\u003cstrong\u003eTrilogie\u003c/strong\u003e\u003c/em\u003e. \u003c/p\u003e\n\u003cp\u003eCe concept unique réunira les meilleurs numéros de ses trois dernières tournées, en plus de proposer de nouvelles créations inspirées du répertoire des grands \u003cem\u003ePianoman\u003c/em\u003e tels que Jerry Lee Lewis, Ray Charles, Stevie Wonder, Elton John et, bien sûr, Billy Joel. Le spectacle inclura également un hommage à Gino Vannelli ainsi que plusieurs moments d’humour comme seul Christian Marc Gendron sait les livrer. \u003c/p\u003e\n\u003cp\u003eAvec \u003cem\u003eTrilogie\u003c/em\u003e, Christian Marc Gendron vous invite dans son univers musical et scénique incomparable, toujours accompagné de son équipe de 9 musiciens sous la direction musicale de Simon Godin et de la participation exceptionnelle de Manon Séguin. \u003c/p\u003e"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/christian-marc-gendron-trilogie/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_christian-marc-gendron-trilogie#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/christian-marc-gendron-trilogie/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:11:28.442-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/christian-marc-gendron-trilogie/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_christian-marc-gendron-trilogie#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"57.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_tete-a-tete-cor-et-archet","@type":["http://schema.org/Event"],"http://schema.org/keywords":[{"@value":"[\"Musique classique\", \"Spectacle en salle\", \"Admission générale\"]"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K5-431"},{"@id":"http://kg.artsdata.ca/resource/K10-533"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-11T14:00:00-04:00"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/OSL/GC-2025-26/OSL_COMOTION_SITE_WEB_CH2.jpg"}],"http://schema.org/name":[{"@language":"fr","@value":"Les Chambristes"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/tete-a-tete-cor-et-archet/#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/f56c4b49-240a-447d-8212-5f0c6333e42a"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/tete-a-tete-cor-et-archet/"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"https://www.patrimoine-culturel.gouv.qc.ca/rpcq/detail.do?methode=consulter\u0026id=166091\u0026type=bien#Place"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eL’OSL vous convie à son concert des Chambristes, mettant sur le devant de la scène un\u003cstrong\u003e quintette à cordes et cor\u003c/strong\u003e. L’ensemble réunira \u003cstrong\u003eFlavie Gagnon\u003c/strong\u003e et \u003cstrong\u003eMaude Fréchette\u003c/strong\u003e au violon, \u003cstrong\u003eFrédéric Lambert\u003c/strong\u003e à l’alto, \u003cstrong\u003eMarc Labranche\u003c/strong\u003e au violoncelle et \u003cstrong\u003eXavier Fortin\u003c/strong\u003e au cor.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e Wolfgang Amadeus Mozart\u003c/strong\u003e a exploré avec brio cette instrumentation atypique, lui consacrant plusieurs œuvres remarquables. Dans une atmosphère intime et chaleureuse, vous pourrez (re)découvrir toute la richesse sonore de cette formation rare.\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_tete-a-tete-cor-et-archet#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/tete-a-tete-cor-et-archet/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:16:09.283-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/tete-a-tete-cor-et-archet/"}]},{"@id":"https://www.patrimoine-culturel.gouv.qc.ca/rpcq/detail.do?methode=consulter\u0026id=166091\u0026type=bien#Place","@type":["http://schema.org/Place"],"http://schema.org/address":[{"@id":"https://www.patrimoine-culturel.gouv.qc.ca/rpcq/detail.do?methode=consulter\u0026id=166091\u0026type=bien#Place-PostalAddress"}],"http://schema.org/name":[{"@language":"fr","@value":"Église Saint-Maurice-de-Duvernay"}],"http://schema.org/sameAs":[{"@value":"https://www.wikidata.org/entity/Q58010848"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_tete-a-tete-cor-et-archet#Offer_0","http://schema.org/price":[{"@value":"28.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"https://www.patrimoine-culturel.gouv.qc.ca/rpcq/detail.do?methode=consulter\u0026id=166091\u0026type=bien#Place-PostalAddress","@type":["http://schema.org/PostalAddress"],"http://schema.org/addressLocality":[{"@language":"fr","@value":"Laval"}],"http://schema.org/streetAddress":[{"@language":"fr","@value":"1961 Rue d'Ivry"}],"http://schema.org/postalCode":[{"@value":"H7G 1S9"}],"http://schema.org/addressCountry":[{"@language":"fr","@value":"CA"}],"http://schema.org/addressRegion":[{"@language":"fr","@value":"Québec"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_liliane-blanco-binette-nouveau-spectacle","@type":["http://schema.org/Event"],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-17T20:00:00-04:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Liliane-2.jpg"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/liliane-blanco-binette-nouveau-spectacle/"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/name":[{"@language":"fr","@value":"Toréador"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eAvec une lucidité désarmante et une autodérision qui frappe juste, Liliane propose un premier One Woman Show où l’humour se mêle à l’intime. Ce n’est pas un simple spectacle, c’est une rencontre. Elle transforme ses vulnérabilités en matière comique, révélant une façon décalée d’observer le monde et d’assumer ses contradictions. Entre confidences crues et complicité partagée, elle entraîne le public dans un moment à la fois fragile et nécessaire, où l’on se reconnaît dans ce qui nous échappe et nous dépasse.\u003cbr /\u003e\u003c/p\u003e\n\u003cp\u003e \u003c/p\u003e"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/liliane-blanco-binette-nouveau-spectacle/#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_liliane-blanco-binette-nouveau-spectacle#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/liliane-blanco-binette-nouveau-spectacle/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:16:03.099-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/liliane-blanco-binette-nouveau-spectacle/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_liliane-blanco-binette-nouveau-spectacle#Offer_0","http://schema.org/price":[{"@value":"42.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_ludovick-bourgeois-karaoke-90","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/ludovick-bourgeois-karaoke-90/"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2027-01-09T20:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/ludovick-bourgeois-karaoke-90/#WebPage"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Ludovick-Bourgeois-2.jpg"}],"http://schema.org/name":[{"@language":"fr","@value":"Karaoké 90"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Spectacle en salle\"]"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eSoutenu par un riche univers visuel, dans la lignée de son plus récent spectacle en hommage aux BB, \u003cstrong\u003e\u003cem\u003eKaraoké 90\u003c/em\u003e\u003c/strong\u003e se déploiera en trois tableaux : les \u003cem\u003ehits\u003c/em\u003e québécois, ceux du groupe Les BB et les mégatubes mondiaux. Du grunge de Nirvana aux power ballades de Céline Dion, en passant par les hymnes pop et rock de U2, Backstreet Boys, Bryan Adams, Bon Jovi, sans oublier les mémorables chansons de nos artistes de chez nous Mario Pelchat, Marie Carmen et plus encore, \u003cstrong\u003eLudovick Bourgeois\u003c/strong\u003e propose une formidable plongée musicale dans cette décennie faste en succès.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e\u003cem\u003eKaraoké 90 \u003c/em\u003e\u003c/strong\u003e: c’est une célébration des années 90 et de la musique qui réunit, un moment de joyeuse nostalgie et de plaisir partagé. Mentionnons qu’à leur arrivée, les spectateurs seront invités à s’inscrire pour participer à un karaoké géant qui verra \u003cstrong\u003echaque soir une personne du public monter sur scène\u003c/strong\u003e pour interpréter sa chanson aux côtés de Ludovick et de ses musiciens, dans une ambiance que l’on promet survoltée !\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_ludovick-bourgeois-karaoke-90#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/ludovick-bourgeois-karaoke-90/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:23:40.664-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/ludovick-bourgeois-karaoke-90/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_ludovick-bourgeois-karaoke-90#Offer_0","http://schema.org/price":[{"@value":"53.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_nothing-avec-francois-lemay","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/nothing-avec-francois-lemay/"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K5-367"}],"http://schema.org/name":[{"@language":"fr","@value":"Nothing avec François Lemay"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Locations-TMC/Lemay.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-11T19:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/nothing-avec-francois-lemay/#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-213"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/keywords":[{"@value":"[\"Conférence\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.artsdata.ca/resource/Conference"},{"@id":"http://kg.artsdata.ca/resource/TalkEvent"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eNOTHING — Expérience immersive\u003c/p\u003e\n\u003cp\u003eEnvie d’une soirée qui sort du cadre ?\u003c/p\u003e\n\u003cp\u003eUne soirée qui fait réfléchir, rire… et ressentir pour vrai ?\u003c/p\u003e\n\u003cp\u003eNOTHING n’est pas une conférence classique. C’est une expérience immersive, vivante, intense et profondément humaine. Au fil de la soirée, François Lemay partage, avec simplicité, humour et authenticité, des réflexions inspirées de la pleine conscience, de la science et de l’expérience humaine. Des prises de conscience qui bousculent doucement nos façons de penser et nous rappellent les grandes évidences de la vie.Puis, sans prévenir, l’expérience s’approfondit. Portée par une musique improvisée en direct, la soirée bascule vers le ressenti. Le mental ralentit, le corps s’ouvre, et le public est invité à vivre une méditation immersive, collective, guidée dans l’instant.\u003c/p\u003e\n\n\n\n\n\n\n\n\n\n\u003cp\u003eOn rit.\u003c/p\u003e\n\u003cp\u003eOn apprend.\u003c/p\u003e\n\u003cp\u003eOn est touché.\u003c/p\u003e\n\u003cp\u003eOn lâche prise.\u003c/p\u003e\n\u003cp\u003eOn se sent vivant.\u003c/p\u003e\n\u003cp\u003eOn ressort avec le cœur grand ouvert, l’esprit plus clair, et cette phrase qui revient naturellement :\u003c/p\u003e\n\n\u003cp\u003e« Wow… c’était tellement bon. »\u003c/p\u003e\n\u003cp\u003eNOTHING, c’est une expérience à vivre, bien au-delà des mots\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_nothing-avec-francois-lemay#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/nothing-avec-francois-lemay/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:11:51.917-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/nothing-avec-francois-lemay/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_nothing-avec-francois-lemay#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"64.99"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_guylaine-tanguay-i-love-rockroll","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/guylaine-tanguay-i-love-rockroll/"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/name":[{"@language":"fr","@value":"I Love Rock 'n' roll"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Guylaine-Tanguay-2.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-30T19:30:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/guylaine-tanguay-i-love-rockroll/#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eAvec I LOVE ROCK’N’ ROLL, \u003cstrong\u003eGuylaine Tanguay\u003c/strong\u003e nous ramène là où tout a commencé : au bar Le Clairon de Normandin, où elle chantait, pendant ses études, les grands succès rock des années 80. De Joan Jett, Pat Benatar, Heart, AC/DC, Queen, Bon Jovi, The Police, Kiss et Tina Turner, jusqu’à Paul Daraîche, elle revisite avec fougue et nostalgie les chansons qui ont marqué son parcours. Un spectacle festif, vibrant et plein d’énergie!\u003c/p\u003e"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Spectacle en salle\"]"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_guylaine-tanguay-i-love-rockroll#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/guylaine-tanguay-i-love-rockroll/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:11:57.740-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/guylaine-tanguay-i-love-rockroll/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_guylaine-tanguay-i-love-rockroll#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"52.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_les-soeurs-boulay-et-elliot-maginot","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K10-414"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/les-soeurs-boulay-et-elliot-maginot/"}],"http://schema.org/name":[{"@language":"fr","@value":"Les soeurs Boulay et Elliot Maginot"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Les-soeurs-Boulay.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-10T20:00:00-04:00"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/les-soeurs-boulay-et-elliot-maginot/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Chanson francophone\", \"Spectacle en salle\"]"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"},{"@id":"http://kg.artsdata.ca/resource/Chansonfrancophone"},{"@id":"http://kg.artsdata.ca/resource/ChansonPerformance"},{"@id":"http://kg.artsdata.ca/resource/Chanson"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e Amis depuis presque 15 ans déjà, autrefois colocataires au \u003cem\u003e4488 de l’amour\u003c/em\u003e,\u003cstrong\u003e l\u003c/strong\u003e\u003cstrong\u003ees soeurs Boulay et Elliot Maginot\u003c/strong\u003e ont fait leurs débuts côte à côte. À l’époque, alors que la carrière des soeurs débutait, Maginot posait des affiches de spectacles pour joindre les deux bouts. Puis, il s’est mis à faire leurs premières parties, jusqu’à être obligé d’arrêter pour embarquer sur ses propres tournées, comme un grand! \u003c/p\u003e\n\u003cp\u003eLes années les ont brassés pas mal depuis, mais ne les ont jamais séparés, tissant entre eux des liens ressemblant aujourd’hui à une filiation familiale; pour le meilleur et pour le pire. Bibittes complexes, remplies d’humour, d’autodérision et de contradictions, ce trio monstre à trois têtes avait envie, à cette étape de leur carrière, de revenir à la genèse; faire de la musique par amour du geste et par envie d’être ensemble. \u003c/p\u003e\n\u003cp\u003eIls se réuniront donc sur scène pour se remémorer: cet appartement, cette table de cuisine, ce petit balcon arrière, ce sous-sol tapissé de vieilles cigarettes écrasées et ces bouteilles trop vite bues. Pour se replonger dans leurs débuts hésitants, parfois maladroits, mais jamais plates. Pour se rappeler, aussi (et surtout), ces chansons qui les ont façonnés et réunis - autant dans leur répertoire propre que dans la tradition des \u003cem\u003esongwriters\u003c/em\u003e, des chansonniers ou des \u003cem\u003epopstars\u003c/em\u003e. \u003c/p\u003e\n\u003cp\u003eOn les entendra en version dépouillée, à trois voix, puis quelques petits instruments acoustiques. \u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_les-soeurs-boulay-et-elliot-maginot#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/les-soeurs-boulay-et-elliot-maginot/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:12:03.909-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/les-soeurs-boulay-et-elliot-maginot/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_les-soeurs-boulay-et-elliot-maginot#Offer_0","http://schema.org/price":[{"@value":"40.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_dominique-fils-aime-the-sunshine-tour","@type":["http://schema.org/Event"],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eConsidérée comme l’une des voix incontournables du jazz vocal actuel, l’autrice compositrice interprète Montréalaise Dominique Fils-Aimé, double lauréate d’un prix JUNO, puise au cœur des traditions afro-américaines pour nourrir une œuvre à la fois sensible et engagée.\u003c/p\u003e\n\n\n\n\u003cp\u003eSur scène, elle offre une expérience immersive où sa voix lumineuse et ses explorations musicales révèlent toute la force de sa présence. Dominique Fils-Aimé captive par sa capacité à tisser un lien intime avec le public, l’invitant à partager un espace d’émotions profondes et de résonance intérieure.\u003c/p\u003e\n\n\n\n\u003cp\u003eQue ce soit au Canada (en tête d’affiche du Festival international de jazz de Montréal), en Europe (en première partie de Melody Gardot, de Diana Krall et de Jamie Cullum) ou aux États-Unis (avec des concerts acclamés au Blue Note de New York et de Los Angeles), Dominique Fils-Aimé captive ses spectateurs et les entraîne dans un voyage transcendant.\u003c/p\u003e"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/dominique-fils-aime-the-sunshine-tour/"}],"http://schema.org/name":[{"@language":"fr","@value":"The Sunshine Tour"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Dom-Fils-Aim%C3%A9.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/dominique-fils-aime-the-sunshine-tour/#WebPage"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Blues / Jazz\", \"Spectacle en salle\"]"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2027-02-11T19:30:00-05:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_dominique-fils-aime-the-sunshine-tour#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/dominique-fils-aime-the-sunshine-tour/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:12:11.060-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/dominique-fils-aime-the-sunshine-tour/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_dominique-fils-aime-the-sunshine-tour#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"49.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_lea-streliski-enfin-en-rodage","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/L%C3%A9a-Str%C3%A9liski-3.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eDans le rodage de son tout premier spectacle solo, \u003cstrong\u003eLéa Stréliski\u003c/strong\u003e espère explorer en cinq tableaux ce qui la constitue. Une occasion de venir la voir travailler ses blagues, apprendre à la connaître et rire avec elle de tout ce qu’elle est : la sœur d’une vedette, une moitié de française, l’épouse un peu lesbienne d’un homme, la mère de deux gars ados et celle d’une fille qu’elle tente de toutes ses forces de préparer au monde qui l’attend. À suivre.\u003c/p\u003e"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/lea-streliski-enfin-en-rodage/"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-25T19:30:00-04:00"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/lea-streliski-enfin-en-rodage/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-212"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Admission générale\", \"Formule cabaret\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/name":[{"@language":"fr","@value":"Enfin en rodage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_lea-streliski-enfin-en-rodage#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/lea-streliski-enfin-en-rodage/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:18:04.777-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/lea-streliski-enfin-en-rodage/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_lea-streliski-enfin-en-rodage#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"35.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_pascal-cameron-nouveau-spectacle","http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/pascal-cameron-nouveau-spectacle/"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-31T20:00:00-05:00"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Pascal-Cameron.jpg"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/pascal-cameron-nouveau-spectacle/#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003ePascal Cameron présente son premier one man show \u003cem\u003eun spectacle\u003c/em\u003e. qui est une représentation humoristique qui veut parler à tout le monde et de tout le monde. \u003c/p\u003e\n\u003cp\u003ePar contre, il est important de noter que c'est un spectacle d'humour, donc les propos de cette représentation seraient inappropriés au Parlement ou à des funérailles mais plus que correct dans un pool party, Noël, un accouchement, la St-Jean ou à crier à travers la fenêtre de ton char quand tu es pogné dans le trafic. \u003c/p\u003e\n\u003cp\u003eDe plus, \u003cem\u003eun \u003c/em\u003espectacle. a comme objectif premier de faire rire. Fack, va pas me citer comme preuve quand tu t'obstines avec du monde «Pascal Cameron a dit ça l'autre jour ». J'ai aucun diplôme sauf celui du meilleur livreur de journaux de la Montérégie 1999. (Après, j'ai lâché ma job pour me retirer au sommet) \u003c/p\u003e\n\u003cp\u003e\u003cem\u003eUn spectacle\u003c/em\u003e. c'est un show de stand up traditionnel avec des blagues fraiches. \u003c/p\u003e"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/name":[{"@language":"fr","@value":"Un spectacle"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_pascal-cameron-nouveau-spectacle#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-18T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_pascal-cameron-nouveau-spectacle#2026-01-31T200000-0500"},{"@id":"http://kg.footlight.io/resource/co-motion-ca_pascal-cameron-nouveau-spectacle#2026-09-18T200000-0400"}]},{"@id":"https://co-motion.ca/spectacles/pascal-cameron-nouveau-spectacle/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:18:10.451-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/pascal-cameron-nouveau-spectacle/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_pascal-cameron-nouveau-spectacle#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"39.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_pascal-cameron-nouveau-spectacle#2026-01-31T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Un spectacle"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-31T20:00:00-05:00"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_pascal-cameron-nouveau-spectacle#2026-09-18T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Un spectacle"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-18T20:00:00-04:00"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_neev-pas-besoin-dajouter-de-la-sauce","@type":["http://schema.org/Event"],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-18T19:30:00-05:00"}],"http://schema.org/name":[{"@language":"fr","@value":"Pas besoin d'ajouter la sauce"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Neev_2024.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/neev-pas-besoin-dajouter-de-la-sauce/#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/neev-pas-besoin-dajouter-de-la-sauce/"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cstrong\u003eNeev \u003c/strong\u003eroule sa bosse dans le milieu de l’humour depuis plusieurs années, tant à la télévision et à la radio que dans de nombreux festivals partout à travers le pays. Après avoir sillonné le Québec en première partie de Louis-José Houde, il présente maintenant son tout premier one-man-show! \u003c/p\u003e\n\u003cp\u003eDans ce spectacle, Neev pose un regard sur ce que nous sommes en tant que société. Il prend plaisir à mettre en lumière les petits travers de notre quotidien. Non seulement il nous fait part de ses réflexions, mais il nous parle avec beaucoup de sensibilité de ses origines multiples, d’intégration, et ce, sur un ton personnel et sans flafla. \u003c/p\u003e\n\u003cp\u003eAvec sa personnalité attachante, son rire chaleureux et son humour bien ficelé, Neev est déjà parfaitement épicé; pas besoin d’ajouter la sauce. \u003c/p\u003e"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_neev-pas-besoin-dajouter-de-la-sauce#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/neev-pas-besoin-dajouter-de-la-sauce/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:19:31.522-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/neev-pas-besoin-dajouter-de-la-sauce/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_neev-pas-besoin-dajouter-de-la-sauce#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"39.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_rencontre-theatre-ados-beluga","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/rencontre-theatre-ados-beluga/"}],"http://schema.org/name":[{"@language":"fr","@value":"Béluga"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/MDA/ECC/RTA.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-22T19:30:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/rencontre-theatre-ados-beluga/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"[\"Théâtre\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cstrong\u003eThéâtre DuBunker\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eMaxime, mémorable tête de turc de sa promotion au Collège Jean-Eudes, et François, impitoyable intimidateur de la polyvalente de Roxton Pond, conservent encore un goût amer de leur passage au secondaire. Dans un duel aux allures de conventum, ils s’affrontent à travers une série de tableaux intimes afin d’exorciser une bonne fois pour toute les fantômes qui hantent leur album de finissants. Seule la faveur du public – maintes fois invité à choisir un camp – permettra au vainqueur du duel, de se réconcilier avec son passé au terme de la représentation. Mais entre la lettre d’excuse que François livre à l’une de ses anciennes victimes du secondaire, ou les stratégies grinçantes que Maxime adopte pour se faire justice, il est parfois délicat de départager le meilleur être humain des deux. D’autant plus que c’est en traversant de l’un côté où de l’autre d’une ligne tracée au sol, sous les yeux des protagonistes trônant de part et d’autre de la salle, que les spectateurs sont appelés à trancher.\u003c/p\u003e\n\n\u003cp\u003e\u003cstrong\u003eTexte et mise en scène\u003c/strong\u003e: Francois Bernier et Maxime Beauregard-Martin. Conseillère à la dramaturgie: Anne-Marie OlivierBernier\u003cbr /\u003e\u003cstrong\u003eConception sonore\u003c/strong\u003e : Antonin Gougeon Moisan\u003cbr /\u003e\u003cstrong\u003eScénographie\u003c/strong\u003e: Anne-Sophie Gaudet\u003c/p\u003e"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-1056"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_rencontre-theatre-ados-beluga#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/rencontre-theatre-ados-beluga/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:12:17.018-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/rencontre-theatre-ados-beluga/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_rencontre-theatre-ados-beluga#Offer_0","http://schema.org/price":[{"@value":"40.60"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_guillaume-pineault-rodage-2026","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/guillaume-pineault-rodage-2026/"}],"http://schema.org/name":[{"@language":"fr","@value":"En rodage"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Guillaume-Pineault-2.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-07-10T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/guillaume-pineault-rodage-2026/#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e C’est reparti pour un 3ème spectacle! Mais avant de le sortir, je dois le travailler, le tester, le peaufiner, bref… le RODER ! C’est avec vous que j’ai envie de faire ça! \u003c/p\u003e\n\u003cp\u003eBienvenue au rodage de mon 3ème spectacle. \u003c/p\u003e\n\u003cp\u003eHâte de vous voir, \u003c/p\u003e\n\u003cp\u003eGuillaume \u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_guillaume-pineault-rodage-2026#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/guillaume-pineault-rodage-2026/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:12:23.177-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/guillaume-pineault-rodage-2026/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_guillaume-pineault-rodage-2026#Offer_0","http://schema.org/price":[{"@value":"37.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_tommy-neron-les-fleurs-poussent-encore","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/tommy-neron-les-fleurs-poussent-encore/"}],"http://schema.org/name":[{"@language":"fr","@value":"Les fleurs poussent encore"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Tommy-N%C3%A9ron.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2027-03-12T20:00:00-05:00"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/tommy-neron-les-fleurs-poussent-encore/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eFallait décrire mon show en quelques lignes pis j’ai décidé de ne pas laisser l’assistante du bureau le faire, ce qui explique pourquoi tu n'as pas encore vu les termes Star du web ou encore Vedette sur les réseaux sociaux. Ces termes-là, c’est comme le mot Flamboyant, Y’a pas un humoriste qui se décrit lui-même comme flamboyant. Mon spectacle, c’est un gros mélange d'observations et d'anecdotes sur mon passé qui, avec le recul, n'était pas exactement comme tout le monde. Aussi, tu peux amener ton ado, je ne pense pas le traumatiser, mais c’est à tes risques et péril. Pour le show, j'ai la chance d'être entouré de la brillante Korine Côté pour la script-édition et du talentueux Laurent Paquin à la mise en scène. Le titre aurait pu être Flamboyant mais j'ai choisi \u003cstrong\u003eLes fleurs poussent encore\u003c/strong\u003e… Tu vas comprendre pourquoi en voyant le spectacle\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_tommy-neron-les-fleurs-poussent-encore#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/tommy-neron-les-fleurs-poussent-encore/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:12:28.996-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/tommy-neron-les-fleurs-poussent-encore/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_tommy-neron-les-fleurs-poussent-encore#Offer_0","http://schema.org/price":[{"@value":"39.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_jf-pauze","http://schema.org/name":[{"@language":"fr","@value":"Les amours de seconde main"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Spectacle en salle\"]"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/JF-PAUZ%C3%89.jpg"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-12T20:00:00-04:00"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/jf-pauze/"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cstrong\u003eSpectacle festif : les gens seront debout dès la première chanson! Choisissez les premières rangées si vous aimez vous lever, chanter et danser, sinon optez pour un siège plus reculé.\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eJF Pauzé, membre des Cowboys Fringants et auteur-compositeur des chansons du groupe (Félix – Auteur-compositeur de l’année 2024), fera ses débuts en solo avec un premier album à titre d’interprète, composé de 11 nouveaux titres originaux. Une sortie des plus attendues à l’automne 2025.\u003c/p\u003e\n\u003cp\u003eJF remettra les pieds sur scène en 2026, entouré de musiciens, avec un spectacle généreux et festif qui comptera plus de 20 chansons. Un moment important dans le paysage musical québécois pour les saisons à venir!\u003c/p\u003e\n\u003cp\u003e\u003csub\u003e1$ par billet vendu sera remis à la Fondation Cowboys Fringants.\u003cbr /\u003eMerci \u0026#x1f49a;\u003c/sub\u003e\u003c/p\u003e"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/jf-pauze/#WebPage"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_jf-pauze#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-19T19:30:00-05:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_jf-pauze#2026-03-12T200000-0400"},{"@id":"http://kg.footlight.io/resource/co-motion-ca_jf-pauze#2026-11-19T193000-0500"}]},{"@id":"https://co-motion.ca/spectacles/jf-pauze/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:21:58.867-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/jf-pauze/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_jf-pauze#Offer_0","http://schema.org/price":[{"@value":"53.15"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_jf-pauze#2026-03-12T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Les amours de seconde main"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-12T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_jf-pauze#2026-11-19T193000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Les amours de seconde main"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-19T19:30:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_la-legende-de-barbe-dor-2025","@type":["http://schema.org/Event"],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-22T14:00:00-05:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/22f50ba1-3403-48a8-a279-460952d3c134"},{"@id":"http://kg.footlight.io/resource/b71d9b1c-a2b4-41f9-96c6-f206b0c4b8ae"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/MDA/Programmation-ville/Jeune-Public/2025-2026/La-l%C3%A9gende-de-Barbe-d%E2%80%99Or.jpg"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/jK9zsUG_bds"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-77"}],"http://schema.org/keywords":[{"@value":"[\"Jeune public\", \"Conte théâtral\", \"Spectacle en salle\"]"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/la-legende-de-barbe-dor-2025/"}],"http://schema.org/name":[{"@language":"fr","@value":"La légende de Barbe d'Or - 2025"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/la-legende-de-barbe-dor-2025/#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003csup\u003eLes Fabulateurs\u003c/sup\u003e \u003c/p\u003e\n\u003ch3\u003eEst-ce qu’un pirate peut être gentil?\u003c/h3\u003e\n\u003cp\u003eIl y a plus de 4 000 lunes, le monde était dirigé par un cruel personnage mi-humain, mi-requin, et sa bande de petits bandits barbus : les pirates. Parmi eux, voilà qu’un pirate au cœur tendre et à la barbe dorée se lève enfin pour défendre la paix. Quand on se bat avec une rose à la ceinture, tous les espoirs sont permis.\u003c/p\u003e\n\u003cp\u003eÀ travers un héros singulier, \u003cem\u003eLa légende de Barbe d’Or\u003c/em\u003e interpelle les enfants sur les conventions sociales entourant la figure masculine et explore les thèmes de la famille, de la gentillesse et du pardon. Parsemée d’humour absurde et de chansons originales, l’histoire est une ode à la différence et un rappel qu’il n’est jamais trop tard pour tendre la main et trouver la paix.\u003c/p\u003e\n\u003cp\u003eEntre théâtre et conte, Les Fabulateurs explorent sans cesse de nouvelles avenues pour impliquer les enfants en tant que spectateurs actifs et leur confier les outils pour réfléchir le monde et le réinventer toutes et tous ensemble.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eDurée : 60 minutes\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e\u003cstrong\u003eProfitez de votre venue à la Maison des arts pour visiter les expositions en cours à la \u003ca href=\"https://www.laval.ca/culture/maison-des-arts/expositions-salle-alfred-pellan/expositions/\" target=\"_blank\" rel=\"noreferrer noopener\"\u003eSalle Alfred Pellan\u003c/a\u003e.\u003c/strong\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e\u003c/strong\u003e\u003ca href=\"http://fabulateurs.ca\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003e\u003cbr /\u003eSite Web des Fabulateurs\u003c/a\u003e \u003ca href=\"https://www.laval.ca/calendrier-activites/legende-barbe-or-mda-conte-jeune-public/\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eMaison des arts de Laval\u003c/a\u003e\u003c/p\u003e"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_la-legende-de-barbe-dor-2025#Offer_0"}]},{"@id":"http://kg.footlight.io/resource/22f50ba1-3403-48a8-a279-460952d3c134","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Jeune public"}],"http://www.w3.org/2004/02/skos/core#closeMatch":[{"@id":"http://kg.artsdata.ca/resource/ChildrensEvent"}]},{"@id":"http://kg.footlight.io/resource/b71d9b1c-a2b4-41f9-96c6-f206b0c4b8ae","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#closeMatch":[{"@id":"http://kg.artsdata.ca/resource/Storytelling"}],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Conte théâtral"}]},{"@id":"https://co-motion.ca/spectacles/la-legende-de-barbe-dor-2025/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:10:28.801-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/la-legende-de-barbe-dor-2025/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_la-legende-de-barbe-dor-2025#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"17.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_les-soeurs-kif-kif","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/MDA/ECC/Les-soeur-kif-kif.jpg"}],"http://schema.org/name":[{"@language":"fr","@value":"Département des retours"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/22f50ba1-3403-48a8-a279-460952d3c134"},{"@id":"http://kg.footlight.io/resource/8f57c4c9-da91-4809-83ae-b5e8db471f39"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-06-06T14:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/les-soeurs-kif-kif/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-116"}],"http://schema.org/keywords":[{"@value":"[\"Jeune public\", \"Cirque\", \"théâtre clownesque\", \"Spectacle en salle\", \"Admission générale\"]"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cstrong\u003eCirque et théâtre clownesque\u003cbr /\u003eUne production des Sœurs kif-kif\u003c/strong\u003e.\u003c/p\u003e\n\u003cp\u003eDes sœurs jumelles vous accueillent au Département des retours, là où on inspecte la marchandise que les consommateurs mécontents ont retournée. Avec amour du travail, elles traitent les plaintes comme on ouvre des boîtes de bonbons. Sur leur convoyeur, elles découvrent toutes sortes d'objets dysfonctionnels et d'erreurs de fabrication. Peu à peu, la tentation de jouer avec la marchandise s’empare d'elles, mais les objets leur jouent plus d’un tour! \u003c/p\u003e\n\u003cp\u003eAvec un humour chaplinesque inspiré des bandes dessinées, les soeurs kif-kif posent un regard ludique sur le monde du travail et le transforment en un véritable jazz des bibelots qu’elles font valser sur un convoyeur d'usine, composant avec sa surface mobile pour manger, jongler ou cuisiner. À travers les produits non conformes et les erreurs de fabrication, elles trouvent un espace de liberté pour exprimer leur créativité, inventer et évoluer. \u003c/p\u003e\n\u003cp\u003e\u003cem\u003eDépartement des retours\u003c/em\u003e est une comédie cotonnée qui donne envie de siffler en travaillant, un moment de liberté où l'on finirait par s’attacher à une pantoufle à cap d’acier!\u003c/p\u003e\n\u003cp\u003eNous remercions le Conseil des arts du Canada, le Conseil des arts et lettres du Québec et le Conseil des arts de Montréal de leur soutien.\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"http://www.kif-kif.ca\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eSite Internet de la compagnie\u003c/a\u003e \u003ca href=\"https://www.laval.ca/calendrier-activites/theatre-departement-retours-ecc/\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eEspace citoyen des Confluents\u003c/a\u003e\u003c/p\u003e"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/les-soeurs-kif-kif/"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-1056"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_les-soeurs-kif-kif#Offer_0"}]},{"@id":"http://kg.footlight.io/resource/8f57c4c9-da91-4809-83ae-b5e8db471f39","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Cirque"}],"http://www.w3.org/2004/02/skos/core#closeMatch":[{"@id":"http://kg.artsdata.ca/resource/CircusPerformance"}]},{"@id":"https://co-motion.ca/spectacles/les-soeurs-kif-kif/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:13:39.393-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/les-soeurs-kif-kif/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_les-soeurs-kif-kif#Offer_0","http://schema.org/price":[{"@value":"12.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_katherine-levac-nouveau-spectacle","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Katherine-Levac_2024.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/katherine-levac-nouveau-spectacle/"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/katherine-levac-nouveau-spectacle/#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-04T19:30:00-05:00"}],"http://schema.org/name":[{"@language":"fr","@value":"L’Homme de ma vie"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/watch?v=_RtaeW-9Hfg"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cstrong\u003eKatherine Levac\u003c/strong\u003e, humoriste incontournable de sa génération remonte enfin sur les planches pour notre plus grand plaisir. Son premier spectacle Velours avait ravi unanimement public et critiques, sa captation de spectacle Grosse nous a offert une fenêtre sans filtre sur sa grossesse de jumeaux. Nous la retrouvons aujourd’hui fidèle à elle-même, vive, sarcastique, tranchante, portant un regard décalé sur notre réalité. Son humour pince-sans-rire et intelligent est une garantie de soirée réussie.\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_katherine-levac-nouveau-spectacle#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/katherine-levac-nouveau-spectacle/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:13:45.009-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/katherine-levac-nouveau-spectacle/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_katherine-levac-nouveau-spectacle#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"49.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_michel-fugain-nouveau-spectacle","@type":["http://schema.org/Event"],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-11T20:00:00-04:00"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Michel-Fugain.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Chanson francophone\", \"Spectacle en salle\"]"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/michel-fugain-nouveau-spectacle/#WebPage"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/name":[{"@language":"fr","@value":"Nouveau spectacle"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/michel-fugain-nouveau-spectacle/"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eDepuis plus de 50 ans, Michel Fugain nous fait voyager dans le monde de demain, celui du Big Bazar et celui de tous ses grands classiques d’hier et d’aujourd’hui. Il nous chante \u003cem\u003eLa vie, l’amour, etc\u003c/em\u003e… \u003cem\u003eAttention mesdames et messieurs\u003c/em\u003e, préparez-vous pour \u003cem\u003eUne belle histoire\u003c/em\u003e. \u003cem\u003eC’est la fête\u003c/em\u003e avec le retour de Michel Fugain au Québec!\u003c/p\u003e"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"},{"@id":"http://kg.artsdata.ca/resource/Chansonfrancophone"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_michel-fugain-nouveau-spectacle#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/michel-fugain-nouveau-spectacle/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:22:14.372-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/michel-fugain-nouveau-spectacle/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_michel-fugain-nouveau-spectacle#Offer_0","http://schema.org/price":[{"@value":"97.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_alex-roy-debout","@type":["http://schema.org/Event"],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/name":[{"@language":"fr","@value":"Debout"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-17T20:00:00-05:00"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Alex-Roy.png"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/alex-roy-debout/#WebPage"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/alex-roy-debout/"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cstrong\u003eAprès plus de 100 représentations et près de 25 000 billets vendus \u003c/strong\u003epour son premier one man show ‘’Des choses qui arrivent’’ Alex Roy, qui vous a fait hurler de rire durant les premières parties de Lise Dion, Les Grandes Crues et José Gaudet, nous replonge, dans son univers maladroit, surprenant et hilarant avec son tout nouveau spectacle \"Debout\" !\u003c/p\u003e\n\u003cp\u003e“Debout” parce que oui, c’est du stand-up, mais pas que ! Dans “Debout”, je vous raconte tout ce qui me pousse à sortir du lit chaque matin : mon métier, mes jumelles (qui n’ont aucune pitié pour la grasse matinée), et un quotidien rempli d’anecdotes aussi farfelues qu’inattendues. Un spectacle drôle, sincère et énergique… parce que rester debout, c’est déjà une victoire !\u003c/p\u003e\n\u003cp\u003eAlors, qu’est-ce qui me tient debout ? Venez le découvrir… et peut-être que vous repartirez avec des raisons en plus de vous lever demain ! \u003c/p\u003e\n\u003cp\u003eAlex Roy est un humoriste qui manie l’art du stand-up avec une énergie débordante et un sens aigu de l’autodérision. Avec son franc-parler et sa capacité à transformer les situations du quotidien en histoires hilarantes, il vous embarque dans un univers où chaque détail devient matière à rire. Que ce soit pour parler de sa famille, de ses expériences de vie ou de ses réflexions improbables, Alex Roy sait comment captiver son public en mêlant sincérité, absurdité et une bonne dose d’impertinence. Préparez-vous à rire… et à vous reconnaître (parfois malgré vous) dans ses anecdotes décapantes !\u003c/p\u003e"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_alex-roy-debout#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/alex-roy-debout/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-16T10:17:54.374-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/alex-roy-debout/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_alex-roy-debout#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"38.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_six-cirque","@type":["http://schema.org/Event"],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-04T15:00:00-05:00"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/MDA/Programmation-ville/Cirque/Six%C2%B0.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-77"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/8f57c4c9-da91-4809-83ae-b5e8db471f39"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/ju52VDrELhs?si=mQrcsAemt2J36b88"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/six-cirque/"}],"http://schema.org/name":[{"@language":"fr","@value":"Six°"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/keywords":[{"@value":"[\"Cirque\", \"Spectacle en salle\"]"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/six-cirque/#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003csub\u003eFLIP Fabrique \u003c/sub\u003e\u003c/p\u003e\n\u003ch3\u003eLe saviez-vous? Nous sommes à six degrés de séparation de tous nous connaître.\u003c/h3\u003e\n\u003cp\u003eCinq inconnus reçoivent une mystérieuse invitation qui les conduit un soir d’orage dans une vieille bâtisse abandonnée au cœur de la forêt. Ces visiteurs découvriront alors que cette maison possède une âme et une personnalité bien à elle. \u003c/p\u003e\n\u003cp\u003eDès que vous franchissez le seuil, vous avez cette étrange impression qu’elle vous connaît parfaitement. Elle vous parle (oui, oui!), vous guide et vous fait découvrir des facettes inconnues de votre personnalité. Avec elle et sa fameuse boîte de contrôle, le chemin peut être rocambolesque, mais il sera toujours festif, coloré et synonyme de prouesses acrobatiques époustouflantes. Joignez-vous au maître farceur et à ses amis pour cette épatante comédie acrobatique, où le banal devient extraordinaire et l’ingéniosité est toujours au rendez-vous! \u003c/p\u003e\n\u003cp\u003eIls ne sont que cinq sur scène… Et si la sixième personne, la connexion manquante, c’était vous ?\u003c/p\u003e\n\u003cp\u003eBasée à Québec, née de l’amitié et du rêve d’artistes de cirque en 2011, FLIP Fabrique est reconnue à travers le monde pour ses spectacles rassembleurs qui allient des performances époustouflantes à une authentique poésie.\u003c/p\u003e\n\u003cp\u003eSpectacle présenté en collaboration avec En Piste – Regroupement national des arts du cirque.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eDurée : 1 h 10 \u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e\u003cstrong\u003eProfitez de votre venue à la Maison des arts pour visiter les expositions en cours à la \u003ca href=\"https://www.laval.ca/culture/maison-des-arts/expositions-salle-alfred-pellan/expositions/\" target=\"_blank\" rel=\"noreferrer noopener\"\u003eSalle Alfred Pellan\u003c/a\u003e.\u003c/strong\u003e\u003cbr /\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"http://flipfabrique.com\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eSite Web de FLIP Fabrique\u003c/a\u003e \u003ca href=\"https://www.laval.ca/calendrier-activites/six-mda-cirque/\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eMaison des arts de Laval\u003c/a\u003e\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_six-cirque#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/six-cirque/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-02T10:14:20.365-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/six-cirque/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_six-cirque#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"30.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_les-grands-explorateurs-sri-lanka","@type":["http://schema.org/Event"],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-16T19:30:00-04:00"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Les-Grands-Explorateurs/2025-2026/LGE_2526_SriLanka_756x1100.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/les-grands-explorateurs-sri-lanka/#WebPage"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K10-427"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K10-427"}],"http://schema.org/keywords":[{"@value":"[\"Documentaire\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/5e5310d0-73e4-4421-8401-75c6c4aaf047"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/les-grands-explorateurs-sri-lanka/"}],"http://schema.org/name":[{"@language":"fr","@value":"Les Grands Explorateurs"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eC’est, depuis la constitution de la république, en 1972, que la traduction littérale la plus juste de « Sri Lanka » serait « île resplendissante ». À l’extrême sud-est de l’Inde, séparée par l’exigu détroit de Palk, elle ressemble à une boucle d’oreille accrochée au lobe indien, cette comparaison paraissant la plus juste du fait de la richesse en pierres précieuses de l’ancienne Ceylan.\u003cbr /\u003eCette île dont l’histoire culturelle est aussi importante que passionnante, se caractérise par son abondante végétation, la beauté de ses plages bordées de cocotiers et ses nombreuses ressources minérales et végétales.\u003cbr /\u003eSa situation d’insularité en a fait à la fois un refuge pour différents éléments de la culture indienne (le bouddhisme originel et les langues cinghalaise et tamoule préservées) et un pays ouvert sur le monde extérieur. Du fait de sa position géographique dans l’océan Indien, Sri Lanka a été pendant des siècles un lieu de passage incontournable entre l’occident et l’Extrême-Orient. Et donc l’un des pays les plus métissés de l’Asie.\u003cbr /\u003eC’est un pays paradoxal. Sa population est sociable et hospitalière mais l’île a une autre facette : la tentation d’un repli identitaire de certains groupes sociaux. La guerre civile qui s’est achevée en 2009 après vingt-six ans d’affrontements entre Cinghalais et Tamouls et les heurts entre\u003cbr /\u003ebouddhistes et musulmans au début du printemps 2018, crise politique et économique majeure à l'été 2022 montrent que l’histoire de Sri Lanka n’est pas un long fleuve tranquille.\u003c/p\u003e"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_les-grands-explorateurs-sri-lanka#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/les-grands-explorateurs-sri-lanka/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:11:22.090-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/les-grands-explorateurs-sri-lanka/"}]},{"@id":"http://kg.footlight.io/resource/5e5310d0-73e4-4421-8401-75c6c4aaf047","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Documentaire"}],"http://www.w3.org/2004/02/skos/core#broadMatch":[{"@id":"http://kg.artsdata.ca/resource/ScreeningEvent"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_les-grands-explorateurs-sri-lanka#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"23.60"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_emile-bilodeau-bill-au-iles","@type":["http://schema.org/Event"],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Chanson francophone\", \"Spectacle en salle\"]"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/%C3%89mile-Bilodeau.jpg"}],"http://schema.org/name":[{"@language":"fr","@value":"Bill au Îles"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/emile-bilodeau-bill-au-iles/"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/emile-bilodeau-bill-au-iles/#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"},{"@id":"http://kg.artsdata.ca/resource/Chansonfrancophone"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-14T20:00:00-04:00"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cstrong\u003eÉmile revient avec des nouvelles chansons enregistrées devant le public des îles-de-la-Madeleine\u003c/strong\u003e, Aux Pas Perdu lors de l'été 2024! Le résultat est unique: ''\u003cem\u003eBill aux Îles\u003c/em\u003e'', un album live, en formule homme-orchestre, où la simplicité des mélodies qui restent dans la tête rencontre la complexité technique nécessaire afin de maîtriser, en même temps, la percussion, l'enchaînement d'accords et les solos de musique à bouche. \u003c/p\u003e\n\u003cp\u003e \u003c/p\u003e\n\u003cp\u003e En plus des textes qui sont à l'image de l'auteur-compositeur-interprète de 29 ans, c'est-à-dire, assumés, colorés et divertissants, Bilodeau vous invite à découvrir l'univers de ce sixième album et à redécouvrir ses succès précédents dans un spectacle solo où l'ambiance visuelle sera à la hauteur de la création sonore. \u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_emile-bilodeau-bill-au-iles#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/emile-bilodeau-bill-au-iles/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:19:37.127-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/emile-bilodeau-bill-au-iles/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_emile-bilodeau-bill-au-iles#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"40.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_tarboosh-jedde-maallak","@type":["http://schema.org/Event"],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/name":[{"@language":"fr","@value":"Tarboosh Jedde Maallak"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Tarboosh-Jedde-Maallak.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-10T20:30:00-04:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/tarboosh-jedde-maallak/"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-213"}],"http://schema.org/keywords":[{"@value":"[\"Théâtre\", \"Spectacle en salle\"]"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/tarboosh-jedde-maallak/#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eLe Théâtre Marcellin-Champagnat ouvre ses portes à une production qui parlera au cœur de chaque Libanais : \u003cstrong\u003eTarboush Jeddeh Maallak\u003c/strong\u003e, une comédie romantique politico-sociale qui ravive notre mémoire collective, notre nostalgie et notre lien indéfectible avec cette terre, aussi loin que nous puissions aller. \u003c/p\u003e\n\n\n\n\n\u003cp\u003eCette pièce explore l’histoire universelle du peuple libanais : celle du départ, du retour, des promesses rompues, et d’un amour qui tente de survivre dans un pays sans cesse mis à l’épreuve par l’histoire. \u003c/p\u003e\n\n\n\u003cp\u003eSe déroulant entre 1980 et 2025, Tarboush Jeddeh Maallak raconte l’histoire de Hala, qui part, et d’Ibrahim, qui reste, deux amants déchirés par la guerre, l’exil et l’espoir. Tandis que Hala construit sa vie entre Paris, Montréal et Dubaï, elle emporte toujours le Liban avec elle. Ses retours sont toujours pleins d’espoir, toujours déçus. Ibrahim, lui, reste ancré dans un pays qui ne cesse de repousser les siens, mais auquel il continue de croire, envers et contre tout. \u003c/p\u003e\n\n\n\n\n\n\n\u003cp\u003eParce que le Liban est bien plus que la politique, c’est une mémoire, une identité, un amour.\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_tarboosh-jedde-maallak#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/tarboosh-jedde-maallak/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:11:46.936-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/tarboosh-jedde-maallak/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_tarboosh-jedde-maallak#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"66.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_bobby-bazini-seul-au-cinema","@type":["http://schema.org/Event"],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/bobby-bazini-seul-au-cinema/"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Bobby-Bazzini.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-17T19:30:00-04:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eAprès avoir lancé l’album Pearl en 2023 et le EP Stone Of June l’an dernier, Bobby Bazini fera paraître l’automne prochain un premier album francophone, \u003cem\u003eSeul au cinéma\u003c/em\u003e. \u003cem\u003eSeul au cinéma\u003c/em\u003e sortira à l'automne 2025 et Bobby n’a jamais été aussi fébrile à l’approche d’une sortie d’album. À ses yeux, c’est un peu comme s’il assistait à l’avant-première de son propre film, comme s’il se voyait jouer son propre rôle, dans cette dualité Bazinet/Bazini. Il se demande si le public le suivra dans cette nouvelle aventure.\u003c/p\u003e"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Seul au cinéma"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/bobby-bazini-seul-au-cinema/#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_bobby-bazini-seul-au-cinema#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/bobby-bazini-seul-au-cinema/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:22:51.264-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/bobby-bazini-seul-au-cinema/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_bobby-bazini-seul-au-cinema#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"50.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_le-crime-de-lorient-express","@type":["http://schema.org/Event"],"http://schema.org/keywords":[{"@value":"[\"Théâtre\", \"Spectacle en salle\"]"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Crime-de-lorient.jpg"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-19T19:30:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/le-crime-de-lorient-express/#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cstrong\u003ePrésentée pour la première fois au Québec\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003ePlongez dans l’univers fascinant de la Reine du crime, Agatha Christie, avec l’une des enquêtes les plus emblématiques du légendaire Hercule Poirot !\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eEntre suspense haletant et touches d’humour, \u003cem\u003eLe Crime de l’Orient-Express\u003c/em\u003e vous embarque dans un voyage à grande vitesse où chaque passager cache un secret. Préparez-vous à une représentation captivante, où chaque regard, chaque détail, vous rapproche un peu plus de la vérité. Vivez l’intensité d’un huis clos palpitant, où le mystère s’épaissit à chaque instant. Entre rebondissements et révélations inattendues, ce chef-d’œuvre incontournable d’Agatha Christie vous tiendra en haleine jusqu’au dénouement final.\u003c/p\u003e\n\u003cp\u003eÀ bord du mythique Orient-Express, reliant Istanbul à Calais, un meurtre inexplicable est commis, suivi d’une tentative d’assassinat. Face à cette énigme complexe, Hercule Poirot met à l’épreuve ses célèbres « petites cellules grises » pour démêler le vrai du faux. Le public est invité à se glisser dans la peau d’un détective, collectant indices et fausses pistes tout au long du spectacle. \u003c/p\u003e"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-213"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/name":[{"@language":"fr","@value":"Le Crime de l'Orient Express"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/le-crime-de-lorient-express/"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_le-crime-de-lorient-express#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/le-crime-de-lorient-express/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:14:03.254-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/le-crime-de-lorient-express/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_le-crime-de-lorient-express#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"52.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_mike-beaudoin-acharne","@type":["http://schema.org/Event"],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Mike-Beaudoin.jpg"}],"http://schema.org/name":[{"@language":"fr","@value":"Acharné"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-06T20:00:00-05:00"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cstrong\u003eMike Beaudoin\u003c/strong\u003e présente son très attendu deuxième one-man show \u003cstrong\u003eAcharné\u003c/strong\u003e. Le solide stand-up, qui n’arrête jamais, continue de puiser dans sa vie - pas plate du tout- et se livre sans compromis...avec des détails, beaucoup de détails croustillants et hilarants. \u003c/p\u003e\n\n\u003cp\u003eAprès le succès de sa première tournée VRAI, ses dizaines de milliers d’abonnés qui le suivent assidûment sur les réseaux sociaux et ses passages toujours remarqués à la radio commerciale à heure de grande écoute, Mike séduit de nouveau son public avec un spectacle qui est un mélange explosif d’humour et de vérité, tout en gardant son mordant et son franc parlé qui en font son unicité. \u003c/p\u003e\n\n\u003cp\u003eSoyez prévenu, vous aurez droit à des péripéties hautes en couleur comme la vie avec son ado qui se confie beaucoup trop, sa peur des psys, son impatience qui teinte plusieurs sphères de sa vie…pour ne nommer que celles-là! \u003c/p\u003e\n\u003cp\u003eAcharné parce qu’il trouve toujours le moyen de surmonter les défis de la vie, tout en nous faisant hurler de rire…lui qui n’est pas reconnu pour avoir la langue dans sa poche! \u003c/p\u003e"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/mike-beaudoin-acharne/#WebPage"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/mike-beaudoin-acharne/"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_mike-beaudoin-acharne#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/mike-beaudoin-acharne/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:11:34.815-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/mike-beaudoin-acharne/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_mike-beaudoin-acharne#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"41.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_patrice-lecuyer","@type":["http://schema.org/Event"],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/34aeabb9-c7cf-46a2-8f57-a5ae9420eb1d"}],"http://schema.org/name":[{"@language":"fr","@value":"Après seulement 32 ans d’absence sur scène"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Patrice-LEcuyer.jpg"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/patrice-lecuyer/#WebPage"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-30T20:00:00-04:00"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/patrice-lecuyer/"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003ePatrice L’Ecuyer présente son tout premier one-man-show intitulé \u003cem\u003eAprès seulement 32 ans d’absence sur scène\u003c/em\u003e. L’humour a toujours occupé une grande place dans sa carrière, que ce soit dans les \u003cem\u003eBye \u003c/em\u003eBye (comédien dans quatorze éditions!), \u003cem\u003eL’\u003c/em\u003eEcuyer, \u003cem\u003ePrière de ne pas envoyer de fleurs\u003c/em\u003e, les galas Juste pour Rire et ComediHa! ou encore le spectacle \u003cem\u003eMerci beaucoup\u003c/em\u003e. Mais aujourd’hui, l’animateur chéri depuis trois décennies s’attaque seul à la scène pour la toute première fois! \u003c/p\u003e\n\u003cp\u003eAu fil de sa carrière, Patrice a toujours été discret sur sa vie, même s’il a vécu des situations hors du commun tant avec ses proches qu’avec de grandes personnalités… mais sur scène, il va tout dévoiler! Flash-back sur sa vie, sa carrière, ses rencontres : à travers un impressionnant flot d’anecdotes remplies d’autodérision, Patrice déploie son immense talent comique. Ancienne star de la LNI (et l’un des joueurs les plus punis!), il a maintenant toute la liberté qu’il faut pour faire rire, surprendre et se mettre à nu sans se sentir coupable. Son talent de conteur, son charisme et ses propos surprenants feront de son spectacle un incontournable de la scène humoristique québécoise. \u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_patrice-lecuyer#Offer_0"}]},{"@id":"http://kg.footlight.io/resource/34aeabb9-c7cf-46a2-8f57-a5ae9420eb1d","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Patrice L'Écuyer"}],"http://schema.org/sameAs":[{"@value":"http://www.wikidata.org/entity/Q3368967"}],"http://schema.org/hasOccupation":[{"@language":"fr","@value":"Animateur de télévision et de radio et acteur québécois"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humouriste"}]},{"@id":"https://co-motion.ca/spectacles/patrice-lecuyer/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:17:23.415-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/patrice-lecuyer/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_patrice-lecuyer#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"54.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_vincent-vallieres-au-travers-de-la-route","@type":["http://schema.org/Event"],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/vincent-vallieres-au-travers-de-la-route/#WebPage"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Vincent-Valliere.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/name":[{"@language":"fr","@value":"Au travers de la route"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Chanson francophone\", \"Spectacle en salle\"]"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e \u003cstrong\u003eVincent Vallières\u003c/strong\u003e revient sur scène entouré de ses musiciens pour un tout nouveau spectacle, où son imposant répertoire s'unit aux pièces de son nouvel album à paraître à l’automne 2025. Un spectacle à l’image de cet incontournable de la chanson québécoise: rassembleur, vivant et authentique. \u003c/p\u003e"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"},{"@id":"http://kg.artsdata.ca/resource/Chansonfrancophone"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/vincent-vallieres-au-travers-de-la-route/"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-12-04T20:00:00-05:00"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_vincent-vallieres-au-travers-de-la-route#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/vincent-vallieres-au-travers-de-la-route/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:19:26.396-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/vincent-vallieres-au-travers-de-la-route/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_vincent-vallieres-au-travers-de-la-route#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"50.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_simon-leblanc-present","@type":["http://schema.org/Event"],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/SimonLeblanc_Present_1080x1920.jpg"}],"http://schema.org/name":[{"@language":"fr","@value":"Présent"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-19T19:30:00-05:00"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/simon-leblanc-present/"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cstrong\u003eSimon Leblanc\u003c/strong\u003e a plus envie que jamais de sillonner le Québec en quête de paysages et de faces qui rient. Simon repart donc en tournée pour présenter son quatrième spectacle, \u003cem\u003e\u003cstrong\u003ePrésent\u003c/strong\u003e\u003c/em\u003e. Il a hâte de vous voir comme ça s'peut pas!\u003c/p\u003e\n\u003cp\u003eÀ bientôt tout le monde...\u003csub\u003e\u003cbr /\u003e\u003c/sub\u003e\u003c/p\u003e\n\u003cp\u003e\u003csub\u003e\u003cstrong\u003e16 ans et plus\u003c/strong\u003e\u003c/sub\u003e\u003c/p\u003e"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/simon-leblanc-present/#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_simon-leblanc-present#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/simon-leblanc-present/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:18:31.858-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/simon-leblanc-present/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_simon-leblanc-present#Offer_0","http://schema.org/price":[{"@value":"44.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_cafe-concerts-echos-damour","@type":["http://schema.org/Event"],"http://schema.org/keywords":[{"@value":"[\"Musique classique\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/f56c4b49-240a-447d-8212-5f0c6333e42a"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/MDA/Dej-croissant/Jacqueline_Julien_15-f%C3%A9v-2026.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-15T11:00:00-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/cafe-concerts-echos-damour/"}],"http://schema.org/name":[{"@language":"fr","@value":"Café-Concerts"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/cafe-concerts-echos-damour/#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003ch5\u003eJacqueline Woodley (soprano) - Julien Patenaude (baryton) - Mariane Patenaude (pianiste)\u003c/h5\u003e\n\u003cp\u003eVariations sur un thème connu aux résonnances de Mozart et Gounod à Piaf et Brel.\u003c/p\u003e"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_cafe-concerts-echos-damour#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/cafe-concerts-echos-damour/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:15:44.168-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/cafe-concerts-echos-damour/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_cafe-concerts-echos-damour#Offer_0","http://schema.org/price":[{"@value":"33.35"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_fondation-des-enfants-de-lecole-jean-piaget-2026","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/fondation-des-enfants-de-lecole-jean-piaget-2026/"}],"http://schema.org/name":[{"@language":"fr","@value":"Fondation des enfants de l'école Jean-Piaget 2026"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Locations-TMC/Visuel_Fondation-Jean-Piaget.jpg"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-213"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-06-04T19:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/fondation-des-enfants-de-lecole-jean-piaget-2026/#WebPage"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003ePour cette 11e édition du Mini Gala d'humour, nous vous proposons une toute nouvelle salle. Celle-ci nous permet maintenant d'accueillir 749 personnes. Pour l'occasion, notre porte-parole, Guillaume Pineault animera à nouveau une soirée hilarante avec l'aide de ses complices : Pierre Hébert, Neev, Mégan Brouillard et Arnaud Soly\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_fondation-des-enfants-de-lecole-jean-piaget-2026#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/fondation-des-enfants-de-lecole-jean-piaget-2026/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:13:15.328-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/fondation-des-enfants-de-lecole-jean-piaget-2026/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_fondation-des-enfants-de-lecole-jean-piaget-2026#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"57.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_lama-daventures-en-aventures","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/lama-daventures-en-aventures/"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Lama.png"}],"http://schema.org/name":[{"@language":"fr","@value":"Lama, D'aventures en aventures"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-11T19:30:00-05:00"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/lama-daventures-en-aventures/#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/keywords":[{"@value":"[\"Théâtre\", \"Théâtre musical\", \"Spectacle en salle\"]"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/PRuQpm92ysI"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"},{"@id":"http://kg.footlight.io/resource/a4561a6b-a551-4243-879c-659e0d0d5a81"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eL’icône française de la chanson, \u003cstrong\u003eSerge Lama (Je suis malade, d’Aventures en aventures, Les Ballons rouges)\u003c/strong\u003e, devait faire une dernière tournée. Il n’a pas pu. Pourtant, il a promis qu’il n’avait pas dit son dernier mot. \u003c/p\u003e\n\u003cp\u003e C’est ainsi que le chanteur-paroliers a approuvé le spectacle biographique musical \u003cstrong\u003eSERGE LAMA – D’Aventures en aventures\u003c/strong\u003e et collabore activement à cette proposition de Mélissa Cardona (Amsterdam-Jacques Brel remonte sur scène, Lili St-Cyr).\u003c/p\u003e\n\u003cp\u003e Sur scène, les comédiens incarnent Serge Lama et les hommes et les femmes ayant façonné sa vie. Le chanteur le dit lui-même, il est la somme de ces rencontres. Avec eux, il a contourné les pièges que lui tendait le destin. Grâce à eux, il a pu aimer, rire, chanter, composer, parler, comprendre, échanger et finalement proclamer : « Je t’aime à la folie, la vie ». \u003c/p\u003e\n\u003cp\u003e L’action, ficelée autour de 22 chansons du répertoire de Serge Lama, permettra une incursion dans la vie d’un homme tenace qui a toujours su avancer. \u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e Équipe de création : \u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eChansons: Serge Lama | Texte du livret : Melissa Cardona \u003cbr /\u003eMise en scène : Charles Dauphinais \u003cbr /\u003eChorégraphies : Alex Francoeur \u003cbr /\u003eQuatre comédien.nes Érick Paulhus - Stéphan Côté - Élisabeth Duperré - Gaële un musicien-comédien et deux musicien.nes.\u003c/strong\u003e\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_lama-daventures-en-aventures#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/lama-daventures-en-aventures/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:12:57.972-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/lama-daventures-en-aventures/"}]},{"@id":"http://kg.footlight.io/resource/a4561a6b-a551-4243-879c-659e0d0d5a81","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Théâtre musical"}],"http://www.w3.org/2004/02/skos/core#closeMatch":[{"@id":"http://kg.artsdata.ca/resource/MusicalTheatrePerformance"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_lama-daventures-en-aventures#Offer_0","http://schema.org/price":[{"@value":"68.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_benevolat","@type":["http://schema.org/Event"],"http://schema.org/keywords":[{"@value":"[\"Théâtre\", \"Spectacle en salle\"]"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/MDA/Programmation-ville/Th%C3%A9atre/2025-2026/B%C3%A9n%C3%A9volat.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003csup\u003eTableau Noir\u003c/sup\u003e\u003c/p\u003e\n\u003ch3\u003eEntre deux règles grammaticales, leurs univers devront se rencontrer.\u003c/h3\u003e\n\u003cp\u003eToutes les semaines, Amaryllis rend visite à Anthony, détenu pour meurtre dans un pénitencier. L’étudiante universitaire en sciences, rigoureuse et pragmatique, offre bénévolement des ateliers de français au jeune prisonnier plutôt loquace et impulsif. Tout sépare le duo de protagonistes, et pourtant ils devront se découvrir et s’apprivoiser.\u003c/p\u003e\n\u003cp\u003eMagnifiquement interprété, \u003cem\u003eBénévolat \u003c/em\u003eest un huis clos sensible et haletant, où les mots agissent comme moteur de l’intrigue, mais également comme catalyseur – provoquant la rencontre improbable et attachante de deux êtres fragilisés par des parcours aux antipodes. Le texte parfois tendu, parfois ludique, met en relief les richesses pouvant jaillir au contact de deux mondes opposés et aborde avec finesse les questions d’inégalités sociales, de justice, d’éducation et de relations hommes-femmes.\u003c/p\u003e\n\u003cp\u003eFondée en 2015, \u003cstrong\u003eTableau Noir\u003c/strong\u003e mise sur l’importance du jeu de l’acteur comme aspect primant sur la forme et sur la production théâtrale francophone d’œuvres contemporaines.\u003c/p\u003e\n\u003cp\u003eAprès une mise en lecture au Festival du Jamais Lu de Montréal (2022), \u003cem\u003eBénévolat\u003c/em\u003e – lauréat du prix Gratien-Gélinas (2022) – est monté pour la toute première fois, dans une production de Tableau Noir. Il s’agit de la deuxième pièce de l’autrice Maud de Palma-Duquet après \u003cem\u003eEnfant unique\u003c/em\u003e. \u003c/p\u003e\n\u003cp\u003eSpectacle présenté en codiffusion avec la \u003cstrong\u003eRencontre Théâtre Ados\u003c/strong\u003e et en collaboration avec \u003cstrong\u003eLes Voyagements – Théâtre de création en tournée. \u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eDurée : 1 h 20\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"http://tableau-noir.ca\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eSite Web de Tableau Noir\u003c/a\u003e \u003ca href=\"https://www.laval.ca/calendrier-activites/benevolat-mda-theatre/\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eMaison des arts de Laval\u003c/a\u003e\u003cstrong\u003e\u003cbr /\u003e\u003c/strong\u003e\u003c/p\u003e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-14T19:30:00-04:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-77"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/benevolat/"}],"http://schema.org/name":[{"@language":"fr","@value":"Bénévolat"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/benevolat/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/930284222?share=copy"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_benevolat#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/benevolat/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:17:08.866-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/benevolat/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_benevolat#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"40.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_ariane-moffatt-airs-de-jeux","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Ariane-Moffatt-2.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Spectacle en salle\"]"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eAriane Moffatt est de retour avec une irrésistible envie de jouer! Son nouveau spectacle s’annonce festif et enivrant. Une aventure synth pop guidée par le beat! \u003c/p\u003e\n\n\u003cp\u003eLe quatuor sur scène oscillera entre les grands succès d’Ariane devenus incontournables et ses nouvelles chansons aux textes universels et d’une pertinence\u003cbr /\u003edésarmante. Ses rythmes contagieux prendront vos corps d'assaut. Allez, l'heure est venue de combattre le \u003cem\u003efeu par le jeu \u003c/em\u003eet de danser à cœur ouvert, les yeux fermés. \u003c/p\u003e"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/ariane-moffatt-airs-de-jeux/"}],"http://schema.org/name":[{"@language":"fr","@value":"Airs de jeux"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-25T19:30:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/ariane-moffatt-airs-de-jeux/#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_ariane-moffatt-airs-de-jeux#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/ariane-moffatt-airs-de-jeux/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:20:20.226-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/ariane-moffatt-airs-de-jeux/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_ariane-moffatt-airs-de-jeux#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"45.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_petits-bonheurs-gzaagiin-malenki","http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/MDA/Programmation-ville/Jeune-Public/2025-2026/Gzaagiin-male%C5%84ki-%E2%80%93-Je-te-promets-une-for%C3%AAt.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/petits-bonheurs-gzaagiin-malenki/#WebPage"}],"http://schema.org/name":[{"@language":"fr","@value":"Petits Bonheurs"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-77"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-03T09:30:00-04:00"}],"http://schema.org/keywords":[{"@value":"[\"Jeune public\", \"Performance sonore\", \"Théâtre immersif\", \"Spectacle en salle\"]"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/732510331?share=copy"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003csub\u003eVoyageurs Immobiles, Cie de création\u003c/sub\u003e \u003c/p\u003e\u003ch3\u003eEst-ce que les traces ou les empreintes peuvent-être invisibles? \u003c/h3\u003e\n\u003cp\u003eImagine un spectacle, mais en forme de forêt, un rassemblement parents-enfants sous une tranquille canopée. Tu fais partie du décor, tu peux t’asseoir et laisser résonner partout dans ton corps les voix chaudes, le battement du tambour, l’incroyable palette de sons du violoncelle, l’eau qui dégèle et ruisselle dans la forêt...\u003c/p\u003e\n\u003cp\u003eUne violoncelliste abitibienne, une comédienne et musicienne d’origine française et anishinaabeg et une metteure en scène émigrée de Pologne vous invitent à une expérience envoûtante, une rencontre tissée de sons, de textures, de mouvement et de poésie, une forêt dont on ressort un peu plus heureux et vivant!\u003c/p\u003e\n\u003cp\u003eVoyageurs Immobiles, Cie de création (Gatineau, 2009) rassemble des artistes de diverses cultures et transgresse les frontières entre les pays et les disciplines artistiques pour provoquer des rencontres, interroger notre présent et engendrer le dialogue. \u003c/p\u003e\n\u003cp\u003eÀ l’image du spectateur qui parcourt le monde grâce à l’imaginaire, \u003cstrong\u003eVoyageurs Immobiles\u003c/strong\u003e s’est bâtie autour de cet étonnant paradoxe : déplacement et immobilité. La compagnie de création rassemble des artistes de diverses cultures et transgresse les frontières entre les pays et les disciplines artistiques pour provoquer des rencontres, interroger notre présent et engendrer le dialogue. En constante réflexion sur la place de l’art dans notre société ainsi que sur la responsabilité de l’artiste, les créations de la compagnie éveillent l’intelligence, l’imagination et le ressenti du spectateur, quel que soit son âge.\u003c/p\u003e\n\u003cp\u003e\u003cem\u003e\u003cstrong\u003ePublic sur la scène, places limitées\u003c/strong\u003e\u003c/em\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eDurée : 30 minutes\u003cbr /\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"http://voyageursimmobiles.ca\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eSite Web de Voyageurs Immobiles\u003c/a\u003e \u003cbr /\u003e\u003c/p\u003e"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/22f50ba1-3403-48a8-a279-460952d3c134"},{"@id":"http://kg.footlight.io/resource/78628b68-7b34-4987-a94b-1d5b262e0bfd"},{"@id":"http://kg.footlight.io/resource/ba61e59b-848c-41e8-928e-d302c7ed598a"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/petits-bonheurs-gzaagiin-malenki/"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_petits-bonheurs-gzaagiin-malenki#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-03T11:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_petits-bonheurs-gzaagiin-malenki#2026-05-03T093000-0400"},{"@id":"http://kg.footlight.io/resource/co-motion-ca_petits-bonheurs-gzaagiin-malenki#2026-05-03T110000-0400"}]},{"@id":"https://co-motion.ca/spectacles/petits-bonheurs-gzaagiin-malenki/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:20:25.517-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/petits-bonheurs-gzaagiin-malenki/"}]},{"@id":"http://kg.footlight.io/resource/78628b68-7b34-4987-a94b-1d5b262e0bfd","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Performance sonore"}]},{"@id":"http://kg.footlight.io/resource/ba61e59b-848c-41e8-928e-d302c7ed598a","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Théâtre immersif"}],"http://www.w3.org/2004/02/skos/core#broadMatch":[{"@id":"http://kg.artsdata.ca/resource/TheatrePerformance"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_petits-bonheurs-gzaagiin-malenki#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"17.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_petits-bonheurs-gzaagiin-malenki#2026-05-03T093000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Petits Bonheurs"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-03T09:30:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_petits-bonheurs-gzaagiin-malenki#2026-05-03T110000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Petits Bonheurs"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-03T11:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_jean-francois-mercier-nouveau-spectacle","@type":["http://schema.org/Event"],"http://schema.org/description":[{"@language":"fr","@value":"\u003ch3\u003eJean-François Mercier est de retour sur scène pour présenter son quatrième spectacle en carrière!\u003c/h3\u003e\n\n\u003cp\u003eAvec son regard critique et pertinent, son ton souvent corrosif et parfois émouvant, il ramène son humour singulier sur les planches avec un seul objectif : brasser une fois de plus la cage de l'humour en confrontant nos convictions pour nous faire rire de nos travers, mais aussi des siens. Auteur de grand talent, animateur et comédien, c'est sur scène, nourri par les réactions du public, qu'il trouve sa voix d’humoriste pour nous faire rire et réfléchir.\u003c/p\u003e"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Jean-Fran%C3%A7ois-Mercier.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-06-18T19:30:00-04:00"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/jean-francois-mercier-nouveau-spectacle/#WebPage"}],"http://schema.org/name":[{"@language":"fr","@value":"Avis d'ébullition"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/jean-francois-mercier-nouveau-spectacle/"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_jean-francois-mercier-nouveau-spectacle#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/jean-francois-mercier-nouveau-spectacle/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:21:38.579-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/jean-francois-mercier-nouveau-spectacle/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_jean-francois-mercier-nouveau-spectacle#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"47.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_bboyizm-in-my-body","@type":["http://schema.org/Event"],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/tQmMXtWd0aQ"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/MDA/Programmation-ville/Danse/2025-2026/In-My-Body.jpg"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/bboyizm-in-my-body/"}],"http://schema.org/name":[{"@language":"fr","@value":"In My Body"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-24T19:30:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/bboyizm-in-my-body/#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"[\"Danse\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/2fa05002-967b-43c2-b5cb-fc93a25edcb1"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003csub\u003eCrazy Smooth \u003cbr /\u003e\u003c/sub\u003e\u003c/p\u003e\n\u003cblockquote\u003e''On dit que la chose la plus courageuse qu’un danseur puisse faire est de vieillir. Pour un b-boy ou une b-girl, ce parcours frôle l’héroïsme.''\u003c/blockquote\u003e\n\u003cp\u003e\u003cem\u003eIn My Body\u003c/em\u003e explore la confiance et l’invincibilité des danseurs sur scène, en contraste avec leur vulnérabilité face au passage du temps et au vieillissement de leur principal outil d’expression : le corps. Dans cette création ambitieuse, \u003cstrong\u003eCrazy Smooth\u003c/strong\u003e réunit un groupe de danseurs intergénérationnels et une équipe de création internationale pour une réflexion athlétique et poignante sur l’évolution personnelle des danseurs de\u003cem\u003e street dance\u003c/em\u003e. \u003cem\u003eIn My Body\u003c/em\u003e est dédié à la mémoire de Frosty Freeze, Marjory Smarth, Flash, Don Campbellock et d’autres figures pionnières du \u003cem\u003estreet dance\u003c/em\u003e. Leur héritage vit en nous.\u003c/p\u003e\n\u003cp\u003eDepuis ses débuts en 1997, Crazy Smooth est une figure incontournable de la danse urbaine au Canada. Lauréat du Prix Clifford E. Lee 2020 du Centre des arts de Banff, il est fondateur et directeur artistique de Bboyizm, compagnie de danse reconnue pour sa préservation et son développement de la culture urbaine.\u003c/p\u003e\n\u003cp\u003e\u003cem\u003eIn My Body\u003c/em\u003e est dédié à la mémoire de Frosty Freeze, Marjory Smarth, Flash, Don Campbellock et d’autres figures pionnières du\u003cem\u003e street dance.\u003c/em\u003e Leur héritage vit en nous.\u003c/p\u003e\n\u003cp\u003eDepuis ses débuts en 1997, Crazy Smooth (Yvon Soglo) est une figure incontournable de la danse urbaine au Canada. Lauréat du Prix Clifford E. Lee 2020 du Centre des arts de Banff, il est fondateur et directeur artistique de Bboyizm, compagnie de danse reconnue pour sa préservation et son développement de la culture urbaine. Crazy Smooth a présenté plusieurs créations majeures, dont \u003cem\u003eL’évolution du b-boying\u003c/em\u003e, \u003cem\u003eIZM\u003c/em\u003e, et \u003cem\u003eMusic Creates Opportunity\u003c/em\u003e, largement acclamées au Canada. En tant que danseur, chorégraphe, juge et mentor, il continue d’être un ambassadeur et un modèle dans la communauté du \u003cem\u003estreet dance.\u003c/em\u003e\u003c/p\u003e\n\u003cp\u003ePremière mondiale le 4 mars 2022, University of Alberta, Timms Centre for the Arts (Brian Webb Dance Company).\u003c/p\u003e\n\u003cp\u003eCe spectacle est présenté avec la collaboration de \u003cstrong\u003eLa danse sur les routes du Québec\u003c/strong\u003e.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e\u003cbr /\u003eDistinctions\u003c/strong\u003e\u003c/p\u003e\n\u003cul\u003e\u003cli\u003e2022 Lauréat de 4 Prix Dora Mavor Moore\u003c/li\u003e\u003cli\u003e2023 : Crazy Smooth remporte le prix « Danseur.euse ou troupe de danse » décerné par le Gala Dynastie\u003c/li\u003e\u003c/ul\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_bboyizm-in-my-body#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/bboyizm-in-my-body/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:21:48.972-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/bboyizm-in-my-body/"}]},{"@id":"http://kg.footlight.io/resource/2fa05002-967b-43c2-b5cb-fc93a25edcb1","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Danse"}],"http://www.w3.org/2004/02/skos/core#closeMatch":[{"@id":"http://kg.artsdata.ca/resource/DancePerformance"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_bboyizm-in-my-body#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"40.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_les-trois-accords-toujours-les-vacances","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/les-trois-accords-toujours-les-vacances/"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Les-Trois-Accords.jpg"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e \u003cem\u003e\u003csup\u003e\u003cstrong\u003eSpectacle festif : les gens seront debout dès la première chanson! Choisissez les premières rangées si vous aimez vous lever, chanter et danser, sinon optez pour un siège plus reculé.\u003c/strong\u003e\u003c/sup\u003e\u003c/em\u003e\u003cbr /\u003e\u003cbr /\u003eDe retour de l’un de leurs nombreux voyages initiatiques, \u003cstrong\u003eLES TROIS ACCORDS\u003c/strong\u003e prennent une pause de leurs aventures le temps d’une nouvelle série de spectacles à travers le Québec. Désormais marqués d’une sagesse et de coups de soleil profonds, ces grands explorateurs nous racontent leurs récits de voyage, des faits vécus hautement rythmés, des expéditions musicales qui nous mènent tantôt sur des sentiers inconnus, tantôt vers des rencontres étranges… On attend impatiemment la sortie du nouvel album le 24 octobre prochain!\u003c/p\u003e\n\u003cp\u003e1$ par billet vendu sera remis à la Fondation Cowboys Fringants.\u003cbr /\u003eMerci \u0026#x1f49a;\u003c/p\u003e"}],"http://schema.org/name":[{"@language":"fr","@value":"Toujours les vacances"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-27T20:00:00-05:00"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Spectacle en salle\"]"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/les-trois-accords-toujours-les-vacances/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_les-trois-accords-toujours-les-vacances#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/les-trois-accords-toujours-les-vacances/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:23:02.474-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/les-trois-accords-toujours-les-vacances/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_les-trois-accords-toujours-les-vacances#Offer_0","http://schema.org/price":[{"@value":"54.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_wooden-shapes-du-classique-au-rock","@type":["http://schema.org/Event"],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-25T15:00:00-05:00"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Wooden-shapes.jpg"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Spectacle en salle\"]"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/wooden-shapes-du-classique-au-rock/#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/wooden-shapes-du-classique-au-rock/"}],"http://schema.org/name":[{"@language":"fr","@value":"Du classique au rock!"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cstrong\u003eWooden Shapes\u003c/strong\u003e vous fait redécouvrir les groupes rock emblématiques qui ont marqué des générations. Ensemble, ils repoussent les limites de leurs instruments ; violons, altos, violoncelle et contrebasse, pour réaliser des interprétations encore jamais entendues des musiques de Queen, Supertramp, Iron Maiden, Metallica et plus encore! \u003c/p\u003e\n\u003cp\u003e« Un sextuor à cordes complètement déjanté, qui s’est amusé à reprendre Pink Floyd et Queen dans un spectacle instrumental rock plus vrai que nature » - L’est Républicain \u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_wooden-shapes-du-classique-au-rock#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/wooden-shapes-du-classique-au-rock/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-24T10:13:52.262-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/wooden-shapes-du-classique-au-rock/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_wooden-shapes-du-classique-au-rock#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"48.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_lupii-lui-calancea-surorile-osoianu","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/lupii-lui-calancea-surorile-osoianu/"}],"http://schema.org/name":[{"@language":"fr","@value":"Lupii lui Calancea \u0026 Surorile Osoianu"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Locations-TMC/Danse-moldaves.jpg"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/2fa05002-967b-43c2-b5cb-fc93a25edcb1"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-213"}],"http://schema.org/keywords":[{"@value":"[\"Danse\", \"Spectacle en salle\"]"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-08T18:00:00-04:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/lupii-lui-calancea-surorile-osoianu/#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003ch4\u003eLupii lui Calancea \u0026amp; Surorile Osoianu ensemble au Canada — un spectacle exceptionnel\u003c/h4\u003e\n\n\u003cp\u003eLupii lui Calancea et Surorile Osoianu arrivent à Laval avec un spectacle intense, dédié à toutes celles et tous ceux qui aiment le folklore authentique, réinterprété dans une formule contemporaine.\u003c/p\u003e\n\n\u003cp\u003eCette collaboration est devenue un véritable phénomène : l’énergie et la puissance des Lupii se mêlent aux harmonies rituelles et uniques des Surorile Osoianu.\u003c/p\u003e\n\n\u003cp\u003eSur scène, le public vivra une expérience complète : rythmes entraînants, orchestre en direct, voix polyphoniques et moments qui transforment la tradition en un spectacle mémorable. Un rendez-vous idéal pour celles et ceux qui souhaitent retrouver leurs racines, ressentir l’esprit roumain et vivre une soirée de musique et d’émotions.\u003c/p\u003e\n\n\u003cp\u003eNe manquez pas l’occasion d’assister à ce concert unique, qui réunit les générations et nous ramène plus près que jamais de notre terre natale, le pays de nos racines.\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_lupii-lui-calancea-surorile-osoianu#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/lupii-lui-calancea-surorile-osoianu/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:23:27.927-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/lupii-lui-calancea-surorile-osoianu/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_lupii-lui-calancea-surorile-osoianu#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"95.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_jean-michel-elie-et-eddy-king","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Jean-Michel Elie et Eddy King"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Eddy-King-2.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003ch3\u003e\u003cstrong\u003eJean-Michel Elie et Eddy King : Deux univers, une soirée, 90 minutes de rires garantis!\u003c/strong\u003e\u003c/h3\u003e\n\u003cp\u003ePlongez dans une soirée où l’humour est roi, avec deux humoristes de grand talent qui vous offriront chacun 45 minutes de pur plaisir!\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eJean-Michel Elie\u003c/strong\u003e, ancien joueur de football universitaire, a trouvé sa véritable passion après un événement marquant qui a changé sa vie. Depuis 2015, il brille sur les scènes québécoises, des bars aux festivals comme Zoofest et Juste pour Rire. En 2021, son passage remarqué au Gala de Rachid Badouri à Radio-Canada l’a confirmé comme un incontournable de la relève humoristique. Son style unique et sa personnalité authentique sauront vous séduire.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eEddy King\u003c/strong\u003e, présent sur la scène depuis 2007, est une figure emblématique de l’humour québécois et international. Avec des performances dans des festivals en Europe et en Afrique, il cumule les succès dans les galas et à la télévision. Reconnu pour son style calme et posé, il manie des punchs percutants qui font mouche à chaque fois. Porté par ses convictions et sa droiture, Eddy King est une valeur sûre pour une soirée mémorable.\u003c/p\u003e\n\u003cp\u003eDeux performances distinctes pour une seule soirée où le rire et le talent sont au rendez-vous! \u003c/p\u003e"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-510"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/jean-michel-elie-et-eddy-king/"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-07T20:00:00-05:00"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/jean-michel-elie-et-eddy-king/#WebPage"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_jean-michel-elie-et-eddy-king#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/jean-michel-elie-et-eddy-king/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:23:34.706-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/jean-michel-elie-et-eddy-king/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_jean-michel-elie-et-eddy-king#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"34.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_troupe-de-danse-moldave-vatra-10-ans-de-tradition-et-passion","@type":["http://schema.org/Event"],"http://schema.org/keywords":[{"@value":"[\"Danse\", \"Spectacle en salle\"]"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/troupe-de-danse-moldave-vatra-10-ans-de-tradition-et-passion/"}],"http://schema.org/name":[{"@language":"fr","@value":"10 ans de tradition et passion"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Locations-TMC/Danes-moldaves.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-21T18:00:00-04:00"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-213"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/2fa05002-967b-43c2-b5cb-fc93a25edcb1"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/troupe-de-danse-moldave-vatra-10-ans-de-tradition-et-passion/#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003ch4\u003e\u003cstrong\u003eVatra fête 10 ans de danse, de passion et de traditions!\u003c/strong\u003e \u003c/h4\u003e\n\u003cp\u003eDix ans déjà que la \u003cstrong\u003etroupe de danse moldave Vatra\u003c/strong\u003e fait battre le cœur du public québécois et canadien au rythme des \u003cstrong\u003emélodies, des couleurs et des traditions\u003c/strong\u003e venues de Moldavie!\u003c/p\u003e\n\u003cp\u003ePour marquer cette décennie d’émotions et de partage, Vatra vous invite à une \u003cstrong\u003egrande célébration\u003c/strong\u003e lors de la \u003cstrong\u003etroisième édition du Festival Mărțișor\u003c/strong\u003e, fête nationale qui honore \u003cstrong\u003el’arrivée du printemps\u003c/strong\u003e, la \u003cstrong\u003erenaissance\u003c/strong\u003e et la \u003cstrong\u003ejoie de vivre\u003c/strong\u003e.\u003c/p\u003e\n\u003cp\u003eSur scène, \u003cstrong\u003esept groupes de Vatra\u003c/strong\u003e, réunissant \u003cstrong\u003etrois générations de danseuses et danseurs\u003c/strong\u003e, feront vibrer les planches dans un tourbillon de \u003cstrong\u003emouvements envoûtants, de musiques entraînantes et d’énergie contagieuse\u003c/strong\u003e. Chaque pas raconte une histoire, chaque sourire illumine la scène — c’est une \u003cstrong\u003eode à la vie, à la culture et à la beauté du geste humain\u003c/strong\u003e.\u003c/p\u003e\n\u003cp\u003eEnsemble, célébrons \u003cstrong\u003e10 ans d’art, de passion et de traditions\u003c/strong\u003e qui nous unissent et nous rappellent que la danse est un langage universel du cœur.\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_troupe-de-danse-moldave-vatra-10-ans-de-tradition-et-passion#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/troupe-de-danse-moldave-vatra-10-ans-de-tradition-et-passion/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:23:57.873-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/troupe-de-danse-moldave-vatra-10-ans-de-tradition-et-passion/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_troupe-de-danse-moldave-vatra-10-ans-de-tradition-et-passion#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"70.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_broue-2026","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/broue-2026/"}],"http://schema.org/name":[{"@language":"fr","@value":"Broue"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K10-438"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/broue-2026/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Broue_2024.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-12-02T19:30:00-05:00"}],"http://schema.org/keywords":[{"@value":"[\"Théâtre\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003c/p\u003e\n\u003cp\u003eDepuis la toute première représentation qui a eu lieu le mercredi 21 mars 1979 l'histoire d'amour entre le public québécois et \u003cem\u003eBroue\u003c/em\u003e aura duré presque 39 ans sans interruption.\u003c/p\u003e\n\u003cp\u003eCette pièce de théâtre a dépassé depuis longtemps le simple statut de divertissement et de comédie pour devenir un véritable objet de fierté nationale.\u003cbr /\u003e\u003cbr /\u003eCar \u003cem\u003eBroue\u003c/em\u003e a fracassé de nombreux records.\u003cbr /\u003e\u003cbr /\u003eSes personnages hauts en couleurs font partis de notre imaginaire collectif, dans un portrait d'un Québec pré-référendaire, à la veille d'une invasion qui menacent leur aire protégée : l'arrivée des femmes dans ce lieu sacro-saint qu'est... la Taverne !\u003cbr /\u003e\u003cbr /\u003e\u003cem\u003eBob, Pointu, Verrue, Travolta\u003c/em\u003e et tous les autres continueront de vivre et de vous faire rire.\u003cbr /\u003e\u003cbr /\u003eNous vous invitons donc cher public à poursuivre l'aventure avec nous. Vous êtes tous cordialement invités à venir ou revenir lever votre verre à la Taverne chez Willy!\u003cbr /\u003e\u003cbr /\u003eEt... Bienvenue aux dames !!!\u003cbr /\u003e\u003cbr /\u003eLa Nouvelle administration : Benoît Brière, Luc Guérin et Martin Drainville\u003c/p\u003e\n\u003cp\u003e\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_broue-2026#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/broue-2026/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:14:40.971-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/broue-2026/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_broue-2026#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"69.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_rta-projet-theremine","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/rta-projet-theremine/"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/MDA/Locations/1000050612.jpg"}],"http://schema.org/name":[{"@language":"fr","@value":"Projet Thérémine"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-13T19:30:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/rta-projet-theremine/#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/keywords":[{"@value":"[\"Théâtre\", \"Spectacle en salle\"]"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eProjet Thérémine s’inspire de la vie fascinante de Léon Thérémine, cet inventeur visionnaire à la croisée de l’art et de la science, dont les créations ont marqué leur époque et continuent d’exercer une influence aujourd’hui. Pionnier des technologies électroniques et créateur du premier instrument de musique sans contact, il a laissé une empreinte durable dans des domaines aussi variés que la musique, le cinéma, l’aviation, l’espionnage, les systèmes de sécurité et même le paiement électronique. Mais plus encore, sa vie incarne une curiosité insatiable, une capacité à transformer même les moments les plus sombres en opportunités d’apprentissage et de découverte.\u003c/p\u003e\n\u003cp\u003eLa première ligne suit la vie de Thérémine jusqu’en 1938, l’année où il disparaît d’Amérique sans laisser de trace. Mort ? Kidnappé ? Parti volontairement ? Seul Thérémine le sait.\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_rta-projet-theremine#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/rta-projet-theremine/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:13:05.336-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/rta-projet-theremine/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_rta-projet-theremine#Offer_0","http://schema.org/price":[{"@value":"40.60"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_theatre-musical-de-laval-la-maison-harmonique","http://schema.org/name":[{"@language":"fr","@value":"La Maison Harmonique"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/theatre-musical-de-laval-la-maison-harmonique/"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/MDA/Locations/AfficheTML_756x110.png"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/theatre-musical-de-laval-la-maison-harmonique/#WebPage"}],"http://schema.org/keywords":[{"@value":"[\"Théâtre musical\", \"Spectacle en salle\"]"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eLors d’un voyage organisé en Amérique du sud, un groupe de touristes hétéroclites se retrouve à visiter la fameuse Maison Harmonique. Une maison qui est devenue célèbre grâce à ses vibrations musicales hors du commun. Quel effet aura cette Maison Harmonique sur nos voyageurs?… sur vous? La musique n’est-elle pas le langage de l’âme?… des émotions? Venez explorer les mystères enfouis de cette demeure magique. Un voyage musical inoubliable. Une comédie musicale pour toute la famille!\u003c/p\u003e"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a4561a6b-a551-4243-879c-659e0d0d5a81"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-19T19:30:00-04:00"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_theatre-musical-de-laval-la-maison-harmonique#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-21T19:30:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_theatre-musical-de-laval-la-maison-harmonique#2026-05-19T193000-0400"},{"@id":"http://kg.footlight.io/resource/co-motion-ca_theatre-musical-de-laval-la-maison-harmonique#2026-05-20T193000-0400"},{"@id":"http://kg.footlight.io/resource/co-motion-ca_theatre-musical-de-laval-la-maison-harmonique#2026-05-21T193000-0400"}]},{"@id":"https://co-motion.ca/spectacles/theatre-musical-de-laval-la-maison-harmonique/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:12:40.452-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/theatre-musical-de-laval-la-maison-harmonique/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_theatre-musical-de-laval-la-maison-harmonique#Offer_0","http://schema.org/price":[{"@value":"40.60"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_theatre-musical-de-laval-la-maison-harmonique#2026-05-19T193000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"La Maison Harmonique"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-19T19:30:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_theatre-musical-de-laval-la-maison-harmonique#2026-05-20T193000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"La Maison Harmonique"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-20T19:30:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_theatre-musical-de-laval-la-maison-harmonique#2026-05-21T193000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"La Maison Harmonique"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-21T19:30:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_marc-dupre-nouveau-spectacle","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Marc-Dupr%C3%A9-2.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/marc-dupre-nouveau-spectacle/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/marc-dupre-nouveau-spectacle/"}],"http://schema.org/name":[{"@language":"fr","@value":"Parce que ce soir"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-06T20:00:00-05:00"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eAprès une pause bien méritée, \u003cstrong\u003eMarc Dupré\u003c/strong\u003e est de retour avec un tout nouveau spectacle qui s'annonce électrisant \u003cstrong\u003e« Parce que ce soir » !\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e L’auteur-compositeur-interprète, chéri du public, reprend la route pour vous offrir une soirée inoubliable où ses plus grands succès se mêleront à ses toutes nouvelles chansons issues de son prochain album. \u003c/p\u003e\n\u003cp\u003eDu premier accord au dernier rappel, préparez-vous à une performance vibrante, pleine d’énergie, d’émotion et de moments de complicité avec le public. Marc revisite son impressionnant répertoire, de ses débuts à aujourd’hui, avec la passion et le talent qui font de lui un véritable hitmaker. \u003c/p\u003e\n\u003cp\u003eVenez chanter, danser et vibrer au rythme de ses mélodies entraînantes et de ses textes qui touchent le cœur.\u003c/p\u003e\n\u003cp\u003eUne chose est certaine : \u003cstrong\u003e« Parce que ce soir »\u003c/strong\u003e marquera son grand retour sur scène en chanson et vous ne voudrez pas le manquer !\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_marc-dupre-nouveau-spectacle#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/marc-dupre-nouveau-spectacle/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:21:53.088-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/marc-dupre-nouveau-spectacle/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_marc-dupre-nouveau-spectacle#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"59.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_claude-dubois","@type":["http://schema.org/Event"],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/claude-dubois/"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Claude-Dubois-2025.jpeg"}],"http://schema.org/name":[{"@language":"fr","@value":"Dubois solide en liberté"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eUn spectacle qui revisite les chansons les plus demandées de l’immense répertoire de l’artiste. \u003cstrong\u003eClaude Dubois\u003c/strong\u003e interprètera pour vous l’ensemble de ces incontournables. Un spectacle puissant qui survole le parcours d’un auteur, compositeur et interprète. Venez partager un moment magique avec le poète rocker au cœur tendre du Québec!\u003c/p\u003e"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"},{"@id":"http://kg.artsdata.ca/resource/Chansonfrancophone"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/watch?v=l_Ow0Y_O-6k"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-19T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/claude-dubois/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Chanson francophone\", \"Spectacle en salle\"]"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_claude-dubois#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/claude-dubois/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:21:09.637-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/claude-dubois/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_claude-dubois#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"70.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_hosanna-ou-la-sheherazade-des-pauvres","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/hosanna-ou-la-sheherazade-des-pauvres/"}],"http://schema.org/name":[{"@language":"fr","@value":"Hosanna ou La Shéhérazade des pauvres"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Hosanna.jpg"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/hosanna-ou-la-sheherazade-des-pauvres/#WebPage"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-13T19:30:00-05:00"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/keywords":[{"@value":"[\"Théâtre\", \"Spectacle en salle\"]"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cstrong\u003eRaconter c’est revivre \u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e Yannick, un jeune journaliste de \u003cem\u003eFugues\u003c/em\u003e – le magazine LGBTQ+ – a réussi à retrouver Claude Lemieux, celui qui, en 1973, sous le nom d’Hosanna, avait été ridiculisé jusqu’à l’anéantissement par ses « amies » de la \u003cem\u003eMain\u003c/em\u003e. Pourtant, ce soir-là, il allait enfin réaliser son rêve : être accepté au sein de leur cénacle en paraissant devant elles déguisée en Cléopâtre telle qu’incarnée par Elizabeth Taylor. Yannick veut l’interroger sur le contexte socioculturel de cette soirée dont la légende est en train de définitivement s’effacer. Or, au journaliste, qui souhaite parler de culture \u003cem\u003equeer\u003c/em\u003e et d’\u003cem\u003eexpression de genre\u003c/em\u003e, Claude Lemieux répond en faisant apparaître devant lui (et devant nous!) les splendeurs et misères passées d’un Montréal marginal en particulier le glorieusement kitsch \u003cem\u003eHawaïan\u003c/em\u003e Lounge et ses extravagantes vedettes : la perfide Sandra et la grandiose Duchesse de Langeais. \u003c/p\u003e\n\n\n\n\n\n\n\u003cp\u003eAujourd’hui, les \u003cem\u003edrag \u003c/em\u003equeens font partie de notre culture populaire. Mais, il y a cinquante ans, qu’est-ce que cela impliquait pour un homme que de se vêtir comme une femme ? En public ! En assemblant des extraits d’\u003cem\u003eHosanna\u003c/em\u003e et du récent roman de Tremblay \u003cem\u003eLa Shéhérazade des pauvres\u003c/em\u003e – émaillés de quelques pétillantes pépites d’\u003cem\u003eIl était une fois dans l’Est\u003c/em\u003e –, le metteur en scène Maxime Robin réussit un tour de force : à la fois raconter les avancées fragiles d’un groupe social et l’histoire profondément émouvante d’un homme blessé par ses propres rêves. \u003c/p\u003e\n\n\u003cp\u003eMaxime Robin a eu l’idée lumineuse d’accorder le rôle d’Hosanna agé, à Luc Provost qui, par son personnage de Mado Lamotte, a fait entrer les \u003cem\u003edrag \u003c/em\u003equeens dans notre quotidien. Ce spectacle, qui a triomphé lors de sa création au Théâtre du Trident en 2023, est enfin présenté à Montréal. \u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_hosanna-ou-la-sheherazade-des-pauvres#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/hosanna-ou-la-sheherazade-des-pauvres/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:15:28.705-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/hosanna-ou-la-sheherazade-des-pauvres/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_hosanna-ou-la-sheherazade-des-pauvres#Offer_0","http://schema.org/price":[{"@value":"62.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_gc-choeur-comme-un-air-dopera","@type":["http://schema.org/Event"],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/f56c4b49-240a-447d-8212-5f0c6333e42a"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K5-431"},{"@id":"http://kg.artsdata.ca/resource/K10-533"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/gc-choeur-comme-un-air-dopera/#WebPage"}],"http://schema.org/name":[{"@language":"fr","@value":"Les Grands Concerts"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/gc-choeur-comme-un-air-dopera/"}],"http://schema.org/keywords":[{"@value":"[\"Musique classique\", \"Spectacle en salle\"]"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/OSL/GC-2025-26/OSL_COMOTION_SITE_WEB_GC3.jpg"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T15:00:00-05:00"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eFondé en 1968 par\u003cstrong\u003e Germain Lefebvre\u003c/strong\u003e, le \u003cstrong\u003eChœur de Laval \u003c/strong\u003eest une institution reconnue internationalement pour la qualité de son répertoire et de ses interprétations. Composé de plus de 80 choristes passionnés, il explore un vaste éventail d’œuvres, de \u003cstrong\u003eBach\u003c/strong\u003e à \u003cstrong\u003eRutter\u003c/strong\u003e, sous la direction inspirée de \u003cstrong\u003eDany Wiseman\u003c/strong\u003e, à la tête du chœur depuis plusieurs années. Pour ce concert, le Chœur se joindra à l’Orchestre pour un programme consacré aux œuvres phares du répertoire lyrique, accompagné de la célèbre suite orchestrale\u003cstrong\u003e Peer Gynt d’Edvard Grieg.\u003c/strong\u003e Un rendez-vous à ne pas manquer, où chaque note résonnera… comme un air d’opéra.\u003c/p\u003e\n\n\u003cp\u003e\u003cstrong\u003eChef d’orchestre \u003c/strong\u003e: Adam Johnson\u003cbr /\u003e\u003cstrong\u003eChœur de Laval\u003c/strong\u003e\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_gc-choeur-comme-un-air-dopera#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/gc-choeur-comme-un-air-dopera/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-30T10:20:56.727-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/gc-choeur-comme-un-air-dopera/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_gc-choeur-comme-un-air-dopera#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"65.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_toc-toc-2025","@type":["http://schema.org/Event"],"http://schema.org/keywords":[{"@value":"[\"Théâtre\", \"Spectacle en salle\"]"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/toc-toc.jpg"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/toc-toc-2025/"}],"http://schema.org/name":[{"@language":"fr","@value":"Toc Toc"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-13T19:30:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/toc-toc-2025/#WebPage"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eMonarque vous invite à vivre une aventure hilarante et émouvante avec \u003cem\u003eTOC \u003c/em\u003eTOC. Cette pièce de théâtre, ovationnée à travers le monde, met en scène six fascinants, patients chacun avec ses Troubles Obsessionnels Compulsifs (TOC), réunis dans la salle d’attente d’un célèbre docteur. On y rencontre des personnages hauts en couleur souffrant du Syndrome de Gilles de la Tourette (tics moteurs et sonores), de l’arithmomanie (obsession des chiffres), de la nosophobie (peur des maladies), du TOC de vérification, de la palilalie (répétition incessante) et du TOC d’ordre et de symétrie. \u003c/p\u003e\n\n\u003cp\u003ePierre-François Legendre, le metteur en scène, précise : « Le psychiatre étant en retard, nos six patients, qui pensaient tous avoir un rendez-vous avec lui, se retrouvent à improviser une thérapie de groupe. Leurs TOC s’entrechoquent dans des interactions aussi drôles que touchantes, révélant des êtres humains vulnérables et courageux. Entre éclats de rire, entraide inattendue, tensions explosives et réconciliations émouvantes, cette rencontre va bouleverser leurs vies et les amener à se comprendre, à se soutenir et, finalement, à s’apprécier ». \u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_toc-toc-2025#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/toc-toc-2025/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-12T10:24:13.454-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/toc-toc-2025/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_toc-toc-2025#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"72.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_jo-cormier-esquisse-en-rodage","@type":["http://schema.org/Event"],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Lavalorama/Jo-Cormier-2.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-31T20:00:00-05:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/name":[{"@language":"fr","@value":"Esquisse (en rodage)"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eJe vous invite à venir faire partie des premières esquisses de mon prochain show. Je ne vous promets rien, tout au plus des « hi hi ». Un travail au plomb, un rough de charpente, des écharpes et des échardes.\u003c/p\u003e\n\u003cp\u003eJ’esquisse mon heure. Tu esquisses un sourire ou deux. Nous esquissons dans le but que ce projet devienne exquis et que tu puisses dire : « J'étais là ».\u003c/p\u003e\n\u003cp\u003eL’esquisse est une première forme, un plan, une pensée, une idée, un nuage. En dessin, on peut parler d’un croquis. En littérature, de notes essentielles guidant l’auteur vers son œuvre.\u003c/p\u003e\n\u003cp\u003eDans toutes les formes d’art, l’esquisse est un secret bien gardé, rarement affichée, plutôt cachée comme un puits d'eau potable dans lequel l’artiste s’abreuve lors de ses sécheresses mentales. En humour, l’art du m’as-tu vu, une esquisse sans public est un coup d’épée dans l’eau.\u003c/p\u003e\n\u003cp\u003eC’est indéniable, un humoriste a besoin de son public à beaucoup d’étapes de la création. C’est un art de réaction, un art de communication dans lequel on vous demande d’écouter, de rire, de ne pas trop parler et d'aller subtilement aux toilettes.\u003c/p\u003e\n\u003cp\u003eC’est un art de simplicité, simple que bien des facteurs tentent de compliquer, où la dernière révolution technologique date de fin 19e siècle avec l’apparition du micro.\u003c/p\u003e\n\u003cp\u003eDepuis, on oscille entre micro casque, micro bâton et micro avec fil dépendant de la génération et des idéaux. Une chose demeure : le rire.\u003cbr /\u003e\u003c/p\u003e"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/jo-cormier-esquisse-en-rodage/"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/jo-cormier-esquisse-en-rodage/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-213"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_jo-cormier-esquisse-en-rodage#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/jo-cormier-esquisse-en-rodage/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-30T10:13:42.261-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/jo-cormier-esquisse-en-rodage/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_jo-cormier-esquisse-en-rodage#Offer_0","http://schema.org/price":[{"@value":"10.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_dumas-piano-voix-diapos","@type":["http://schema.org/Event"],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/name":[{"@language":"fr","@value":"PIANO VOIX DIAPOS"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Dumas.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/dumas-piano-voix-diapos/#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/dumas-piano-voix-diapos/"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cstrong\u003ePIANO VOIX DIAPOS\u003c/strong\u003e c’est Dumas sans guitare, accompagné d’un pianiste et de diapositives. Dans une mise en scène minimaliste, il se raconte en chansons, revisitant 25 ans de répertoire, avec sensibilité et toujours cette volonté d’offrir une expérience inoubliable et lumineuse au public.\u003c/p\u003e\n\u003cp\u003e1$ par billet vendu sera remis à la Fondation Cowboys Fringants.\u003cbr /\u003eMerci \u0026#x1f49a;\u003c/p\u003e"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Spectacle en salle\"]"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-06T20:00:00-05:00"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_dumas-piano-voix-diapos#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/dumas-piano-voix-diapos/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:17:03.033-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/dumas-piano-voix-diapos/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_dumas-piano-voix-diapos#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"44.05"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_ari-cui-cui-ari-cui-cui-et-les-jeux-droles","@type":["http://schema.org/Event"],"http://schema.org/keywords":[{"@value":"[\"Jeune public\", \"Spectacle en salle\"]"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/ari-cui-cui-ari-cui-cui-et-les-jeux-droles/"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-15T15:30:00-04:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/ari-cui-cui.jpg"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Les contes classiques en musique"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/22f50ba1-3403-48a8-a279-460952d3c134"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/ari-cui-cui-ari-cui-cui-et-les-jeux-droles/#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cem\u003eAri Cui \u003c/em\u003eCui et les jeux d’rôles – Les contes classiques en musique est un spectacle familial amusant et ludique qui permet aux enfants et aux parents d’exprimer leurs émotions par la musique et le jeu. Ari Cui Cui a toujours de nouvelles histoires magiques à partager et sait transformer l'ordinaire en extraordinaire grâce à sa touche unique et enchantée. Les enfants adorent, les parents ouvrent grand les oreilles ! \u003c/p\u003e\n\u003cp\u003eLe fil conducteur de ce voyage s’articule autour de grands contes classiques tels que \u003cem\u003eCendrillon\u003c/em\u003e, \u003cem\u003eLe\u003c/em\u003e petit chaperon rouge, \u003cem\u003eHansel\u003c/em\u003e et Gretel, \u003cem\u003eLa cigale et l\u003c/em\u003ea fourmi, \u003cem\u003eLe loup et les trois petits cochons\u003c/em\u003e et \u003cem\u003eBoucle d’or.\u003c/em\u003e Il s’adresse aux enfants de\u003cstrong\u003e 6 mois à 8 ans\u003c/strong\u003e, qui sont invités avec leurs parents à se déguiser, à jouer des personnages et à mimer les émotions qu’ils ressentent dans un environnement allumé et dynamique créé par Ari Cui Cui et son ami et complice, le Boulanger joyeux. \u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_ari-cui-cui-ari-cui-cui-et-les-jeux-droles#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/ari-cui-cui-ari-cui-cui-et-les-jeux-droles/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:14:54.304-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/ari-cui-cui-ari-cui-cui-et-les-jeux-droles/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_ari-cui-cui-ari-cui-cui-et-les-jeux-droles#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"30.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_andre-philippe-gagnon-remasterise","@type":["http://schema.org/Event"],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/andre-philippe-gagnon-remasterise/#WebPage"}],"http://schema.org/name":[{"@language":"fr","@value":"Remasterisé"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-19T15:00:00-04:00"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/APG_Remasterise_NOtext_Story_1080x1920.jpg"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/andre-philippe-gagnon-remasterise/"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eDans ce nouveau spectacle, André-Philippe Gagnon continue de nous épater avec ses prouesses vocales, comme il le fait depuis 40 ans. En plus de 70 imitations virtuoses, il nous partage pour la première fois des anecdotes personnelles de son incroyable carrière, livrées avec humour et authenticité, pour nous faire découvrir l’homme derrière les milles voix. Il revisite les plus belles chansons des dernières décennies qui nous accompagnent dans nos vies. Un moment unique, haut en rire, en émotion et en musique !\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_andre-philippe-gagnon-remasterise#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/andre-philippe-gagnon-remasterise/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:13:29.838-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/andre-philippe-gagnon-remasterise/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_andre-philippe-gagnon-remasterise#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"53.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_billy-tellier-2025","@type":["http://schema.org/Event"],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eFier du succès de sa dernière tournée avec plus de 50 000 billets vendus et près de 200 représentations, \u003cstrong\u003eBilly Tellier\u003c/strong\u003e présente son 3e one-man-show Kicker la ruche. Billy, qui a ce talent inouï de toucher la cible avec des sujets rassembleurs, présente cette fois-ci un spectacle encore plus punché et rythmé dans lequel il livre ses états d’âme sur sa peur de faire les mauvais choix... Toute sa vie, Billy a tenté de vivre en bonne petite abeille ouvrière, en faisant ce qu’on attendait de lui et en prenant les décisions les plus sages afin d’impacter le moins possible les gens qui l’entourent. Dans ce nouveau spectacle, il se demande s’il ne serait pas temps de remettre les choses en question, de brasser son quotidien, bref de kicker la ruche. Mais est-ce vraiment une bonne idée pour quelqu'un qui angoisse en choisissant entre crémeuse ou traditionnelle?\u003c/p\u003e"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/name":[{"@language":"fr","@value":"Kicker la ruche"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Billy-Tellier.jpg"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/billy-tellier-2025/"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-21T19:30:00-05:00"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/billy-tellier-2025/#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_billy-tellier-2025#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/billy-tellier-2025/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-20T10:13:45.395-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/billy-tellier-2025/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_billy-tellier-2025#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"43.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_pierre-yves-roy-desmarais-une-annee-en-un-an","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/pierre-yves-roy-desmarais-une-annee-en-un-an/"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/PYRD.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-20T20:00:00-05:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/name":[{"@language":"fr","@value":"Une année en un an"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eAprès avoir remporté les Olivier du spectacle de l’année et d’auteur de l’année, ainsi que le Félix du spectacle d’humour de l’année, \u003cstrong\u003ePierre-Yves Roy-Desmarais\u003c/strong\u003e élève son art à un niveau supérieur avec son deuxième spectacle.\u003c/p\u003e\n\n\n\u003cp\u003eUn soir, alors qu’il ne s’attend à rien, Pierre-Yves Roy-Desmarais vit un événement qui changera le cours de son existence. Il tente tant bien que mal de garder le contrôle de sa vie, mais le temps devient flou, les souvenirs se mélangent et les unités de mesure ne veulent plus rien dire. Avec tout ce qui lui arrive, il a l’impression de vivre l’équivalent d’une année complète, en seulement un an. Une année en un an est un spectacle alliant stand-up, chansons et fromage, d’une durée de 75 centimètres.\u003c/p\u003e"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/pierre-yves-roy-desmarais-une-annee-en-un-an/#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_pierre-yves-roy-desmarais-une-annee-en-un-an#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/pierre-yves-roy-desmarais-une-annee-en-un-an/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:21:44.552-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/pierre-yves-roy-desmarais-une-annee-en-un-an/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_pierre-yves-roy-desmarais-une-annee-en-un-an#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"49.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_elles-luce-dufault-kim-richardson-lulu-hughes","http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Chanson francophone\", \"Jazz / Blues\", \"Rock / Rock indépendant\", \"Spectacle en salle\"]"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-08T15:00:00-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/elles-luce-dufault-kim-richardson-lulu-hughes/"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/elles-luce-dufault-kim-richardson-lulu-hughes/#WebPage"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Elles-avec-Luce-Dufault-Kim-Richardson-et-Lulu-Hughes_2025.jpg"}],"http://schema.org/name":[{"@language":"fr","@value":"ELLES"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"},{"@id":"http://kg.artsdata.ca/resource/Chansonfrancophone"},{"@id":"http://kg.footlight.io/resource/9da661fe-089b-4061-a274-ac91dfdc45c8"},{"@id":"http://kg.footlight.io/resource/0a6aa5c3-3abf-4bbe-b591-a63dfc9a34f7"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cstrong\u003eLuce Dufault, Lulu Hughes et Kim Richardson réunies sur scène dans ELLES \u003c/strong\u003e\u003c/p\u003e\n\n\u003cp\u003eS’il y a trois voix que l’on rêve d’entendre à l’unisson, ce sont bien celles de Luce Dufault, Lulu Hughes et Kim Richardson. Ce rêve deviendra réalité puisque les trois interprètes se retrouveront ensemble sur scène dans un nouveau spectacle de Productions Martin Leclerc intitulé ELLES, et qui est en quelque sorte un retour aux sources pour les trois artistes. Leur répertoire? Jazz, soul, rhythm and blues, folk et rock bien sûr, avec des chansons qui leur permettront de s’éclater. Frissons garantis. \u003c/p\u003e\n\u003cp\u003eLuce, Lulu et Kim sont au sommet de leur art et elles habiteront la scène avec panache, que ce soit en solo, en duo ou en trio, dans une rencontre exceptionnelle où la spontanéité et la complicité ressemblent à l’amitié qui les unit depuis 30 ans. \u003c/p\u003e\n\u003cp\u003e La mise en scène de ELLES a été confiée à Michel Poirier et la direction musicale sera signée Jean Garneau. \u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003ePour chaque billet vendu 1$ sera remis à la Fondation du Y des Femmes et 1$ sera remis au réseau Diffuseurs Inter-Centres pour le fond de soutien à la création.\u003c/strong\u003e\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_elles-luce-dufault-kim-richardson-lulu-hughes#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-16T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_elles-luce-dufault-kim-richardson-lulu-hughes#2026-02-08T150000-0500"},{"@id":"http://kg.footlight.io/resource/co-motion-ca_elles-luce-dufault-kim-richardson-lulu-hughes#2026-10-16T200000-0400"}]},{"@id":"https://co-motion.ca/spectacles/elles-luce-dufault-kim-richardson-lulu-hughes/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:19:47.614-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/elles-luce-dufault-kim-richardson-lulu-hughes/"}]},{"@id":"http://kg.footlight.io/resource/0a6aa5c3-3abf-4bbe-b591-a63dfc9a34f7","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Rock / Rock indépendant"}],"http://www.w3.org/2004/02/skos/core#closeMatch":[{"@id":"http://kg.artsdata.ca/resource/RockMusicPerformance"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_elles-luce-dufault-kim-richardson-lulu-hughes#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"55.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_elles-luce-dufault-kim-richardson-lulu-hughes#2026-02-08T150000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"ELLES"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-08T15:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_elles-luce-dufault-kim-richardson-lulu-hughes#2026-10-16T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"ELLES"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-16T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_rue-duplessis-ma-petite-noirceur","@type":["http://schema.org/Event"],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eQui aurait cru qu’un petit gars de la rue Duplessis à Drummondville, qui a grandi dans la pauvreté, la peur et les préjugés, deviendrait un sociologue estimé et un animateur réputé à Radio-Canada ? Jean-Philippe Pleau vient d’une famille bienveillante dans laquelle l’ambition intellectuelle suscitait la méfiance : « L’école, c’est pour ceux qui pètent plus haut que leur trou » y disait-on. Nourri au « macarouni » gratiné au fromage Singles de Kraft, celui qu’on connaît surtout pour ses réflexions savoureuses à \u003cem\u003eC’est fou… \u003c/em\u003e(avec Serge Bouchard) et à \u003cem\u003eRéfléchir à voix haute\u003c/em\u003e est aujourd’hui étranger au monde d’où il vient, sans vraiment appartenir à celui dans lequel il a abouti. \u003cem\u003eRue Duplessis,\u003c/em\u003e c’est son histoire, celle de sa « migration intérieure » de transfuge de classe; mais c’est aussi un regard critique sur les inégalités socioéconomiques qui dictent encore trop souvent le destin et les aspirations de celles et ceux qui sont « né·es pour un p’tit pain ». Un parcours drôle et émouvant, empreint de courage, de nostalgie… et de sociologie. \u003c/p\u003e\n\u003cp\u003eJean-Philippe Pleau, qui s’est faufilé entre les mailles d’un système qui ne le prédisposait pas à devenir auteur, dit qu’il écrit pour « venger les siens ». Après l'énorme succès de son roman autobiographique \u003cem\u003eRue Duplessis, ma petite noirceur\u003c/em\u003e, il s’apprête maintenant à franchir une autre étape : monter sur scène pour défendre son récit si personnel devant des centaines de spectateur·ices, soir après soir. Le codirecteur artistique du Théâtre Duceppe, David Laurin, qui en signe l’adaptation, et la metteuse en scène Marie-Ève Milot (\u003cem\u003eMama\u003c/em\u003e, \u003cem\u003eDocteure\u003c/em\u003e) en proposent une version théâtrale riche et surprenante au fil de laquelle l’auteur nous racontera sa propre révolution tranquille. Au-delà de l’intime, ce spectacle trouvera assurément une résonance toute particulière chez de nombreux·ses Québécois·es qui, comme Jean-Philippe, naviguent entre deux mondes; ils et elles y reconnaîtront leur propre vécu, leur fierté et leurs contradictions. \u003c/p\u003e"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Rue-Duplessis.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/keywords":[{"@value":"[\"Théâtre\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/rue-duplessis-ma-petite-noirceur/"}],"http://schema.org/name":[{"@language":"fr","@value":"Rue Duplessis | Ma petite noirceur"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/rue-duplessis-ma-petite-noirceur/#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-20T19:30:00-04:00"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_rue-duplessis-ma-petite-noirceur#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/rue-duplessis-ma-petite-noirceur/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:16:54.435-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/rue-duplessis-ma-petite-noirceur/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_rue-duplessis-ma-petite-noirceur#Offer_0","http://schema.org/price":[{"@value":"66.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_abra-cadabra-un-hommage-a-abba","@type":["http://schema.org/Event"],"http://schema.org/keywords":[{"@value":"[\"Spectacle hommage\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/8f6b3be1-6023-4ad2-aadb-a259194b9a33"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/abra-cadabra-un-hommage-a-abba/"}],"http://schema.org/name":[{"@language":"fr","@value":"ABRA Cadabra - Un hommage à ABBA"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/abra-cadabra-un-hommage-a-abba/#WebPage"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Locations-TMC/Abba.jpg"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-213"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-06-14T19:30:00-04:00"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eABRA Cadabra: Un hommage à ABBA\u003c/p\u003e\n\u003cp\u003eAttention tous les fans d'ABBA ! Réservez la date du 14 juin 2026 dans vos agendas, car ABRA Cadabra, le célèbre groupe hommage à ABBA de Vancouver, se produira à Laval pour la première fois ! Ce spectacle familial promet de faire danser et chanter le public sur tous les grands succès d'ABBA comme Mamma Mia, Dancing Queen, Take a Chance et bien plus encore!\u003c/p\u003e\n\u003cp\u003e\u003cbr /\u003eA Tribute To ABBA\u003c/p\u003e\n\u003cp\u003eAttention all ABBA fans! Circle June 14, 2026 on your calendars because ABRA Cadabra, Vancouver's renowned ABBA Tribute band, is coming to Laval for the 1st time! This family-friendly stage extravaganza promises to have audiences on their feet, clapping and singing along to all of ABBA’s iconic hits like Mamma Mia, Dancing Queen, Take a Chance and so much more!\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_abra-cadabra-un-hommage-a-abba#Offer_0"}]},{"@id":"http://kg.footlight.io/resource/8f6b3be1-6023-4ad2-aadb-a259194b9a33","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Spectacle hommage"}],"http://www.w3.org/2004/02/skos/core#closeMatch":[{"@id":"http://kg.artsdata.ca/resource/PerformingArtsEvent"}]},{"@id":"https://co-motion.ca/spectacles/abra-cadabra-un-hommage-a-abba/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:24:05.032-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/abra-cadabra-un-hommage-a-abba/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_abra-cadabra-un-hommage-a-abba#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"59.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_qw4rtz-dernier-spectacle","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/qw4rtz-dernier-spectacle/"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Qw4rtz.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/qw4rtz-dernier-spectacle/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Dernier spectacle"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-18T20:00:00-04:00"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eAprès plus de 15 ans à enflammer les scènes du Québec et d’ailleurs avec leur a cappella unique, \u003cstrong\u003eQW4RTZ\u003c/strong\u003e vous présente leur ultime spectacle – le plus grand, le plus fou, le plus mémorable.\u003c/p\u003e\n\u003cp\u003eUn concentré de leurs meilleures harmonies, de leurs pires et meilleures blagues… mais surtout, un adieu sincère et inoubliable.\u003c/p\u003e\n\u003cp\u003eUn show qui vous fera vibrer encore pour la prochaine décennie.\u003cbr /\u003e\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_qw4rtz-dernier-spectacle#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/qw4rtz-dernier-spectacle/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:22:28.908-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/qw4rtz-dernier-spectacle/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_qw4rtz-dernier-spectacle#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"49.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_quand-tu-me-regardes","@type":["http://schema.org/Event"],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-26T15:00:00-04:00"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/22f50ba1-3403-48a8-a279-460952d3c134"},{"@id":"http://kg.footlight.io/resource/2fa05002-967b-43c2-b5cb-fc93a25edcb1"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Quand tu me regardes"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/MDA/Programmation-ville/Jeune-Public/2025-2026/Quand-tu-me-regardes.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-77"}],"http://schema.org/keywords":[{"@value":"[\"Jeune public\", \"Danse\", \"Spectacle en salle\"]"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/819196505"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/quand-tu-me-regardes/#WebPage"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/quand-tu-me-regardes/"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003csub\u003eCorpuscule Danse \u003c/sub\u003e\u003cbr /\u003e\u003c/p\u003e\n\u003ch3\u003eQu’est-ce qui nous rend toutes et tous uniques? \u003c/h3\u003e\n\u003cp\u003eDes corps vibrants et des fauteuils roulants. Tout ça se déchaîne en scène! Trois performeurs virtuoses font tournoyer ces véhicules fous, dansent avec, les défont en morceaux pour qu’ils deviennent marionnettes, roues lumineuses, robots disco, quoi d’autre? L’imagination est la seule limite.\u003c/p\u003e\n\u003cp\u003eQuand on voit un fauteuil roulant, on pense à quoi? Les trois interprètes tout-terrain y voient un bolide épatant aux possibilités de jeu infinies. Agencé comme un joyeux collier de perles scéniques, ce spectacle tendre et ludique pour danseurs, danseuses et objets nous entraîne dans un univers où les corps sont souvent en fusion, mais où chacun se révèle, raconte sa propre histoire en mouvement.\u003c/p\u003e\n\u003cp\u003e\u003cem\u003e\u003cstrong\u003eQuand tu me regardes\u003c/strong\u003e\u003c/em\u003e est l’histoire d’une danseuse en situation de handicap et celle d’humains qui partagent, le temps d’une danse, toute la fragilité et la beauté d’être soi. Ensemble.\u003cbr /\u003e\u003cbr /\u003eFondée par \u003cstrong\u003eFrance Geoffroy\u003c/strong\u003e, artiste en situation de handicap, \u003cstrong\u003eMartine Lusignan\u003c/strong\u003e et \u003cstrong\u003eIsaac Savoie\u003c/strong\u003e, \u003cstrong\u003eCorpuscule Danse\u003c/strong\u003e est la première compagnie professionnelle de création en danse inclusive au Canada. La compagnie se consacre au développement d’une danse inclusive fondée sur la rencontre d’artistes, étant ou non en situation de handicap, perceptible ou imperceptible. \u003c/p\u003e\n\u003cp\u003eDurée : 50 minutes\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"http://corpusculedanse.com\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eSite Web de Corpuscule Danse\u003c/a\u003e \u003ca href=\"https://www.laval.ca/calendrier-activites/quand-regardes-mda-danse-jeune-public/\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eMaison des arts de Laval\u003c/a\u003e\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_quand-tu-me-regardes#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/quand-tu-me-regardes/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:22:39.584-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/quand-tu-me-regardes/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_quand-tu-me-regardes#Offer_0","http://schema.org/price":[{"@value":"17.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_mohamed-khassani-ma-gueule","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"MA GUEULE"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Locations-TMC/Mohamed-Khassani.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eMohamed Khassani est un comédien, humoriste, chanteur et DJ Algérien originaire d'Oran, connu pour ses spectacles familiaux et son humour universel qui puise dans la vie quotidienne et aborde des thèmes sociaux et culturels\u003c/p\u003e"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/9475525c-10fa-4a53-b89b-8c7d9ba68726"}],"http://schema.org/keywords":[{"@value":"[\"Variétés\", \"Spectacle en salle\"]"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-24T20:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/mohamed-khassani-ma-gueule/#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-213"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/mohamed-khassani-ma-gueule/"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_mohamed-khassani-ma-gueule#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/mohamed-khassani-ma-gueule/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-22T10:16:03.291-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/mohamed-khassani-ma-gueule/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_mohamed-khassani-ma-gueule#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"50.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_real-beland-comme-chez-nous","@type":["http://schema.org/Event"],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-13T20:00:00-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/real-beland-comme-chez-nous/"}],"http://schema.org/name":[{"@language":"fr","@value":"Comme chez nous"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/R%C3%A9al-B%C3%A9land.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/real-beland-comme-chez-nous/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-213"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003ch4\u003e« Comme chez nous », c’est le 6ème show de Réal Béland! \u003c/h4\u003e\n\u003cp\u003eUn spectacle qui entre dans l’intimité d’un comique qui comme le dit la rumeur a été conçu dans les coulisses d’une salle de spectacle. Après avoir passé sa vie dans les salles, Réal se sent chez lui lorsqu’il s’arrête en tournée chez vous! Une simplicité désarmante avec ces gags absurdes, son M. Latreille et beaucoup de surprises!!!\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_real-beland-comme-chez-nous#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/real-beland-comme-chez-nous/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:15:19.254-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/real-beland-comme-chez-nous/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_real-beland-comme-chez-nous#Offer_0","http://schema.org/price":[{"@value":"44.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_le-petit-roi-pour-toujours","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Le-petit-roi.jpg"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/le-petit-roi-pour-toujours/"}],"http://schema.org/name":[{"@language":"fr","@value":"Le Petit Roi pour toujours"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2027-04-10T20:00:00-04:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Spectacle hommage\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"},{"@id":"http://kg.footlight.io/resource/8f6b3be1-6023-4ad2-aadb-a259194b9a33"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/le-petit-roi-pour-toujours/#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cstrong\u003eJean-Pierre Ferland\u003c/strong\u003e, icône de la chanson québécoise, restera à jamais \u003cem\u003eLe Petit Roi\u003c/em\u003e pour tous ceux et celles qui ont eu la chance d’entendre, de ressentir et de vivre sa musique. \u003c/p\u003e\n\u003cp\u003eAuteur-compositeur-interprète parmi les plus marquants et les plus importants du patrimoine musical et culturel du Québec, il laisse une œuvre riche et intemporelle. \u003c/p\u003e\n\u003cp\u003eLe spectacle \u003cem\u003eLe Petit Roi pour toujours\u003c/em\u003e rend un hommage sincère et rassembleur à ce pilier de la chanson québécoise, célébrant son héritage unique et s’adressant à tous les amoureux de sa musique et de sa poésie. \u003c/p\u003e\n\u003cp\u003eAinsi, une quarantaine de succès, témoins de plus de soixante ans de création seront interprétés par une distribution remarquable composée de certaines des plus belles voix du Québec avec \u003cstrong\u003eJohanne Blouin, Kevin Parent, Marc-André Fortin, Éric Paulhus\u003c/strong\u003e, ainsi que \u003cstrong\u003eJulie Anne Saumur\u003c/strong\u003e. Accompagné de \u003cstrong\u003eLynn\u003c/strong\u003e \u003cstrong\u003eJodoin\u003c/strong\u003e, choriste et complice de longue date de Jean-Pierre, et par une dizaine de musiciens sur scène, les voix, les archives et les projections d’images sur écran, plongeront le public dans un univers empreint de poésie, d’humour et de tendresse – celui d’un artiste inoubliable et de son immense répertoire. \u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_le-petit-roi-pour-toujours#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/le-petit-roi-pour-toujours/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:18:44.815-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/le-petit-roi-pour-toujours/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_le-petit-roi-pour-toujours#Offer_0","http://schema.org/price":[{"@value":"88.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_david-corriveau-1000-contrefacons","@type":["http://schema.org/Event"],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/david-corriveau-1000-contrefacons/#WebPage"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/david-corriveau-1000-contrefacons/"}],"http://schema.org/name":[{"@language":"fr","@value":"100 contrefaçons"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/David-Corriveau.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-30T15:00:00-04:00"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/keywords":[{"@value":"[\"Variétés\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/9475525c-10fa-4a53-b89b-8c7d9ba68726"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cstrong\u003eImitateur aux mille voix et aux mille talents !\u003c/strong\u003e Découvert sur le plateau de \u003cem\u003eSalut Bonjour! \u003c/em\u003een 2021 où il a impressionné Gino Chouinard et le Québec tout entier, David s’envole ensuite pour \u003cem\u003eLa France a un incroyable talent\u003c/em\u003e où il décroche le golden buzzer de Sugar Sammy et se rend en finale. \u003c/p\u003e\n\n\u003cp\u003eDepuis, il cumule plus de 35 millions de vues sur les réseaux sociaux et il a le vent dans les voiles grâce à son premier spectacle \u003cem\u003e100 contrefaçons\u003c/em\u003e avec lequel il plonge dans un répertoire des chansons populaires des années 1970 à 2000. \u003c/p\u003e\n\n\u003cp\u003eC’est dans une ambiance intime, chaleureuse et feel good qu’il nous raconte comment il a réussi à passer du poulailler familial de Saint-Boniface-de-Shawinigan à la Place des Arts de Montréal pour réaliser son rêve de chanter sur scène comme ses idoles, qu’il imite de façon spectaculaire ! Une aventure possible grâce à vous et qui ne fait que commencer… \u003c/p\u003e\n\n\u003cp\u003e\u003cem\u003e100 contrefaçons\u003c/em\u003e, les prouesses d’un imitateur à l’énergie contagieuse qui prend un véritable plaisir à nous faire revivre des moments pas si lointains que l’on chérit encore. Vous fermez les yeux et vous croyez entendre Céline Dion, Édith Piaf, Claude Dubois, Laurence Jalbert, Marie Carmen, James Blunt ou Ginette Reno, mais en les ouvrant, incrédules, vous avez un seul homme devant vous. \u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_david-corriveau-1000-contrefacons#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/david-corriveau-1000-contrefacons/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:18:53.553-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/david-corriveau-1000-contrefacons/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_david-corriveau-1000-contrefacons#Offer_0","http://schema.org/price":[{"@value":"41.25"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_ladies-night-2025","@type":["http://schema.org/Event"],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/name":[{"@language":"fr","@value":"Ladies Night"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Ladies-Night.jpg"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/ladies-night-2025/"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-04T19:30:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/ladies-night-2025/#WebPage"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"[\"Théâtre\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp dir=\"ltr\"\u003e\u003cstrong\u003e5 chômeurs désespérés. 1 plan fou : se déshabiller !\u003c/strong\u003e\u003c/p\u003e\n\u003cp dir=\"ltr\"\u003eLe phénomène qui fait rire (et rougir) le Québec depuis 25 ans est de retour avec sa distribution originale ! Michel Charette, François Chénier et Marcel Lebœuf reprennent leurs rôles cultes, accompagnés des nouvelles recrues Pierre-Paul Alain, Lyndz Dantiste et Frédérike Bédard.\u003c/p\u003e\n\u003cp dir=\"ltr\"\u003eL'histoire ? Cinq chums au chômage depuis 6 mois, fauchés et prêts à tout pour s'en sortir. Leur plan B ? Se déshabiller pour se remplir les poches ! Parce que quand on n'a plus rien à perdre, aussi bien tout enlever !\u003c/p\u003e\n\u003cp dir=\"ltr\"\u003eAvec l'aide (et la patience) de Josianne, une ex-danseuse, ces amateurs tenteront l'impossible : créer un vrai spectacle de strip-tease… ou presque !\u003c/p\u003e\n\u003cp dir=\"ltr\"\u003eEntre les fous rires, les maladresses et les strings mal ajustés, ils foncent vers la présentation ultime !\u003c/p\u003e\n\u003cp dir=\"ltr\"\u003e\u003cstrong\u003eUn spectacle ridiculement sexy... et sérieusement divertissant!\u003c/strong\u003e\u003cbr /\u003e\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_ladies-night-2025#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/ladies-night-2025/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:18:59.216-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/ladies-night-2025/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_ladies-night-2025#Offer_0","http://schema.org/price":[{"@value":"68.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_guy-nantel-bienveillant","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Bienveillant"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Guy-Nantel.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2027-02-27T20:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/guy-nantel-bienveillant/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/guy-nantel-bienveillant/"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eAlors que l’actualité regorge de sujets explosifs, d’opinions tranchées et de contradictions bien de chez nous, l’humoriste Guy Nantel revient avec un tout nouveau spectacle au titre pour le moins intrigant : \u003cem\u003eBienveillant.\u003c/em\u003e\u003c/p\u003e\n\u003cp\u003eOui, vous avez bien lu. BIENVEILLANT. Le mot qu’on associe le moins spontanément à un humoriste reconnu pour son franc-parler, son regard critique et son humour sociopolitique sans compromis. Disons que le titre donne le ton! Depuis toujours, Nantel est cette voix différente qui ose dire tout haut ce que tout le monde pense tout bas. Avec sa lucidité mordante et son ironie bien placée, il met le doigt sur nos travers collectifs, nos incohérences et nos grands débats d’époque, tout ça au grand plaisir du public. Avec ce 7e spectacle en carrière, Guy Nantel continue de faire ce qu’il fait le mieux : nous dérouter, nous désarmer et surtout nous faire rire à gorge déployée!\u003c/p\u003e\n\u003cp\u003eMais la question demeure : comment s’y prend-il pour le faire avec bienveillance? On ne demande qu’à voir… mais surtout à entendre. Avouez que ça promet!\u003c/p\u003e\n\n\n\n\n\n\n\n\n\u003cp\u003e\u003cem\u003e \u003cbr /\u003e\u003c/em\u003e\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_guy-nantel-bienveillant#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/guy-nantel-bienveillant/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:20:35.973-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/guy-nantel-bienveillant/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_guy-nantel-bienveillant#Offer_0","http://schema.org/price":[{"@value":"52.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_olivier-martineau-cest-pas-moi-cest-toi","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"C'est pas moi, c'est toi"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-06T20:00:00-05:00"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Olivier-Martineau.jpg"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/olivier-martineau-cest-pas-moi-cest-toi/"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/olivier-martineau-cest-pas-moi-cest-toi/#WebPage"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp dir=\"ltr\"\u003eSociété, c’est fini.\u003c/p\u003e\n\u003cp dir=\"ltr\"\u003eOui, on a eu de beaux moments, mais là, c’est trop. T’as changé. Moi, je m’en vais. Je te laisse tes règles farfelues et tes promesses en plastique. J’en ai assez de cette mascarade.\u003c/p\u003e\n\u003cp dir=\"ltr\"\u003e\u003cstrong\u003eC’EST PAS MOI, C’EST TOI\u003c/strong\u003e, c’est du stand-up pur et dur, un véritable feu roulant de gags où chaque punch frappe en plein cœur. Un regard sans filtre, mordant, mais surtout hilarant, sur une société où la rupture était inévitable.\u003c/p\u003e\n\u003cp dir=\"ltr\"\u003eAprès trois spectacles, celui-ci est le plus audacieux, le plus osé, le plus authentique.\u003c/p\u003e\n\u003cp dir=\"ltr\"\u003eBref, c’est la fin d’une histoire… et le début d’un show qui décoiffe. Mais moi, je m’en fous, j’ai pas de cheveux.\u003cbr /\u003e\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_olivier-martineau-cest-pas-moi-cest-toi#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/olivier-martineau-cest-pas-moi-cest-toi/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:13:24.229-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/olivier-martineau-cest-pas-moi-cest-toi/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_olivier-martineau-cest-pas-moi-cest-toi#Offer_0","http://schema.org/price":[{"@value":"45.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_mehdi-bousaidan-le-garcon-et-la-fenetre","@type":["http://schema.org/Event"],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Mehdi-Bousaidan-2.jpg"}],"http://schema.org/name":[{"@language":"fr","@value":"Le garçon et la fenêtre"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-09T19:30:00-04:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/mehdi-bousaidan-le-garcon-et-la-fenetre/"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/mehdi-bousaidan-le-garcon-et-la-fenetre/#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003c/p\u003e\n\u003cp dir=\"ltr\"\u003eDans son tout nouveau spectacle, \u003cstrong\u003eMehdi Bousaidan\u003c/strong\u003e se dévoile comme jamais auparavant. Avec ce troisième one-man show, il choisit de faire une introspection sur sa vie et nous en partage une analyse intime avec l’humour efficace qu’on lui connaît. Ce spectacle est une invitation à jeter un œil à travers la fenêtre de sa vie. C’est un voyage où l’on avance de sa tendre enfance à l’homme qu’il est aujourd’hui en se rapprochant un peu plus de sa réalité. \u003c/p\u003e\n\u003cp dir=\"ltr\"\u003eÀ travers des anecdotes surprenantes, il brouille la frontière entre l’histoire personnelle et l’universel. Il aborde aussi des questions existentielles, comme le mariage, les relations familiales, les peurs et les craintes ainsi que les expériences qui forgent l'âme. \u003c/p\u003e\n\u003cp dir=\"ltr\"\u003eC’est un Mehdi qu’on reconnaît, certes, de son sens critique aiguisé, de ses analyses inhabituelles et de ses punchs inattendus, mais c’est aussi un Mehdi qu’on apprend à connaître sous un nouveau visage. Il nous ouvre le livre de sa vie privée pour une toute première fois dans un humour toujours aussi rafraîchissant.\u003cbr /\u003e\u003c/p\u003e\n\u003cp\u003e\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_mehdi-bousaidan-le-garcon-et-la-fenetre#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/mehdi-bousaidan-le-garcon-et-la-fenetre/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:14:36.598-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/mehdi-bousaidan-le-garcon-et-la-fenetre/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_mehdi-bousaidan-le-garcon-et-la-fenetre#Offer_0","http://schema.org/price":[{"@value":"43.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_menopause","@type":["http://schema.org/Event"],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/keywords":[{"@value":"[\"Comédie musicale\", \"Spectacle en salle\"]"}],"http://schema.org/name":[{"@language":"fr","@value":"Ménopause"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/watch?v=S1Y8Kijr80A"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/menopause/"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/menopause/#WebPage"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-07T20:00:00-05:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/5b8f9e4a-cd16-45ae-aa69-8f9dc30c77d9"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/M%C3%A9nopause-2.jpg"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003ch4\u003eMénopause, le succès mondial qui défie les règles!\u003cbr /\u003e\u003c/h4\u003e\n\u003cp\u003eLa célèbre parodie musicale Ménopause, présentée par Vitoli, arrive pour la toute première fois au Québec! À l’affiche depuis plus de 15 ans à Las Vegas, triomphant à Paris depuis 2 ans et ayant été applaudi par près de 17 millions de personnes dans 16 pays, ce spectacle festif pose un regard hilarant et sincère sur ce que les femmes vivent lorsqu’elles traversent la ménopause. Mettant en vedette les talentueuses\u003cstrong\u003e Guylaine Tanguay\u003c/strong\u003e, \u003cstrong\u003eClaudine Mercier\u003c/strong\u003e, \u003cstrong\u003eGeneviève Charest\u003c/strong\u003e et \u003cstrong\u003eCatherine Sénart\u003c/strong\u003e dans une mise en scène de \u003cstrong\u003eAlexis Pitkevicht\u003c/strong\u003e, Ménopause, la parodie musicale vous fera rire aux éclats, mais aussi chanter et danser sur des parodies de grands succès musicaux anglophones des années 60, 70 et 80 que vous adorez et dont l’adaptation francophone humoristique est signée par la prolifique Danièle Lorain. \u003cbr /\u003e\u003cbr /\u003ePertes de mémoire, bouffées de chaleur, sueurs nocturnes, vie sexuelle inerte… ou trop active! Lorsque quatre femmes venant de milieux très différents se rencontrent par hasard dans une vente de lingerie, elles se rendent comptent qu’elles partagent au moins une chose : la ménopause. Après tout, les femmes ménopausées sont toutes dans le même bain… mieux vaut en rire et transformer ce sujet tabou en une célébration remplie de légèreté et de sororité! \u003cbr /\u003e\u003cstrong\u003e\u003cbr /\u003ePsst : Les hommes sont également les bienvenus, alors qu’ils pourront enfin mieux comprendre ce que vivent les femmes de leur vie… tout en riant avec elles!\u003c/strong\u003e\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_menopause#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/menopause/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:15:52.760-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/menopause/"}]},{"@id":"http://kg.footlight.io/resource/5b8f9e4a-cd16-45ae-aa69-8f9dc30c77d9","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Comédie musicale"}],"http://www.w3.org/2004/02/skos/core#closeMatch":[{"@id":"http://kg.artsdata.ca/resource/MusicalComedyPerformance"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_menopause#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"70.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_louis-t-male-alpha","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/louis-t-male-alpha/"}],"http://schema.org/name":[{"@language":"fr","@value":"Mâle Alpha"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-27T20:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/louis-t-male-alpha/#WebPage"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Louis-T_2024.jpg"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-213"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eQuand on pense à \u003cstrong\u003eLouis T\u003c/strong\u003e, on se dit : domination, charisme, puissance, virilité. Non ? Tant pis ! Louis T revient quand même sur scène avec son troisième one-man-show intitulé \u003cem\u003e\u003cstrong\u003eMâle Alpha\u003c/strong\u003e\u003c/em\u003e. \u003c/p\u003e\n\u003cp\u003eDans ce troisième one-man-show qui se veut plus humain, Louis T est tanné de se cacher et n’a plus peur de le dire: il aime les hommes! Que ce soit ses deux petits garçons, ses amis, son père ou son grand-père, il arrive même à avoir de la sympathie pour ceux qui l’insultent sur Twitter, pour vrai. Il demeure réaliste: les hommes ne sont pas toujours faciles à aimer. C’est donc avec un regard bienveillant et drôle sur la masculinité que Louis T nous fait réaliser qu’en s’y attardant suffisamment, on trouve un petit côté attachant à leurs niaiseries et leurs comportements. \u003c/p\u003e\n\u003cp\u003eRassurez-vous, Louis T continue d’aimer les femmes (particulièrement sa blonde), considérant que ce sont elles, en majorité, qui achètent les billets de spectacle d’humour! \u003c/p\u003e\n\u003cp\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca class=\"notice\"\u003eSuivre l'artiste\u003cbr /\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://www.tiktok.com/@louisthumoriste\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eFacebook\u003c/a\u003e \u003ca href=\"https://www.tiktok.com/@louisthumoriste\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eInstagram\u003c/a\u003e \u003ca href=\"https://www.tiktok.com/@louisthumoriste\" class=\"button btn-primary\" target=\"_blank\" rel=\"noreferrer noopener\"\u003eTikTok\u003c/a\u003e\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_louis-t-male-alpha#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/louis-t-male-alpha/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:16:13.462-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/louis-t-male-alpha/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_louis-t-male-alpha#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"43.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_philippe-laprise-au-sommet","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/philippe-laprise-au-sommet/"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Philippe-Laprise-2.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-15T19:30:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/philippe-laprise-au-sommet/#WebPage"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/name":[{"@language":"fr","@value":"Au sommet"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cem\u003eCette année est bien remplie pour Philippe Laprise! Il lance son 5e one-man-show en 25 \u003c/em\u003e\u003cem\u003eans de carrière tout en soulignant ses 50 ans! Si on ajoute à ça le fait qu’il est père de 3 \u003c/em\u003e\u003cem\u003eenfants et d’un chien, co-propriétaire d’une équipe de baseball, en couple depuis près de \u003c/em\u003e\u003cem\u003e25 ans et qu’il a presque gagné (mais pas tant que ça) Sortez-moi d’ici et LOL : Qui rira \u003c/em\u003e\u003cem\u003ele dernier?, Philippe a vraiment dépassé les attentes de ses proches… et les siennes!\u003c/em\u003e\u003c/p\u003e\n\n\n\n\n\u003cp\u003e\u003cem\u003eDans ce spectacle solo, Philippe se questionne sur ce que c’est réellement être au \u003c/em\u003e\u003cem\u003esommet à notre époque où tout évolue rapidement. Maintenant rendu à un âge \u003c/em\u003e\u003cem\u003e« presque » vénérable, Philippe s’assume et n’a plus rien à perdre. Il nous présente donc \u003c/em\u003e\u003cem\u003eAu sommet, un spectacle sans filtre mais toujours avec une bonne dose d’autodérision. \u003c/em\u003e\u003cem\u003eLa route vers le sommet est longue, surtout quand l’intérieur des cuisses frotte ensemble,\u003c/em\u003e\u003cem\u003ealors partons tous maintenant et suivons Philippe au sommet!\u003c/em\u003e\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_philippe-laprise-au-sommet#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/philippe-laprise-au-sommet/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:20:14.283-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/philippe-laprise-au-sommet/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_philippe-laprise-au-sommet#Offer_0","http://schema.org/price":[{"@value":"51.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_silvi-tourigny-en-feu","@type":["http://schema.org/Event"],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-11T19:30:00-05:00"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"En feu"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Silvi-Tourigny_2025.jpg"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/watch?v=u3mhhvw24O8"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/silvi-tourigny-en-feu/"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/silvi-tourigny-en-feu/#WebPage"}],"http://schema.org/sameAs":[{"@value":"https://www.facebook.com/silvitourignyhumoriste"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eL’humoriste \u003cstrong\u003eSilvi Tourigny\u003c/strong\u003e s'apprête à brûler les planches avec son premier spectacle solo, \u003cem\u003eEn feu\u003c/em\u003e! Ce one-woman-show est une plongée hilarante dans son vécu, mais surtout dans ses envies, celles d’une femme libérée et assumée. Au cours des dernières années, il s'en est passé des choses dans sa vie : une maison, un enfant, une moto, une séparation, une autre maison, des rencontres douteuses, une autre relation et c’est sans compter sa passion beaucoup trop intense pour les animaux! Silvi et sa fidèle acolyte, Carole, sont plus enflammées que jamais et prêtes à non seulement vous faire rire, mais également à vous donner le goût de danser! Avec son naturel désarmant, la rouquine fait souffler un vent d'énergie et d'humour qui séduit à grands éclats de rire. \u003c/p\u003e\n\u003cp\u003eComme dirait Carole :\u003c/p\u003e\n\u003cblockquote\u003ePas besoin de t’avertir que tu vas avoir du fun parce que Silvi est EN FEU!!!\u003c/blockquote\u003e\n\u003cp\u003e\u003cbr /\u003e\u003c/p\u003e\n\u003ca class=\"notice\"\u003eSuivre l'artiste\u003c/a\u003e\n\u003cp\u003e\u003ca href=\"https://www.facebook.com/silvitourignyhumoriste\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eFacebook\u003c/a\u003e \u003ca href=\"https://www.instagram.com/silvitourigny/\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eInstagram\u003c/a\u003e \u003ca href=\"https://www.youtube.com/c/SilviTourigny\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eYouTube\u003c/a\u003e\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_silvi-tourigny-en-feu#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/silvi-tourigny-en-feu/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:16:22.152-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/silvi-tourigny-en-feu/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_silvi-tourigny-en-feu#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"39.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_william-cloutier-lamour","@type":["http://schema.org/Event"],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Wiliam-Cloutier.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/william-cloutier-lamour/#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-27T15:00:00-04:00"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/name":[{"@language":"fr","@value":"L'amour"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/william-cloutier-lamour/"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eEntre la folle aventure \u003cstrong\u003eMixmania\u003c/strong\u003e et sa performance mémorable dans \u003cstrong\u003eStarmania\u003c/strong\u003e, 10 ans plus tard, ce qui est sûr, c’est que le gagnant de \u003cstrong\u003eStar Académie 2021\u003c/strong\u003ea su s’imposer dans le paysage de la musique québécoise. En formule trio – accompagné d’Antoine Tousignant à la guitare et de Marie-Anne Tessier au drum – il interprète ses chansons avec une énergie brute et une proximité désarmante.\u003c/p\u003e\n\u003cp\u003eDes plus grands succès de son album \u003cem\u003eOn ira\u003c/em\u003e (plus de 5 millions d’écoutes) aux titres plus récents de son opus \u003cem\u003eL’amour\u003c/em\u003e (parution en mars 2025), William livre un spectacle vibrant, porté par une voix exceptionnelle. Une formule épurée, mais chargée d’émotion, où chaque chanson devient un moment de partage sincère.\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_william-cloutier-lamour#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/william-cloutier-lamour/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:10:49.860-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/william-cloutier-lamour/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_william-cloutier-lamour#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"45.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_luc-langevin-lillusionniste","http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/keywords":[{"@value":"[\"Magie\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/75fd4d7c-18ad-4560-818a-42232b15137c"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/luc-langevin-lillusionniste/#WebPage"}],"http://schema.org/name":[{"@language":"fr","@value":"L'illusionniste"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-21T20:00:00-04:00"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Lun-Langevin-2025.jpg"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/luc-langevin-lillusionniste/"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003ch4\u003eLà où l’impossible prend vie.\u003c/h4\u003e\n\u003cp\u003eLuc Langevin est enfin de retour avec un tout nouveau spectacle à grand déploiement. Au sommet de son art et en parfaite maîtrise de son talent unique, il exécute avec finesse et précision des numéros de grandes illusions, créés sur mesure avec son complice de toujours, le magicien Stéphane Bourgoin. Dans un écrin de lumière où la magie opère, les effets sont époustouflants et touchants à la fois, la musique nous envoûte et nous transporte.\u003c/p\u003e\n\u003cp\u003eLe quatrième spectacle de l’illusionniste frappe l’imaginaire. Il explore la dualité entre la tête et le cœur, nous invitant dans un monde fabriqué de toutes pièces pour fuir le réel.\u003c/p\u003e\n\u003cp\u003eUn spectacle étonnant qu’il présentera dans toute la Francophonie et un peu partout à travers le monde au cours des prochaines années.\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_luc-langevin-lillusionniste#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-22T15:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_luc-langevin-lillusionniste#2026-03-21T200000-0400"},{"@id":"http://kg.footlight.io/resource/co-motion-ca_luc-langevin-lillusionniste#2026-03-22T150000-0400"}]},{"@id":"http://kg.footlight.io/resource/75fd4d7c-18ad-4560-818a-42232b15137c","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Magie"}],"http://www.w3.org/2004/02/skos/core#broadMatch":[{"@id":"http://kg.artsdata.ca/resource/Event"}]},{"@id":"https://co-motion.ca/spectacles/luc-langevin-lillusionniste/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:20:46.238-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/luc-langevin-lillusionniste/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_luc-langevin-lillusionniste#Offer_0","http://schema.org/price":[{"@value":"67.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_luc-langevin-lillusionniste#2026-03-21T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"L'illusionniste"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-21T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_luc-langevin-lillusionniste#2026-03-22T150000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"L'illusionniste"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-22T15:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_dej-croissant-musique-felix-et-georges","@type":["http://schema.org/Event"],"http://schema.org/keywords":[{"@value":"[\"Musique classique\", \"Spectacle en salle\"]"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/dej-croissant-musique-felix-et-georges/#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/MDA/Dej-croissant/Julien_Juliette_janvf%C3%A9v-2026.jpg"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/cfd110d3-63b4-45f7-8772-97609987b338"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/f56c4b49-240a-447d-8212-5f0c6333e42a"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/dej-croissant-musique-felix-et-georges/"}],"http://schema.org/name":[{"@language":"fr","@value":"Déjeuners Croissant-Musique"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:00:00-05:00"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003ch5\u003eJulien Patenaude (baryton et narrateur) et Juliette Duguay (harpe et artiste visuelle)\u003c/h5\u003e\n\u003cp\u003eDes chansons connues et moins connues de Félix Leclerc et Georges Brassens entremêlées d’anecdotes humoristiques.\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_dej-croissant-musique-felix-et-georges#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/dej-croissant-musique-felix-et-georges/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-30T10:19:02.520-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/dej-croissant-musique-felix-et-georges/"}]},{"@id":"http://kg.footlight.io/resource/cfd110d3-63b4-45f7-8772-97609987b338","@type":["http://schema.org/Place"],"http://schema.org/name":[{"@language":"fr","@value":"Studio de la MDA"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"art_gallery, point_of_interest, establishment at 1395 Boul. de la Concorde O., Laval, QC H7N 5W1, Canada"}],"http://schema.org/address":[{"@id":"http://kg.footlight.io/resource/cfd110d3-63b4-45f7-8772-97609987b338#PostalAddress"}],"http://schema.org/longitude":[{"@value":"-73.716391"}],"http://schema.org/latitude":[{"@value":"45.55835649999999"}],"http://schema.org/sameAs":[{"@value":"https://maps.google.com/?cid=4694425902250871743"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_dej-croissant-musique-felix-et-georges#Offer_0","http://schema.org/price":[{"@value":"33.35"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/cfd110d3-63b4-45f7-8772-97609987b338#PostalAddress","@type":["http://schema.org/PostalAddress"],"http://schema.org/streetAddress":[{"@language":"fr","@value":"1395 Boul. de la Concorde O."}],"http://schema.org/postalCode":[{"@value":"H7N 5W1"}],"http://schema.org/addressLocality":[{"@language":"fr","@value":"Laval"}],"http://schema.org/addressRegion":[{"@language":"fr","@value":"QC"}],"http://schema.org/addressCountry":[{"@language":"fr","@value":"CA"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_les-grands-explorateurs-perou","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Les Grands Explorateurs"}],"http://schema.org/keywords":[{"@value":"[\"Documentaire\", \"Spectacle en salle\"]"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/les-grands-explorateurs-perou/#WebPage"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K10-427"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Les-Grands-Explorateurs/2025-2026/LGE_2526_P%C3%A9rou_756x1100.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K10-427"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/les-grands-explorateurs-perou/"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-11T13:30:00-05:00"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eAndré Maurice nous emmène dans la Cordillère des Andes et ses parties basses, pour nous livrer ses richesses, tant ethniques que géographiques et historiques.\u003c/p\u003e\n\u003cp\u003eC’est dans la communauté de Patakancha que nous commencerons notre voyage à travers l’espace et le temps : site de Moray, Montagne des 7 couleurs et Salines de Maras.\u003c/p\u003e\n\u003cp\u003eVous découvrirez, Cusco, la capitale de l’empire inca, qui brille par ses vestiges et ses fêtes. Puis nous partirons pour le Machu Picchu, merveille de l’architecture inca par excellence.\u003c/p\u003e\n\u003cp\u003eNous partirons ensuite dans la forêt tropicale, où l’on cultive le cacao, le thé et le café.\u003c/p\u003e\n\u003cp\u003eLa deuxième partie s’amorce par Trujillo, où la marinera, danse de multiples influences, s’imposera à nous.\u003c/p\u003e\n\u003cp\u003eYungay, point de départ pour explorer la Cordillère Blanche, tremble encore aujourd’hui de l’avalanche de boue du Huascarán qui l’a enfouie sous terre en 1970.\u003c/p\u003e\n\u003cp\u003eLima, capitale du Pérou, est un important centre culturel, la fête nationale y est célébrée en juillet. Et si nous descendons vers le sud, l’Oasis de Huacachina nous fascinera, ainsi que les fameuses Lignes de Nazca.\u003c/p\u003e\n\u003cp\u003eArequipa, la deuxième du pays, est un passage obligé pour accéder au Canyon de Colca, où les vigognes font partie du paysage et où le Mirador des Condors nous offrira un point de vue extraordinaire.\u003c/p\u003e"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/5e5310d0-73e4-4421-8401-75c6c4aaf047"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_les-grands-explorateurs-perou#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/les-grands-explorateurs-perou/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-10T10:11:29.684-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/les-grands-explorateurs-perou/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_les-grands-explorateurs-perou#Offer_0","http://schema.org/price":[{"@value":"23.60"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_boucar-diouf-trois-predateurs-et-un-bungalow","@type":["http://schema.org/Event"],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/boucar-diouf-trois-predateurs-et-un-bungalow/"}],"http://schema.org/name":[{"@language":"fr","@value":"Trois prédateurs et un bungalow"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-29T19:30:00-04:00"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Boucar-Diouf.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/boucar-diouf-trois-predateurs-et-un-bungalow/#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eNos bungalows sont le théâtre d'une cohabitation singulière entre trois prédateurs, réunis autour d'un frigidaire. Si l'humain et le chien cherchent naturellement la compagnie des autres, le chat tend à être plus solitaire. Comme l'a si bien illustré Ira Lewis : « Le chien pense : ils me nourrissent, ils me protègent, ils doivent être des dieux. Le chat, lui, pense : ils me nourrissent, ils me protègent, je dois être un dieu. » Ce spectacle propose un mélange habile de biologie, d'histoire et de culture afin de raconter, avec humour et sensibilité, la présence du chien et du chat auprès de l'humain, une histoire intimement liée à notre humanité. Un spectacle drôle, instructif et touchant. \u003cbr /\u003e\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_boucar-diouf-trois-predateurs-et-un-bungalow#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/boucar-diouf-trois-predateurs-et-un-bungalow/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:15:57.216-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/boucar-diouf-trois-predateurs-et-un-bungalow/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_boucar-diouf-trois-predateurs-et-un-bungalow#Offer_0","http://schema.org/price":[{"@value":"59.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_les-39-marches","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/les-39-marches/"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-05T19:30:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/les-39-marches/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/keywords":[{"@value":"[\"Théâtre\", \"Spectacle en salle\"]"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/39-MARCHES_MEV_LAVAL-756x1100-PHOTO.jpg"}],"http://schema.org/name":[{"@language":"fr","@value":"Les 39 Marches"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003ch5\u003eAvec Les 39 marches, le meurtre n’a jamais été aussi drôle!\u003c/h5\u003e\n\u003cp\u003eLes 39 marches, la comédie policière des plus primées à travers le monde, prend l’affiche au Québec avec en vedette \u003cstrong\u003eBenoît Brière, Martin Drainville, Luc Guérin et Évelyne Rompré\u003c/strong\u003e. Adaptée à l’origine par Patrick Barlow et tirée du roman de John Buchan et du classique film à suspense The 39 Steps d’Alfred Hitchcock, cette satire hilarante met en scène des dizaines de personnages, permettant au génie comique et au jeu physique des quatre acteurs de se déployer sous nos yeux.\u003c/p\u003e\n\u003cp\u003eCette délirante intrigue d’espionnage raconte l’histoire de Richard Hannay, un gentleman faussement accusé du meurtre d’une femme, forcé de partir dans une incroyable cavale à travers l’Angleterre. Pendant cette chasse à l’homme aussi captivante que désopilante, Hannay devra aussi tenter de décoder un mystérieux secret pour sauver son pays. Il s’agit d’un spectacle pour un public de tous âges, alors que cette histoire aussi riche en rebondissements qu’en divertissement tient en haleine jusqu’à la toute fin, en plus de faire rire et d’éblouir par les prouesses scéniques permettant de créer autant de lieux et de personnages. \u003c/p\u003e\n\u003cp\u003eDans un savant mélange de mystère, d’action et d’humour, Les 39 marches est la pièce humoristique que vous souhaitez absolument voir! \u003cbr /\u003e\u003c/p\u003e\n\u003cp\u003e\u003csub\u003e\u003ca class=\"notice\"\u003e\u003cbr /\u003e\u003c/a\u003e\u003c/sub\u003e\u003c/p\u003e\n\u003cp\u003e\u003csub\u003e\u003ca class=\"notice\"\u003e\u003c/a\u003eCe spectacle contient des effets lumineux incluant des effets stroboscopiques susceptibles d’incommoder certains spectateurs.\u003c/sub\u003e\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_les-39-marches#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/les-39-marches/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:15:24.499-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/les-39-marches/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_les-39-marches#Offer_0","http://schema.org/price":[{"@value":"68.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_petits-bonheurs-deja-au-debut","@type":["http://schema.org/Event"],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/name":[{"@language":"fr","@value":"Petits Bonheurs"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/1018342897"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/petits-bonheurs-deja-au-debut/"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-77"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/MDA/Programmation-ville/Jeune-Public/2025-2026/D%C3%A9j%C3%A0-au-d%C3%A9but%E2%80%A6.jpg"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/22f50ba1-3403-48a8-a279-460952d3c134"},{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"},{"@id":"http://kg.footlight.io/resource/2fa05002-967b-43c2-b5cb-fc93a25edcb1"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003csub\u003eL’Arrière Scène \u003c/sub\u003e\u003c/p\u003e\u003ch3\u003eEst-ce qu’on se souvient de son tout début?\u003c/h3\u003e\n\u003cp\u003eUne petite créature curieuse entre dans un espace formé de cylindres de carton. Au rythme des battements de cœurs et de douces mélodies, elle explore son environnement. Son imagination fait naître une panoplie de lieux poétiques : une forêt qui se déploie, des algues valsant sous l’eau, une ville tonitruante, la cime d’une montagne... Puis, le jour se lève, c’est le temps de sortir de sa cachette!\u003c/p\u003e\n\u003cp\u003e\u003cem\u003eDéjà, au début\u003c/em\u003e... évoque les splendeurs du monde avec ses projections vidéo colorées et sa scénographie riche en formes et textures. Ce spectacle sans paroles est une expérience enveloppante. Bercés par ce délicieux voyage pour les sens, les enfants vivront pleinement la magie du théâtre, pour la première fois peut-être, mais sûrement pas la dernière...\u003c/p\u003e\n\u003cp\u003eAprès plus de 200 représentations à travers le monde depuis sa création, \u003cstrong\u003eL’Arrière Scène\u003c/strong\u003e célèbre le 10\u003csup\u003ee\u003c/sup\u003eanniversaire du spectacle en proposant une nouvelle mouture pour de nouveaux publics.\u003c/p\u003e\n\u003cp\u003eDurée : 40 minutes \u003c/p\u003e\n\u003cp\u003e\u003ca href=\"http://arrierescene.qc.ca\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eSite Web de L’Arrière Scène \u003c/a\u003e\u003c/p\u003e"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-09T11:00:00-04:00"}],"http://schema.org/keywords":[{"@value":"[\"Jeune public\", \"Théâtre\", \"Danse\", \"Spectacle en salle\"]"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/petits-bonheurs-deja-au-debut/#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_petits-bonheurs-deja-au-debut#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/petits-bonheurs-deja-au-debut/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:12:35.069-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/petits-bonheurs-deja-au-debut/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_petits-bonheurs-deja-au-debut#Offer_0","http://schema.org/price":[{"@value":"17.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_avec-le-temps-dalida-par-joan-bluteau","@type":["http://schema.org/Event"],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K2-2423"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-19T20:00:00-04:00"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-213"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/avec-le-temps-dalida-par-joan-bluteau/#WebPage"}],"http://schema.org/keywords":[{"@value":"[\"Spectacle hommage\", \"Spectacle en salle\"]"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/avec-le-temps-dalida-par-joan-bluteau/"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/8f6b3be1-6023-4ad2-aadb-a259194b9a33"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Locations-TMC/dALIDA.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eAvec le temps… Dalida, c’est plus de 20 chansons que livre la chanteusecomédienne entourée de quatre musiciens, enfilant plusieurs tenues inspirées de la star ! « Nous proposons un nouveau rendez-vous pour célébrer Dalida et son œuvre, rythmé par toutes ces chansons indémodables\u003c/p\u003e"}],"http://schema.org/name":[{"@language":"fr","@value":"Avec le temps... Dalida par Joan Bluteau"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_avec-le-temps-dalida-par-joan-bluteau#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/avec-le-temps-dalida-par-joan-bluteau/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:12:51.878-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/avec-le-temps-dalida-par-joan-bluteau/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_avec-le-temps-dalida-par-joan-bluteau#Offer_0","http://schema.org/price":[{"@value":"53.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_emilieclaire-barlow","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Emilie-Claire Barlow"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-48"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-08T15:00:00-04:00"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Jazz / Blues\", \"Spectacle en salle\"]"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Emilie-claire-Barlow.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/emilieclaire-barlow/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eLaissez-vous emporter par Emilie-Claire Barlow et son groupe exceptionnel pour une soirée de grands classiques, de bossa novas rythmées, de ballades élégantes et de chanson française, le tout interprété dans son style distinctif. Dans ce nouveau spectacle, elle présente quelques-uns de ses coups de cœur de La plus belle saison, son plus récent projet, une lettre d'amour au Québec et la bande sonore d'un nouveau chapitre de sa vie. \u003c/p\u003e\n\u003cp\u003eLa générosité d’Emilie-Claire sur scène et sa complicité remarquable avec ses musiciens font de chaque représentation une expérience inoubliable. Ne manquez pas cette occasion unique de découvrir (ou redécouvrir) cette artiste brillante et charmante, dont la voix d’une beauté intemporelle touche à l’essentiel. \u003c/p\u003e\n\u003cp\u003eEmilie-Claire Barlow n’est pas qu’une chanteuse d’exception, elle est aussi une arrangeuse talentueuse\u003cbr /\u003eet une productrice aguerrie. Avec plus de 500 millions d’écoutes en ligne et une impressionnante\u003cbr /\u003ediscographie de 14 albums, elle a marqué de façon significative la scène musicale canadienne, récoltant de nombreux prix, dont un Félix et deux JUNO Awards.\u003c/p\u003e"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"},{"@id":"http://kg.footlight.io/resource/9da661fe-089b-4061-a274-ac91dfdc45c8"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/emilieclaire-barlow/"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_emilieclaire-barlow#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/emilieclaire-barlow/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:14:45.592-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/emilieclaire-barlow/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_emilieclaire-barlow#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"45.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_jo-cormier-machine","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Jo-Cormier-3.jpg"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/jo-cormier-machine/"}],"http://schema.org/name":[{"@language":"fr","@value":"Machine"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-23T20:00:00-04:00"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/jo-cormier-machine/#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003ch4\u003eL’incomparable humoriste Jo Cormier débarque avec son deuxième one-man-show : \u003cem\u003eMACHINE\u003c/em\u003e.\u003c/h4\u003e\n\u003cp\u003eAlors que dans son premier spectacle il comparait l’humain à un animal, cette fois-ci il pousse la réflexion sur l’être humain encore plus loin, toujours avec son regard unique. Ou comme dirait Jo : un regard donné par un genre de gars fin trentaine, la bédaine, qui pense avoir 23 ans mentalement, mais qui en a 4 en réalité et qui aime regarder le soleil le plus longtemps possible.\u003c/p\u003e\n\u003cp\u003eAvec la technologie, on optimise nos vies comme si on était des ordinateurs, mais est-ce qu’on se demande encore c’est quoi, être un humain? Dans ce spectacle plus personnel et plus honnête, Jo se montre tout aussi vulnérable que drôle alors qu’il se questionne à savoir si oui ou non il est humain. \u003c/p\u003e\n\u003cp\u003e\u003cem\u003eMACHINE\u003c/em\u003e est un spectacle simple, amusant, nono.\u003cbr /\u003eUn spectacle où personne n’a besoin d’être parfait, seulement humain.\u003c/p\u003e"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_jo-cormier-machine#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/jo-cormier-machine/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:17:43.790-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/jo-cormier-machine/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_jo-cormier-machine#Offer_0","http://schema.org/price":[{"@value":"41.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_mentana-les-frolics","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/mentana-les-frolics/"}],"http://schema.org/name":[{"@language":"fr","@value":"Les Frolics"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-21T20:00:00-05:00"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Mentana.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/mentana-les-frolics/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Musique Indie / Folk\", \"Spectacle en salle\"]"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cem\u003e\u003cstrong\u003e\u003c/strong\u003e\u003c/em\u003e\u003cstrong\u003eMentana\u003c/strong\u003e s’articule autour de la voix rocailleuse et de la guitare haletante de Robin-Joël Cool, acadien d’origine au parcours atypique, ainsi que du piano délicat et de la voix angélique de Viviane Audet. La section rythmique, composée du batteur Yannick Parent et du bassiste Erik West-Millette (West Trainz) avance langoureusement sur certaines pistes et charge à toute vapeur sur d’autres. \u003c/p\u003e\n\u003cp\u003e Le nouveau spectacle de Mentana dans lequel s’entremêlent les chansons du nouvel album en français, dont la sortie est prévue pour l’automne 2025, et les chansons des albums précédents, est une véritable épopée ! Aux chansons s’ajoutent les riches et réjouissants récits de Robin-Joël. \u003c/p\u003e\n\u003cp\u003e Le nouveau spectacle de Mentana est à la fois une grande fête et un voyage dépaysant ! \u003c/p\u003e"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"},{"@id":"http://kg.footlight.io/resource/faf18f22-9210-4f54-9198-204eb745a1cc"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_mentana-les-frolics#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/mentana-les-frolics/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:17:48.645-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/mentana-les-frolics/"}]},{"@id":"http://kg.footlight.io/resource/faf18f22-9210-4f54-9198-204eb745a1cc","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Musique Indie / Folk"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_mentana-les-frolics#Offer_0","http://schema.org/price":[{"@value":"44.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_megan-brouillard-chiendent","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/megan-brouillard-chiendent/"}],"http://schema.org/name":[{"@language":"fr","@value":"Chiendent"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/M%C3%A9gan-Brouillard_2024.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eLe chiendent, c’est subjectif. La mauvaise herbe, c’est quelque chose qui pousse là où toi, tu ne voulais pas. Ce qui est du chiendent pour l’un ne l’est peut-être pas pour l’autre et ce qui est du chiendent quelque part, ne l’est pas ailleurs… Mon spectacle tourne autour de cette perspective, des différentes perceptions de chacun et chacune et autour de mes propres perceptions. \u003c/p\u003e\n\n\u003cp\u003eLe premier one-woman-show de Mégan Brouillard est un spectacle mordant et authentique, où Mégan présente elle et les siens, sans filtre et toujours de façon hilarante!\u003c/p\u003e"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2027-01-20T19:30:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/megan-brouillard-chiendent/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/watch?v=3R2ZHcmpbZ4"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_megan-brouillard-chiendent#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/megan-brouillard-chiendent/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:17:53.813-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/megan-brouillard-chiendent/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_megan-brouillard-chiendent#Offer_0","http://schema.org/price":[{"@value":"43.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_marie-elaine-thibert-parlez-moi-delle","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/marie-elaine-thibert-parlez-moi-delle/"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Marie-Helene-Thibert.jpg"}],"http://schema.org/name":[{"@language":"fr","@value":"Parlez-moi d'elle"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2027-04-18T15:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/marie-elaine-thibert-parlez-moi-delle/#WebPage"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Spectacle hommage\", \"Chanson francophone\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"},{"@id":"http://kg.footlight.io/resource/8f6b3be1-6023-4ad2-aadb-a259194b9a33"},{"@id":"http://kg.artsdata.ca/resource/Chansonfrancophone"},{"@id":"http://kg.artsdata.ca/resource/ChansonPerformance"},{"@id":"http://kg.artsdata.ca/resource/Chanson"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003ch4\u003eUn hommage éblouissant à Dalida \u003c/h4\u003e\n\u003cp\u003ePour donner suite à la sortie de son plus récent album « \u003cstrong\u003e11 bis, rue d’Orchampt\u003c/strong\u003e » \u003cstrong\u003eMarie-Élaine Thibert \u003c/strong\u003eprésente « \u003cstrong\u003eParlez-moi d’elle\u003c/strong\u003e », un nouveau spectacle à grand déploiement qui promet d’émerveiller son public. Avec des interprétations puissantes, un décor parisien enchanteur et une sélection de chansons intemporelles, ce spectacle promet d'être une expérience unique et une célébration de l'héritage musical de\u003cstrong\u003e Dalida\u003c/strong\u003e. \u003c/p\u003e\u003ch4\u003eUne Interprète touchante \u003c/h4\u003e\n\u003cp\u003e\u003cstrong\u003eMarie-Élaine Thibert\u003c/strong\u003e a su s'imposer comme une figure incontournable de la musique québécoise. Avec sa voix puissante et émotive, elle a touché le cœur de nombreux admirateurs au Québec et ailleurs. Son spectacle « \u003cstrong\u003eParlez-moi d’elle\u003c/strong\u003e » promet de célébrer la légende de \u003cstrong\u003eDalida\u003c/strong\u003e en offrant des interprétations saisissantes des plus grands succès de l'icône. \u003cstrong\u003e\u003cem\u003eJ’attendrai, Gigi l'amoroso, Il venait d'avoir 18 ans, Paroles paroles,\u003c/em\u003e\u003c/strong\u003e \u003cstrong\u003e\u003cem\u003eParlez-moi de lui, Darla dirladada, Besame mucho et bien d'autres.\u003c/em\u003e\u003c/strong\u003e \u003c/p\u003e\u003ch4\u003eUn Voyage à Paris \u003c/h4\u003e\n\u003cp\u003eLe décor du spectacle sera parisien, recréant l’atmosphère envoûtante des rues de Paris, où \u003cstrong\u003eDalida\u003c/strong\u003e a marqué l'histoire de la chanson française. Ce cadre romantique et élégant transportera les spectateurs sur les traces de la légendaire artiste, offrant une expérience immersive et nostalgique. Les spectateurs auront l'impression de se promener dans Montmartre, de flâner sur les boulevards parisiens et de ressentir l'âme et la magie de la Ville lumière.\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_marie-elaine-thibert-parlez-moi-delle#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/marie-elaine-thibert-parlez-moi-delle/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:17:59.602-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/marie-elaine-thibert-parlez-moi-delle/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_marie-elaine-thibert-parlez-moi-delle#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"52.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_simon-gouache-pour-linstant","@type":["http://schema.org/Event"],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/simon-gouache-pour-linstant/#WebPage"}],"http://schema.org/name":[{"@language":"fr","@value":"Pour l'instant"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/simon-gouache-pour-linstant/"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Simon-Gouache.jpg"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-12-11T20:00:00-05:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e« Je vais faire ça, pour l’instant… » C’est ce que Simon Gouache s'est dit quand il s'est lancé en stand-up, il y a maintenant plus de vingt ans. Visiblement, cet instant dure toujours. De ce qui devait être une courte aventure est née une grande passion, qui est au premier plan de ce quatrième spectacle.\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_simon-gouache-pour-linstant#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/simon-gouache-pour-linstant/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:18:49.614-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/simon-gouache-pour-linstant/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_simon-gouache-pour-linstant#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"44.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_patricia-paquin-et-mathieu-gratton-les-ex-separables","@type":["http://schema.org/Event"],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/patricia-paquin-et-mathieu-gratton-les-ex-separables/#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/patricia-paquin-et-mathieu-gratton-les-ex-separables/"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/name":[{"@language":"fr","@value":"ANNULÉ // Les EXséparables"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Les-EXs%C3%A9parables_Paquin-et-Gratton_2025.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-04T20:00:00-04:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eÉcrit par \u003cstrong\u003eMathieu Gratton\u003c/strong\u003e, supervisé par la contrôlante de service \u003cstrong\u003ePatricia Paquin\u003c/strong\u003e et mis en scène par l’unique \u003cstrong\u003eJoël Legendre\u003c/strong\u003e, \u003cstrong\u003e\u003cem\u003eLes EXséparables\u003c/em\u003e\u003c/strong\u003e, avec sa différence et sa fraîcheur, vous étonnera sans doute.\u003c/p\u003e\n\u003cp\u003ePatricia et Mathieu n’ayant pas réussi à régler leurs différends devant le médiateur, c’est donc devant le public québécois qu’ils finiront le travail. Cette confrontation humoristique promet des moments de rire, d’émotion et de vérité, offrant une perspective franche sur les relations entre ex. Joignez-vous à nous pour ces deux soirées exceptionnelles où le talent se mêle à l’authenticité, et où les EXséparables ne manqueront pas de vous surprendre.\u003c/p\u003e\n\u003cp\u003eÇa promet!\u003cbr /\u003e\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_patricia-paquin-et-mathieu-gratton-les-ex-separables#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/patricia-paquin-et-mathieu-gratton-les-ex-separables/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:23:46.811-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/patricia-paquin-et-mathieu-gratton-les-ex-separables/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_patricia-paquin-et-mathieu-gratton-les-ex-separables#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"47.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_prince-panthere","@type":["http://schema.org/Event"],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/prince-panthere/#WebPage"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/prince-panthere/"}],"http://schema.org/name":[{"@language":"fr","@value":"Prince Panthère"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-77"}],"http://schema.org/keywords":[{"@value":"[\"Jeune public\", \"Cabaret onirico-philosophique interactif\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/22f50ba1-3403-48a8-a279-460952d3c134"},{"@id":"http://kg.footlight.io/resource/2970dd0c-06e0-41f4-98fd-6f7d9ae4cb87"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/MDA/Programmation-ville/Jeune-Public/2025-2026/Prince-Panth%C3%A8re.jpg"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-08T14:00:00-05:00"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003csub\u003eLe Petit Théâtre de Sherbrooke\u003c/sub\u003e\u003cbr /\u003e\u003c/p\u003e\n\u003ch3\u003eQue faire avec tout ce qui déborde à l’intérieur de nous?\u003c/h3\u003e\n\u003cp\u003eLe jour où le chat de Christine meurt, un Prince Panthère, tout droit arrivé de son royaume de Panthésia, surgit de sa commode. Qui est-il et que lui veut-il, avec son humour griffu, ses minauderies et ses questions féroces? Est-ce un ami ou un ennemi? Et que penser de son comparse Bonzo, sauvage et indomptable? Ensemble, ils forment un duo de guides aussi flamboyants que chafouins pour… oui, au fait, pourquoi sont-ils là?\u003c/p\u003e\n\u003cp\u003eCabaret onirico-philosophique interactif, le spectacle se construit autour des échanges avec le public : le Prince Panthère interroge les jeunes et veut entendre leurs réponses. Devant un micro, à la manière d’un stand-up, ou exhibant sa longue queue et ses costumes flamboyants dans un décor tenant à la fois de la chambre et de la jungle, le Prince pose tout haut des questions qu’on se pose souvent tout bas.\u003c/p\u003e\n\u003cp\u003eLibrement inspiré du roman graphique \u003cem\u003ePanthère\u003c/em\u003e, de l’artiste flamand Brecht Evens. \u003c/p\u003e\n\u003cp\u003eDepuis plus de 50 ans, \u003cstrong\u003eLe Petit Théâtre de Sherbrooke\u003c/strong\u003e place les mots et la dramaturgie au centre de ses recherches et donne à des équipes multidisciplinaires l’occasion d’expérimenter un dialogue entre les langages musical, visuel et corporel.\u003c/p\u003e\n\u003cp\u003e\u003cem\u003e\u003cstrong\u003ePublic sur la scène, places limitées\u003c/strong\u003e\u003c/em\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eDurée : 60 minutes \u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e\u003cstrong\u003eProfitez de votre venue à la Maison des arts pour visiter les expositions en cours à la \u003ca href=\"https://www.laval.ca/culture/maison-des-arts/expositions-salle-alfred-pellan/expositions/\" target=\"_blank\" rel=\"noreferrer noopener\"\u003eSalle Alfred Pellan\u003c/a\u003e.\u003c/strong\u003e\u003cbr /\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e\u003c/strong\u003e\u003cstrong\u003e\u003cbr /\u003e\u003c/strong\u003e\u003ca href=\"http://petittheatre.qc.ca\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eSite Web du Petit Théâtre de Sherbrooke\u003c/a\u003e \u003ca href=\"https://www.laval.ca/calendrier-activites/prince-panthere-mda-theatre-jeune-public/\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eMaison des arts de Laval\u003c/a\u003e\u003cbr /\u003e\u003c/p\u003e"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/781496852"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_prince-panthere#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/prince-panthere/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:13:19.549-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/prince-panthere/"}]},{"@id":"http://kg.footlight.io/resource/2970dd0c-06e0-41f4-98fd-6f7d9ae4cb87","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Cabaret onirico-philosophique interactif"}],"http://www.w3.org/2004/02/skos/core#broadMatch":[{"@id":"http://kg.artsdata.ca/resource/PerformingArtsEvent"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_prince-panthere#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"20.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_alexandre-barrette-nouveau-spectacle","@type":["http://schema.org/Event"],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/alexandre-barrette-nouveau-spectacle/#WebPage"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Lavalorama/Alexandre-Barrette-756x1100.png"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/alexandre-barrette-nouveau-spectacle/"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-16T19:30:00-04:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/name":[{"@language":"fr","@value":"Ouvert"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eAvec sa vivacité d’esprit, son aisance naturelle et son sens unique de la répartie, \u003cstrong\u003eAlexandre Barrette \u003c/strong\u003es’est taillé une place de choix sur scène, à la radio et à la télévision depuis plus de 20 ans. Récemment, il a animé Sortez-moi d’ici!, La Tour et Incroyables!, en plus de co-animer régulièrement Tout le monde en parle. Vous pouvez aussi l’entendre quotidiennement sur les ondes de WKND à Québec. Alexandre peaufine en ce moment son quatrième spectacle solo qu’il présentera sous peu en tournée au Québec.\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_alexandre-barrette-nouveau-spectacle#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/alexandre-barrette-nouveau-spectacle/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:22:08.628-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/alexandre-barrette-nouveau-spectacle/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_alexandre-barrette-nouveau-spectacle#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"37.35"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_michel-barrette-drole-depuis-1957","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/michel-barrette-drole-depuis-1957/"}],"http://schema.org/name":[{"@language":"fr","@value":"Drôle depuis 1957"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/michel-barrette-drole-depuis-1957/#WebPage"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-10T20:00:00-04:00"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Nouv-siteweb_poster_Spectacle-756-x-1100-px-27.jpg"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eDans son tout nouveau spectacle \u003cem\u003eDrôle depuis 1957\u003c/em\u003e, \u003cstrong\u003eMichel Barrette\u003c/strong\u003e revient en force avec un regard hilarant et tendre sur sa génération. Il y célèbre « la revanche des baby-boomers », ces jeunes d’hier devenus les grands sages d’aujourd’hui… mais toujours aussi irrévérencieux. \u003c/p\u003e\n\u003cp\u003eÀ travers anecdotes savoureuses, souvenirs touchants et observations mordantes, il nous raconte son époque avec une énergie contagieuse. Entre rires et réflexions, Michel prouve qu’on peut vieillir sans jamais cesser d’être drôle. Ce spectacle est une véritable lettre d’amour au passé… et un clin d’œil bien senti au présent.\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_michel-barrette-drole-depuis-1957#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/michel-barrette-drole-depuis-1957/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:21:03.944-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/michel-barrette-drole-depuis-1957/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_michel-barrette-drole-depuis-1957#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"49.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_melanie-couture-ca-change-toute","@type":["http://schema.org/Event"],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-31T19:30:00-04:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/melanie-couture-ca-change-toute/"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/M%C3%A9lanie-Couture_2025.jpg"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/name":[{"@language":"fr","@value":"Ça change toute"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/melanie-couture-ca-change-toute/#WebPage"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eDans ce deuxième spectacle solo, \u003cstrong\u003eMélanie Couture\u003c/strong\u003e vous amènera à voir les choses d’un autre angle. À travers sa paire de lunettes, vous verrez qu’avoir des profs de maths qui se retrouvent avec la charge d’enseigner l’éducation sexuelle, ça change tout\u003cem\u003ee\u003c/em\u003e; que prendre un bain relaxant quand tu as un petit bain et des grosses fesses, ça change tout\u003cem\u003ee\u003c/em\u003e; qu’avoir des enfants quand tu as besoin d’une bonne nuit de sommeil, ça change tout\u003cem\u003ee\u003c/em\u003e; que de ne pas consulter de femme pour écrire un personnage féminin dans un film hollywoodien, ça change tout\u003cem\u003ee\u003c/em\u003e.\u003c/p\u003e\n\u003cp\u003eGrâce à sa façon intelligente d’attaquer les sujets les plus intimes, son rire contagieux, ses propos crus et son talent pour dénouer les tabous, \u003cstrong\u003eMélanie Couture\u003c/strong\u003e nous fait réaliser que parfois c'est long avant que tout change. D’autres fois, ça ne prend qu’un \u003cem\u003ee\u003c/em\u003e.\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_melanie-couture-ca-change-toute#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/melanie-couture-ca-change-toute/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:19:10.779-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/melanie-couture-ca-change-toute/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_melanie-couture-ca-change-toute#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"42.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_la-light-du-borgot","@type":["http://schema.org/Event"],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/la-light-du-borgot/#WebPage"}],"http://schema.org/keywords":[{"@value":"[\"Conte\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/f614a85a-8bc9-4151-8d6b-494849be0eb3"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/o3L28Swp1aw"}],"http://schema.org/name":[{"@language":"fr","@value":"La light du Borgot"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-11T17:00:00-04:00"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-77"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/la-light-du-borgot/"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/MDA/Programmation-ville/Th%C3%A9atre/2025-2026/La-light-du-Borgot.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003ch3\u003eUn phare dans la grisaille.\u003c/h3\u003e\n\u003cp\u003eImaginez la stupéfaction des Madelinots : ils viennent tout juste de se rendre compte que leurs îles adorées ont disparu du globe terrestre, qu’il n’en reste plus trace sur aucune carte! \u003c/p\u003e\n\u003cp\u003eLa light du Borgot est le nom du célèbre phare des Îles-de-la-Madeleine qui sert à guider les bateaux par temps de brume pour les ramener à bon port, mais il sert aussi à ramener sur le droit chemin les âmes égarées. \u003c/p\u003e\n\u003cp\u003eÀ mi-chemin entre le rêve et la réalité, \u003cstrong\u003eCédric Landry\u003c/strong\u003e vous transporte dans un univers de poésie, de rêve et d’humour. Avec son imaginaire fertile jumelé à une parlure qui va droit au cœur, cet artiste charismatique et engagé vous charmera par son authenticité, comme il a conquis le public de la francophonie canadienne et de la France et du Québec. \u003c/p\u003e\n\u003cp\u003eCédric Landry est aussi l’auteur de plusieurs pièces de théâtre publiées chez Dramaturges Éditeurs, dont \u003cem\u003ePierre-Luc à Isaac à Jos\u003c/em\u003e, qui a été en nomination pour le prix Michel-Tremblay pour meilleur texte dramatique. Il a aussi réalisé la série documentaire \u003cem\u003eCapitaines des hauts-fonds\u003c/em\u003e, diffusée à Explora, Ici Radio-Canada et TV5 monde.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eProfitez de votre venue à la Maison des arts pour visiter les expositions en cours à la \u003ca href=\"https://www.laval.ca/culture/maison-des-arts/expositions-salle-alfred-pellan/expositions/\" target=\"_blank\" rel=\"noreferrer noopener\"\u003eSalle Alfred Pellan\u003c/a\u003e.\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eCe spectacle est présenté avec la collaboration de \u003cstrong\u003eCircuits Paroles Vivantes\u003c/strong\u003e.\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://www.laval.ca/calendrier-activites/light-borgot-cedric-landry-mda-conte/\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eMaison des arts de Laval\u003c/a\u003e\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_la-light-du-borgot#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/la-light-du-borgot/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:22:24.192-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/la-light-du-borgot/"}]},{"@id":"http://kg.footlight.io/resource/f614a85a-8bc9-4151-8d6b-494849be0eb3","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Conte"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_la-light-du-borgot#Offer_0","http://schema.org/price":[{"@value":"30.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_rachid-badouri-the-tale-of-the-syrian-dragon","@type":["http://schema.org/Event"],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/rachid-badouri-the-tale-of-the-syrian-dragon/#WebPage"}],"http://schema.org/name":[{"@language":"fr","@value":"The Tale of the Syrian Dragon"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/rachid-badouri-the-tale-of-the-syrian-dragon/"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Rachid-Badouri.jpg"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-20T19:00:00-05:00"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003csub\u003e\u003cem\u003eSpectacle présenté en anglais\u003c/em\u003e\u003c/sub\u003e\u003c/p\u003e\n\u003cp\u003eCelebrated French-Canadian standup-comedian Rachid Badouri, renowned for captivating audiences in French across the globe with three specials and over 1 million tickets sold in 10 countries, is set to make his groundbreaking English debut. \u003c/p\u003e\n\u003cp\u003eKnown for his humor and captivating stage presence, Rachid brings a fresh perspective to stand-up comedy, blending cultural anecdotes with his vibrant energy. \u003c/p\u003e\n\u003cp\u003eWhether you're a long-time fan or new to his work, this show promises to be an engaging and entertaining experience that you won’t want to miss! \u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_rachid-badouri-the-tale-of-the-syrian-dragon#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/rachid-badouri-the-tale-of-the-syrian-dragon/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:12:45.899-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/rachid-badouri-the-tale-of-the-syrian-dragon/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_rachid-badouri-the-tale-of-the-syrian-dragon#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"46.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_tete-a-tete-flute-et-harpe","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/tete-a-tete-flute-et-harpe/"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/tete-a-tete-flute-et-harpe/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/f56c4b49-240a-447d-8212-5f0c6333e42a"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K5-431"},{"@id":"http://kg.artsdata.ca/resource/K10-533"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/OSL/GC-2025-26/OSL_COMOTION_SITE_WEB_CH3.jpg"}],"http://schema.org/location":[{"@id":"https://www.patrimoine-culturel.gouv.qc.ca/rpcq/detail.do?methode=consulter\u0026id=166091\u0026type=bien#Place"}],"http://schema.org/keywords":[{"@value":"[\"Musique classique\", \"Spectacle en salle\", \"Admission générale\"]"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eCe dernier concert des Chambristes vous invite à un voyage empreint de douceur et d'élégance, guidé par un duo flûte et harpe. \u003cstrong\u003eJean-Philippe Tanguay\u003c/strong\u003e à la flûte et \u003cstrong\u003eValérie Milot\u003c/strong\u003e à la harpe vous interpréteront un répertoire varié inspiré de la \u003cstrong\u003emusique impressionniste française\u003c/strong\u003e et de \u003cstrong\u003emélodies japonaises.\u003c/strong\u003e\u003c/p\u003e"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Les Chambristes"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-02T14:00:00-04:00"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_tete-a-tete-flute-et-harpe#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/tete-a-tete-flute-et-harpe/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:17:14.275-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/tete-a-tete-flute-et-harpe/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_tete-a-tete-flute-et-harpe#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"28.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_coucou-passe-partout","http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-03T13:00:00-05:00"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/coucou-passe-partout/"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eAprès l'immense succès télévisuel de l’émission, \u003cstrong\u003ePasse-Partout\u003c/strong\u003e, \u003cstrong\u003ePasse-Carreau \u003c/strong\u003eet \u003cstrong\u003ePasse-Montagne\u003c/strong\u003e sillonneront enfin les routes du Québec à la rencontre de leurs poussinots et poussinettes. Retrouvez-les sur scène dans un spectacle de 50 minutes incluant des jeux participatifs et les chansons populaires de l'émission préférée des tout-petits à Télé-Québec. \u003c/p\u003e\n\u003cp\u003eUn spectacle à la fois divertissant et stimulant pour tous les enfants d'âge préscolaire... et les parents nostalgiques!\u003c/p\u003e\n\u003cp\u003eMise en scène : \u003cstrong\u003eMarc-St Martin\u003cbr /\u003e\u003c/strong\u003eActeurs: \u003cstrong\u003eÉlodie Grenier\u003c/strong\u003e, \u003cstrong\u003eGabrielle Fontaine\u003c/strong\u003e et \u003cstrong\u003eJean-François Pronovost\u003c/strong\u003e\u003c/p\u003e"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/coucou-passe-partout/#WebPage"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Passe-Partout_2025.jpg"}],"http://schema.org/keywords":[{"@value":"[\"Jeune public\", \"Chanson francophone\", \"Théâtre\", \"Spectacle de Noël\", \"Spectacle en salle\"]"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Le spectacle"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/watch?v=C5hKD0WT2f0"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/22f50ba1-3403-48a8-a279-460952d3c134"},{"@id":"http://kg.artsdata.ca/resource/Chansonfrancophone"},{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"},{"@id":"http://kg.footlight.io/resource/095ccae1-e28b-4e1c-b4b2-93d3380e5a62"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_coucou-passe-partout#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-03T15:30:00-05:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_coucou-passe-partout#2026-01-03T130000-0500"},{"@id":"http://kg.footlight.io/resource/co-motion-ca_coucou-passe-partout#2026-01-03T153000-0500"}]},{"@id":"https://co-motion.ca/spectacles/coucou-passe-partout/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-02T10:12:55.185-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/coucou-passe-partout/"}]},{"@id":"http://kg.footlight.io/resource/095ccae1-e28b-4e1c-b4b2-93d3380e5a62","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#broadMatch":[{"@id":"http://kg.artsdata.ca/resource/PerformingArtsEvent"}],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Spectacle de Noël"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_coucou-passe-partout#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"32.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_coucou-passe-partout#2026-01-03T130000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Le spectacle"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-03T13:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_coucou-passe-partout#2026-01-03T153000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Le spectacle"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-03T15:30:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_pour-une-autre-histoire-dun-soir","@type":["http://schema.org/Event"],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2027-01-22T20:00:00-05:00"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Pour-une-autre-histoire-dun-soir.jpg"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/pour-une-autre-histoire-dun-soir/"}],"http://schema.org/name":[{"@language":"fr","@value":"Pour une autre histoire d'un soir"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/pour-une-autre-histoire-dun-soir/#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Spectacle en salle\"]"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eTrois des plus belles voix du Québec, \u003cstrong\u003eJoe Bocan, Marie Carmen et Marie Denise Pelletier\u003c/strong\u003e, se retrouveront de nouveau ensemble sur scène, avec \u003cem\u003e\u003cstrong\u003ePour une autre histoire d’un soir\u003c/strong\u003e,\u003c/em\u003e une version revisitée du spectacle qui leur a valu le Félix du Spectacle de l'année Variétés/Réinterprétations en 2022 et plus de 50,000 spectateurs.\u003c/p\u003e\n\u003cp\u003eDans une mise en scène renouvelée de Michel Poirier, voici un spectacle qui n’aura rien perdu de son charme. Un hommage à l’auteur-compositeur-interprète Germain Gauthier, décédé en août dernier, s’inscrira parmi les nouveautés. Et rassurez-vous : la rumeur veut que \u003cem\u003eL’aigle noir\u003c/em\u003e, \u003cem\u003eRepartir à zéro\u003c/em\u003e et \u003cem\u003eTous les cris les S.O.S.\u003c/em\u003e soient de nouveau au programme !\u003c/p\u003e\n\u003cp\u003e\u003cem\u003ePour une autre histoire d’un soir \u003c/em\u003eest une occasion pour les fans de la première heure de revivre des moments inoubliables tout en découvrant quelques surprises. Pour les autres, l’occasion de comprendre pourquoi ce spectacle a récolté tant d’éloges de la part du public et des critiques. \u003c/p\u003e\n\u003cp\u003e\u003cem\u003ePour une autre histoire d’un soir\u003c/em\u003e, la porte s’ouvre sur une nouvelle aventure où se reflètent le meilleur du passé et des découvertes de grande envergure. \u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_pour-une-autre-histoire-dun-soir#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/pour-une-autre-histoire-dun-soir/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:16:38.537-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/pour-une-autre-histoire-dun-soir/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_pour-une-autre-histoire-dun-soir#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"60.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_fallait-etre-la-2026","@type":["http://schema.org/Event"],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-28T19:30:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/fallait-etre-la-2026/#WebPage"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/fallait-etre-la-2026/"}],"http://schema.org/name":[{"@language":"fr","@value":"Fallait être là"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Lavalorama/FEL-LAVAL_1080x1920-photo.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Impro\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003ch4\u003eFallait être là – Le jeu!\u003cbr /\u003e1 animateur, 4 improvisateurs vedettes, 0 reprise\u003c/h4\u003e\n\u003cp\u003e\u003cem\u003e\u003c/em\u003e\u003c/p\u003e\n\u003cp\u003eFallait être là est un nouveau concept de spectacle humoristique et interactif qui redéfinit les codes de l’impro et de l’humour de scène traditionnel afin de créer des moments déjantés. Philippe Laprise, maître de cérémonie, accueille quatre invités vedettes : Marie-Eve Morency, Lelouis Courchesne, Simon Duchesne ainsi que Stéphane Bellavance qui participent à des défis humoristiques décalés, avec comme objectif de déclencher les plus grands fous rires. Fallait être là est un spectacle événementiel audacieux, hilarant et surprenant, dans lequel le public sera amené à participer à plusieurs niveaux. Assistez à une expérience inédite de laquelle vous sortirez en vous disant : « Fallait être là! ».\u003cbr /\u003e\u003c/p\u003e\n \u003cp\u003e\u003c/p\u003e"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_fallait-etre-la-2026#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/fallait-etre-la-2026/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-26T10:22:02.218-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/fallait-etre-la-2026/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_fallait-etre-la-2026#Offer_0","http://schema.org/price":[{"@value":"35.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_leif-vollebekk-revelation-tour","@type":["http://schema.org/Event"],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Musique Indie / Folk\", \"Spectacle en salle\"]"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/name":[{"@language":"fr","@value":"Revelation Tour"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Leif.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/leif-vollebekk-revelation-tour/#WebPage"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-03T19:30:00-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/leif-vollebekk-revelation-tour/"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cstrong\u003eLeif Vollebekk est un auteur-compositeur-interprète et multi-instrumentiste \u003c/strong\u003ecanadien basé à Montréal. Reconnu pour son univers intime et profondément sensible, il transporte son public grâce à une voix évocatrice et à des mélodies d’une grande richesse émotionnelle. Sur scène, il alterne avec fluidité entre piano et guitare, créant une atmosphère à la fois envoûtante et authentique. \u003c/p\u003e\n\n\u003cp\u003eSa musique, qui puise autant dans le folk et l’indie que dans des influences jazz, charme par son originalité et sa profondeur. Chacun de ses concerts devient une véritable expérience immersive : Vollebekk y partage des improvisations spontanées et les histoires qui se cachent derrière ses chansons, ce qui renforce le sentiment de proximité et de sincérité. \u003c/p\u003e\n\n\u003cp\u003eQu’il se produise dans une petite salle ou devant un public plus large, l’intensité et la générosité de sa présence scénique captivent à chaque fois, offrant aux spectateurs le privilège de vivre un moment rare, à la fois doux, puissant et inoubliable.\u003c/p\u003e"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"},{"@id":"http://kg.footlight.io/resource/faf18f22-9210-4f54-9198-204eb745a1cc"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_leif-vollebekk-revelation-tour#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/leif-vollebekk-revelation-tour/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:21:26.737-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/leif-vollebekk-revelation-tour/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_leif-vollebekk-revelation-tour#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"48.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_176-pas","@type":["http://schema.org/Event"],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-25T14:00:00-05:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-77"}],"http://schema.org/name":[{"@language":"fr","@value":"176 Pas"}],"http://schema.org/keywords":[{"@value":"[\"Jeune public\", \"Marionnettes\", \"Spectacle en salle\"]"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003csub\u003eThéâtre de l’Œil\u003c/sub\u003e\u003c/p\u003e\n\u003ch3\u003eComment la peur peut-elle devenir une alliée?\u003c/h3\u003e\n\u003cp\u003eOctave adore la musique et joue merveilleusement bien du piano. Il vit avec sa mère et sa tante Simone dans un somptueux domaine dont il ne sort presque jamais. Octave se sent paralysé par la peur de l’inconnu. \u003c/p\u003e\n\u003cp\u003ePour Delphine, c’est tout l’inverse : elle nage, pêche, explore et travaille pour ses parents poissonniers, quand elle ne se chamaille pas joyeusement avec ses quatre frères. À ses yeux, la vie est une aventure perpétuelle… sauf à l’école! \u003c/p\u003e\n\u003cp\u003eUn jour, le chemin de Delphine croise celui d’Octave, et une improbable amitié naît entre eux. Delphine est-elle aussi téméraire qu’elle en a l’air? Quelle fracture dans l’histoire d’Octave lui fait craindre à ce point le monde extérieur? Sauront-ils s’aider l’un l’autre à vivre enfin librement? Cette fable tendre sur les différents visages de l’anxiété, célèbre le pouvoir transformateur de l’amitié. \u003cbr /\u003e\u003c/p\u003e\n\u003cp\u003eCompagnie de tournée, depuis ses débuts, le \u003cstrong\u003eThéâtre de l’Œil \u003c/strong\u003ediffuse largement ses spectacles au Québec, au Canada et à l’étranger. Voulant diversifier et enrichir l’art de la marionnette, le Théâtre de l’Œil privilégie l’alternance ou le mélange de divers types de marionnettes. Ses conceptions scénographiques, l’ingéniosité de ses marionnettes et l’originalité des thèmes abordés confèrent une solide réputation à la compagnie. \u003cem\u003e176 pas\u003c/em\u003e est le troisième projet de Simon Boudreault à titre de directeur artistique du Théâtre de l’Œil, ayant succédé au cofondateur André Laliberté en 2020.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eDurée : 60 minutes\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e\u003cstrong\u003eProfitez de votre venue à la Maison des arts pour visiter les expositions en cours à la \u003ca href=\"https://www.laval.ca/culture/maison-des-arts/expositions-salle-alfred-pellan/expositions/\" target=\"_blank\" rel=\"noreferrer noopener\"\u003eSalle Alfred Pellan\u003c/a\u003e.\u003c/strong\u003e\u003cbr /\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"http://theatredeloeil.qc.ca\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eSite Web du Théâtre de l’œil\u003c/a\u003e \u003ca href=\"https://www.laval.ca/calendrier-activites/176-pas-mda-marionnettes-jeune-public/\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eMaison des arts de Laval\u003c/a\u003e\u003csub\u003e\u003cbr /\u003e\u003c/sub\u003e\u003c/p\u003e"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/176-pas/#WebPage"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/MDA/Programmation-ville/Jeune-Public/2025-2026/176-pas.jpg"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/https://youtu.be/sxEk2eF7MNM"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/22f50ba1-3403-48a8-a279-460952d3c134"},{"@id":"http://kg.footlight.io/resource/a9e00c6a-1e97-484a-b8ef-91e02a06c51b"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/176-pas/"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_176-pas#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/176-pas/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-24T10:10:22.619-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/176-pas/"}]},{"@id":"http://kg.footlight.io/resource/a9e00c6a-1e97-484a-b8ef-91e02a06c51b","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Marionnettes"}],"http://www.w3.org/2004/02/skos/core#broadMatch":[{"@id":"http://kg.artsdata.ca/resource/PerformingArtsEvent"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_176-pas#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"17.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_lou-adriane-cassidy-triste-animal","@type":["http://schema.org/Event"],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/lou-Adrianne.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/lou-adriane-cassidy-triste-animal/#WebPage"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-10T20:00:00-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/lou-adriane-cassidy-triste-animal/"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Spectacle en salle\"]"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eAprès avoir marqué la première moitié de 2025 avec Journal d’un loup-garou, dont l’album et le spectacle ont récoltés une pluie d’éloges et un succès populaire sans précédent pour l’artiste, Lou-Adriane Cassidy nous revient avec Triste Animal, spectacle tiré de son quatrième album du même nom. Sur scène comme sur disque, ce nouveau chapitre de son travail nous donne un accès inédit à cette interprète d’exception qui, entourée de cinq musicien.nes, nous touchera au plus profond de l’âme tant par la mature sobriété de son interprétation que par la frénétique énergie qui font sa réputation depuis toujours.\u003cbr /\u003e\u003c/p\u003e"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/name":[{"@language":"fr","@value":"Triste animal"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_lou-adriane-cassidy-triste-animal#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/lou-adriane-cassidy-triste-animal/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-08T10:17:14.888-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/lou-adriane-cassidy-triste-animal/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_lou-adriane-cassidy-triste-animal#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"38.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_michel-rivard-apres-on-va-ou","@type":["http://schema.org/Event"],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/michel-rivard-apres-on-va-ou/#WebPage"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/keywords":[{"@value":"[\"Théâtre musical\", \"Spectacle en salle\"]"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-24T20:00:00-04:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Michel-Rivard-5.jpg"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/michel-rivard-apres-on-va-ou/"}],"http://schema.org/name":[{"@language":"fr","@value":"Après on va où ?"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003ch4\u003eThéâtre musical en solitaire\u003c/h4\u003e\n\u003cp dir=\"ltr\"\u003eDurant les deux semaines ( symboliques ) que dure la magnifique floraison du magnolia qui illumine la fenêtre de sa chambre, «l'homme qui me ressemble» ( 74 ans ) se questionne sur sa vie, avant et après la mort, en confrontant ses doutes et ses certitudes.\u003c/p\u003e\n\u003cp dir=\"ltr\"\u003e\tEntre un rendez-vous médical et le choix d'une urne dans un catalogue, entre un premier amour et une marche en forêt avec son petit-fils de sept ans, «l'homme qui me ressemble» monologue, poétise et chante sa recherche du «plus-grand-que-soi».\u003cbr /\u003e\u003c/p\u003e"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a4561a6b-a551-4243-879c-659e0d0d5a81"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_michel-rivard-apres-on-va-ou#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/michel-rivard-apres-on-va-ou/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:21:33.336-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/michel-rivard-apres-on-va-ou/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_michel-rivard-apres-on-va-ou#Offer_0","http://schema.org/price":[{"@value":"60.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_les-grands-explorateurs-maroc","@type":["http://schema.org/Event"],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/les-grands-explorateurs-maroc/#WebPage"}],"http://schema.org/keywords":[{"@value":"[\"Documentaire\", \"Spectacle en salle\"]"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K10-427"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K10-427"}],"http://schema.org/name":[{"@language":"fr","@value":"Les Grands Explorateurs"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Les-Grands-Explorateurs/2025-2026/LGE_2526_Maroc_756x1100.jpg"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/les-grands-explorateurs-maroc/"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/5e5310d0-73e4-4421-8401-75c6c4aaf047"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-26T13:30:00-04:00"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eVoyage libre et inspiré, c'est à bord de son van aménagé que \u003cstrong\u003eMarc\u003c/strong\u003e nous fait découvrir \"son\" Maroc depuis Tanger jusqu'à Dakhla, dans l'extrême sud du pays. Guidé par ses envies, son intuition, une bonne dose d'improvisation et sa passion pour le kitesurf, ce voyage culturel, humain et aventureux révèle le sens de l'accueil des Marocains et dévoile avec originalité, sensibilité et sincérité un Maroc surprenant, déroutant.\u003c/p\u003e\n\u003cp\u003eParti depuis sa Belgique natale avec son van, Marc rejoint Tanger depuis Marseille. Son « Maroc version vanlife » commence dans le nord du pays avec les villes de Tétouan, Chefchaouen, Fès ou encore la forêt d’Azrou. Marc s’égare ensuite dans les altitudes des Monts Atlas avant de rejoindre les décors inattendus de Ouarzazate. Recherchant l’ordinaire, mais tombant bien souvent sur l’extraordinaire, il déroule les kilomètres jusqu’à l'océan Atlantique en passant par Taliouine et Tafraoute et rejoint le petit village d’Imsouane avant une étape à Essaouira. Commence ensuite sa longue route dans le désert du Sahara pour atteindre Dakhla, la Mecque du kitesurf, sa destination finale.\u003c/p\u003e\n\u003cp\u003eAvec pour intention d’interroger un pays « lien » entre l’occident et l’Afrique, ce voyage à la fois dynamique et rêveur, se veut proche de toutes les sensations qu’un Maroc moderne et traditionnel peut offrir.\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_les-grands-explorateurs-maroc#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/les-grands-explorateurs-maroc/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:22:56.757-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/les-grands-explorateurs-maroc/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_les-grands-explorateurs-maroc#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"23.60"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_arnaud-soly-nouveau-spectacle","@type":["http://schema.org/Event"],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/arnaud-soly-nouveau-spectacle/#WebPage"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/arnaud-soly-nouveau-spectacle/"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/sameAs":[{"@value":"https://www.facebook.com/solyarnaud/"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eAvec son premier one-man-show Stand-Up a vendu près de 70 000 billets, ses milliers d’abonnés sur les réseaux sociaux, ses nominations au Gala Les Olivier 2024: Olivier de l’année et Émission humoristique télé de l’année avec Club Soly, tout le monde attend le retour de l’inimitable humoriste !\u003c/p\u003e\n\u003cp\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca class=\"notice\"\u003eSuivre l'artiste: \u003cbr /\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://www.facebook.com/solyarnaud/\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eFacebook\u003c/a\u003e \u003ca href=\"https://www.instagram.com/arnaudsoly/\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eInstagram\u003c/a\u003e \u003ca href=\"https://www.youtube.com/c/ArnaudSoly\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eYouTube\u003c/a\u003e\u003c/p\u003e"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-07T19:30:00-04:00"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Nouv-siteweb_poster_Spectacle-756-x-1100-px-2.jpg"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/name":[{"@language":"fr","@value":"Bon jus"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_arnaud-soly-nouveau-spectacle#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/arnaud-soly-nouveau-spectacle/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:19:42.870-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/arnaud-soly-nouveau-spectacle/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_arnaud-soly-nouveau-spectacle#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"47.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_finale-locale-de-secondaire-en-spectacle-2026","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Finale locale de Secondaire en spectacle 2026"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/finale-locale-de-secondaire-en-spectacle-2026/"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Locations-TMC/ses-25-26-affiche-finaleslocalesregionales-1200x1800-4.png"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-213"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/9475525c-10fa-4a53-b89b-8c7d9ba68726"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/finale-locale-de-secondaire-en-spectacle-2026/#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eLa vie étudiante du Collège Laval vous invite à sa finale locale de Secondaire en spectacle, une soirée où nos jeunes artistes feront rayonner leur talent à travers diverses disciplines artistiques, dont le théâtre, le chant et la danse.\u003c/p\u003e\n\n\n\n\u003cp\u003eUn moment vibrant d’énergie, d’émotion et de fierté, alors que nos élèves montent sur scène pour offrir des performances mémorables. Les numéros gagnants poursuivront leur aventure jusqu’à la finale régionale, puis au Rendez-vous panquébécois, le plus grand rassemblement d’artistes amateurs et l’événement culturel jeunesse le plus important au Québec.\u003c/p\u003e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-20T19:00:00-05:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/keywords":[{"@value":"[\"Variétés\", \"Spectacle en salle\"]"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_finale-locale-de-secondaire-en-spectacle-2026#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/finale-locale-de-secondaire-en-spectacle-2026/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-18T10:14:42.436-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/finale-locale-de-secondaire-en-spectacle-2026/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_finale-locale-de-secondaire-en-spectacle-2026#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"13.60"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_cinq-balles-dans-la-tete","@type":["http://schema.org/Event"],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/name":[{"@language":"fr","@value":"5 balles dans la tête"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/TB8WfYZnIHA?si=QtK0cLp0LZdAUT_L"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-77"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/cinq-balles-dans-la-tete/"}],"http://schema.org/keywords":[{"@value":"[\"Théâtre\", \"Spectacle en salle\"]"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/MDA/Programmation-ville/Th%C3%A9atre/2025-2026/5-Balles-dans-la-t%C3%AAte.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-05T19:30:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/cinq-balles-dans-la-tete/#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003csub\u003eThéâtre DuBunker\u003c/sub\u003e\u003c/p\u003e\n\u003ch3\u003e\u003csub\u003e\u003c/sub\u003eDes récits véridiques, parfois comiques, parfois tragiques, qui flirtent avec la fiction pour raconter la guerre, vécue de l’intérieur.\u003c/h3\u003e\n\u003cp\u003eLorsqu’une autrice et professeure de littérature accepte de prendre une bière avec des militaires canadiens afin qu’ils lui parlent de ce qu’ils ont vécu en Afghanistan, elle ne se doute pas que c’est le début d’une grande aventure. Si les soldats jouent d’abord les fiers-à-bras, une confiance s’établit et les langues se délient. Ils nous amènent avec eux en zone de guerre, partagent leurs récits drôles, durs et touchants, racontent la camaraderie, les champs de mines qui explosent, les peurs et les regrets. Leurs témoignages bouleverseront la dramaturge jusque dans ses plus solides convictions.\u003c/p\u003e\n\u003cp\u003ePour ce projet, Roxanne Bouchard s’est immergée dans l’univers de l’armée canadienne, rencontrant une trentaine de militaires de huit corps de métier, écoutant leurs histoires, visitant les bases militaires, confrontant ses propres valeurs et préjugés. Comment vivre avec les souvenirs et traumas de la guerre? C’est l’une des questions centrales que pose ce texte qui offre aussi une réflexion sur les rencontres marquantes pouvant survenir quand on ose s’ouvrir aux autres.\u003c/p\u003e\n\u003cp\u003eAvant d’avoir été adaptée pour la scène, cette pièce est d’abord un projet de recherche de Roxanne Bouchard, qui a donné naissance au livre \u003cem\u003e5 balles dans la tête; récits de guerre\u003c/em\u003e, publié chez Québec Amérique. \u003c/p\u003e\n\u003cp\u003eFondée en 2005, \u003cstrong\u003eDuBunker \u003c/strong\u003ea assuré une présence soutenue sur les scènes d’Europe, du Québec et du Canada. Depuis 2010, la compagnie embrasse une démarche documentaire de non-fiction, c’est-à-dire inspirée de la réalité pour développer des œuvres qui peuvent intégrer une part de fiction, un style littéraire et dramatique, une forme qui se détache de la réalité, du verbatim. \u003c/p\u003e\n\u003cp\u003eCe spectacle est présenté avec la collaboration des \u003cstrong\u003eVoyagements – Théâtre de création\u003c/strong\u003e en tournée. Présenté au Théâtre des Muses, en \u003cstrong\u003ecodiffusion avec Co-Motion.\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cem\u003e5 balles dans la tête; récits de guerre\u003c/em\u003e, de \u003cstrong\u003eRoxane Bouchard\u003c/strong\u003e, est publié chez Québec Amérique. \u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eDurée : 1 h 40, sans entracte\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e\u003cstrong\u003eProfitez de votre venue à la Maison des arts pour visiter les expositions en cours à la \u003ca href=\"https://www.laval.ca/culture/maison-des-arts/expositions-salle-alfred-pellan/expositions/\" target=\"_blank\" rel=\"noreferrer noopener\"\u003eSalle Alfred Pellan\u003c/a\u003e.\u003c/strong\u003e\u003cbr /\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"http://dubunker.com\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eSite Web du Théâtre DuBunker\u003c/a\u003e \u003ca href=\"https://www.laval.ca/calendrier-activites/5-balles-dans-la-tete-mda-theatre/\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eMaison des arts de Laval\u003c/a\u003e\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_cinq-balles-dans-la-tete#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/cinq-balles-dans-la-tete/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:13:54.148-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/cinq-balles-dans-la-tete/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_cinq-balles-dans-la-tete#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"45.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_likouri-ziya","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/likouri-ziya/"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-1056"}],"http://schema.org/name":[{"@language":"fr","@value":"Ziya"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-26T19:30:00-04:00"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/MDA/ECC/Likouri.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-116"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/likouri-ziya/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"[\"Musique du monde\", \"Spectacle en salle\", \"Admission générale\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/98447dea-0eaa-4182-893c-01cf7baada1d"}],"http://schema.org/sameAs":[{"@value":"https://www.facebook.com/likouri2"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cstrong\u003e Ziya\u003c/strong\u003e est un spectacle qui navigue entre le récit de conte, la musique de chambre et la chanson française. Il vous transportera au bord du Saint-Laurent, entre les tapis de nos grands-mères et les parfums de l’enfance. Destiné aux âmes rêveuses, il invite à l’émerveillement et à la contemplation. \u003cstrong\u003eLiKouri\u003c/strong\u003e, accompagnée de cinq talentueux musiciens et musiciennes, tisse un univers où les mélodies et les mots se mêlent pour raconter une histoire empreinte de poésie et d’espoir. Ensemble, ils vous emmènent dans un voyage sensoriel et intime, où la musique devient un pont entre passé et présent. \u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://www.laval.ca/calendrier-activites/likouri-ziya-musiques-monde-ecc/\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eEspace citoyen des Confluents\u003c/a\u003e\u003cbr /\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://www.facebook.com/likouri2\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eFacebook\u003c/a\u003e \u003ca href=\"https://www.instagram.com/likouri.band?igsh=dWZoNGFycTJyZnlk\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eInstagram\u003c/a\u003e \u003ca href=\"https://open.spotify.com/artist/5vRkO0ppbqtttKTD8ggtze?si=6Iv3BombRyOZe-oAc-kVfQ\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eSpotify\u003c/a\u003e \u003c/p\u003e\n\u003cp\u003e\u003cbr /\u003e\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_likouri-ziya#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/likouri-ziya/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:14:07.551-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/likouri-ziya/"}]},{"@id":"http://kg.footlight.io/resource/98447dea-0eaa-4182-893c-01cf7baada1d","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Musique du monde"}],"http://www.w3.org/2004/02/skos/core#closeMatch":[{"@id":"http://kg.artsdata.ca/resource/WorldMusicPerformance"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_likouri-ziya#Offer_0","http://schema.org/price":[{"@value":"20.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_mariana-mazza-foie-gras","http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-21T20:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/mariana-mazza-foie-gras/#WebPage"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/name":[{"@language":"fr","@value":"Foie gras"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Mariana-Mazza.jpg"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/mariana-mazza-foie-gras/"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003ePour son troisième spectacle, \u003cem\u003eFoie Gras\u003c/em\u003e, Mariana Mazza décide de s’exprimer avec sa tête et son cœur… pour faire changement.\u003c/p\u003e\n\u003cp\u003eDans ce one-woman-show bien personnel, la jeune fille qui a grandi à Montréal-Nord, maintenant rendue une femme embourgeoisée qui vit à Saint-Lambert avec ses caniches et ses toiles invendues, se montre aussi brillante qu’hilarante.\u003c/p\u003e\n\u003cp\u003eÉtant habituée à dire des choses uniquement pour provoquer un rire, elle souhaite aller plus loin cette fois et faire un coming out au public : elle est émotive et vulnérable. Le chihuahua sur le Red Bull est officiellement remplacé par le tank au cœur de porcelaine.\u003c/p\u003e\n\u003cp\u003eComme elle le dit si bien : « C’est moi la meilleure tabarnak! »\u003c/p\u003e\n\u003cp\u003eOui, Mariana a changé. Mais à quel point?\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_mariana-mazza-foie-gras#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-05T19:30:00-05:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_mariana-mazza-foie-gras#2026-02-21T200000-0500"},{"@id":"http://kg.footlight.io/resource/co-motion-ca_mariana-mazza-foie-gras#2026-04-23T193000-0400"},{"@id":"http://kg.footlight.io/resource/co-motion-ca_mariana-mazza-foie-gras#2026-11-05T193000-0500"}]},{"@id":"https://co-motion.ca/spectacles/mariana-mazza-foie-gras/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:14:50.331-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/mariana-mazza-foie-gras/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_mariana-mazza-foie-gras#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"53.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_mariana-mazza-foie-gras#2026-02-21T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Foie gras"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-21T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_mariana-mazza-foie-gras#2026-04-23T193000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Foie gras"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-23T19:30:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_mariana-mazza-foie-gras#2026-11-05T193000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Foie gras"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-05T19:30:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_tournee-mondiale-du-festival-du-film-de-montagne-de-banff-au-quebec-2025","@type":["http://schema.org/Event"],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/tournee-mondiale-du-festival-du-film-de-montagne-de-banff-au-quebec-2025/"}],"http://schema.org/name":[{"@language":"fr","@value":"Tournée mondiale du Festival du film de montagne de Banff au Québec 2025"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Locations/BANFF.jpg"}],"http://schema.org/keywords":[{"@value":"[\"Documentaire\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/5e5310d0-73e4-4421-8401-75c6c4aaf047"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-22T19:00:00-05:00"}],"http://schema.org/sameAs":[{"@value":"https://fr-fr.facebook.com/pages/Festival-du-film-de-montagne-de-Banff-au-Qu%C3%A9bec/199229139905"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/tournee-mondiale-du-festival-du-film-de-montagne-de-banff-au-quebec-2025/#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp dir=\"ltr\"\u003e\u003cstrong\u003e50e édition! La Tournée mondiale du Festival du film de montagne de Banff est de retour!\u003c/strong\u003e\u003cbr /\u003e\u003c/p\u003e\n\u003cp dir=\"ltr\"\u003eEn janvier et février 2026, la tournée marque 50 ans de pur dépassement. \u003c/p\u003e\n\u003cp dir=\"ltr\"\u003eVibrez devant des images spectaculaires où des hommes et des femmes d’exception poussent souvent leurs exploits à la limite du possible. Une expérience cinématographique incontournable qui célèbre la passion et l’audace. \u003cem\u003eLa programmation finale sera révélée en novembre 2025.\u003c/em\u003e\u003c/p\u003e\n\u003cp dir=\"ltr\"\u003eAvec 23 villes visitées, la tournée au Québec est l’une des plus importantes du \u003cem\u003eWorld Tour\u003c/em\u003e qui se déplace dans plus de 40 pays. Cette année, près de 600 films ont été soumis au jury, un record de tous les temps. Du nombre, environ 80 sont présentés aux festivaliers.\u003cbr /\u003e\u003c/p\u003e\n\n\u003cp dir=\"ltr\"\u003e\u003cstrong\u003eInformations pratiques\u003c/strong\u003e\u003cbr /\u003e\u003c/p\u003e\n\u003cul\u003e\u003cli dir=\"ltr\"\u003e\u003cp dir=\"ltr\"\u003eLa soirée dure trois heures au total, entracte et tombola compris.\u003c/p\u003e\u003c/li\u003e\u003cli dir=\"ltr\"\u003e\u003cp dir=\"ltr\"\u003eLe programme est le même pour chaque projection.\u003c/p\u003e\u003c/li\u003e\u003cli dir=\"ltr\"\u003e\u003cp dir=\"ltr\"\u003eLes films sont présentés en version originale sous-titrée en français afin de préserver leur authenticité.\u003c/p\u003e\u003c/li\u003e\u003c/ul\u003e\u003cul\u003e\u003cli dir=\"ltr\"\u003e\u003cp dir=\"ltr\"\u003eDe nombreux prix sont tirés au sort après l'entracte.\u003c/p\u003e\u003c/li\u003e\u003cli dir=\"ltr\"\u003e\u003cp dir=\"ltr\"\u003eUne exposition photographique sur un thème différent chaque année accompagne la tournée.\u003c/p\u003e\u003c/li\u003e\u003c/ul\u003e\n\u003cp dir=\"ltr\"\u003e\u003ca href=\"http://www.banffquebec.ca/\" target=\"_blank\" rel=\"noreferrer noopener\"\u003ewww.banffquebec.ca\u003c/a\u003e \u003cbr /\u003eFacebook : \u003ca href=\"https://fr-fr.facebook.com/pages/Festival-du-film-de-montagne-de-Banff-au-Qu%C3%A9bec/199229139905\" target=\"_blank\" rel=\"noreferrer noopener\"\u003eFestival du film de montagne de Banff au Québec\u003cbr /\u003e\u003c/a\u003eInstagram : \u003ca href=\"https://www.instagram.com/banffquebec/\" target=\"_blank\" rel=\"noreferrer noopener\"\u003eFestival du film de montagne de Banff au Québec\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cbr /\u003e\u003cbr /\u003e\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_tournee-mondiale-du-festival-du-film-de-montagne-de-banff-au-quebec-2025#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/tournee-mondiale-du-festival-du-film-de-montagne-de-banff-au-quebec-2025/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-20T10:22:46.887-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/tournee-mondiale-du-festival-du-film-de-montagne-de-banff-au-quebec-2025/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_tournee-mondiale-du-festival-du-film-de-montagne-de-banff-au-quebec-2025#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"30.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_basia-bulat-basias-palace","@type":["http://schema.org/Event"],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/basia-bulat-basias-palace/"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"},{"@id":"http://kg.artsdata.ca/resource/Chansonanglophone"},{"@id":"http://kg.footlight.io/resource/faf18f22-9210-4f54-9198-204eb745a1cc"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/name":[{"@language":"fr","@value":"Basia's Palace"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Basia-Bu%C5%82at.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cstrong\u003eBasia Bula\u003c/strong\u003et est une autrice-compositrice-interprètequi habite à Montréal, au Canada. Elle possède une voix distinctive et sa musique s’inspire autant des musiques gospel et soul que du folk classique. En plus d’avoir une voix puissante, Bulat est une multi-instrumentiste accomplie, qui joue de la guitare électrique, du piano, de l’autoharpe, du ukulélé, de la basse et du charango.\u003c/p\u003e\n\u003cp\u003eSon talent a été largement reconnu : ses chansons ont été adaptées dans le cadre de concerts majeurs avec des orchestres symphoniques, et elle a été invitée à participer à de prestigieux hommages à Leonard Cohen, Daniel Lanois, Nick Cave, et The Band. Depuis la parution de son premier album en 2007, elle a partagé la scène avec des artistes tels que St Vincent, Sufjan Stevens, Nick Cave and the Bad Seeds, The National, Michael Kiwanuka, Daniel Lanois, Beirut, Destroyer, US Girls, Jim James et plusieurs autres.\u003c/p\u003e\n\u003cp\u003eElle a offert une performance dans le cadre des \u003cstrong\u003eTiny Desk Concerts de NPR\u003c/strong\u003e, elle a été l’invitée musicale des émissions \u003cstrong\u003eLate Show with Stephen Colbert\u003c/strong\u003e, dans le cadre des séries #LateShowMeMusic et \u003cstrong\u003eLater with Jools Holland\u003c/strong\u003e, et elle a fait l’objet de couverture par \u003cstrong\u003eThe Needle Drop\u003c/strong\u003e, \u003cstrong\u003eThe New York Times\u003c/strong\u003e et diverses autres publications. Bulat a été trois fois finaliste pour le \u003cstrong\u003ePrix de musique Polaris\u003c/strong\u003e et a été nommée pour cinq prix\u003cstrong\u003e JUNO\u003c/strong\u003e. \u003c/p\u003e\n\u003cp\u003eSon nouvel album, Basia’s Palace, sera lancé le 21 février 2025 via Secret City Records.\u003c/p\u003e"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-14T20:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/basia-bulat-basias-palace/#WebPage"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Chanson anglophone\", \"Musique Indie / Folk\", \"Spectacle en salle\"]"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_basia-bulat-basias-palace#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/basia-bulat-basias-palace/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:15:39.099-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/basia-bulat-basias-palace/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_basia-bulat-basias-palace#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"35.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_fouki-still-kankan","@type":["http://schema.org/Event"],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"},{"@id":"http://kg.footlight.io/resource/a72d7b0a-b597-43c4-b2c3-2be59adfb0e0"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/fouki-still-kankan/#WebPage"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/fouki-still-kankan/"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Rap / Hip hop / R\u0026B\", \"Spectacle en salle\"]"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/319.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cstrong\u003eSpectacle festif : les gens seront debout dès la première chanson! Choisissez les premières rangées si vous aimez vous lever, chanter et danser, sinon optez pour un siège plus reculé.\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eAvec un son reconnaissable entre tous et son charisme naturel, \u003cstrong\u003eFouKi \u003c/strong\u003evous entraînera instantanément dans son univers. Une expérience vibrante à la fois décontractée et énergique où, toujours accompagné de son acolyte QuietMike, il naviguera allègrement entre son nouvel album, morceaux plus connus, medleys festifs et freestyles ; il y aura du Zay pour tous les goûts.\u003c/p\u003e\n\u003cp\u003eLa zaynergie contagieuse de FouKi transformera chaque spectacle en une fête mémorable, un chilling à saveur estival où tout le monde est convié, « peu importe la couleur de ses caleçons »!\u003c/p\u003e"}],"http://schema.org/name":[{"@language":"fr","@value":"Still Kankan"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-16T20:00:00-05:00"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_fouki-still-kankan#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/fouki-still-kankan/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-14T10:13:59.450-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/fouki-still-kankan/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_fouki-still-kankan#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"40.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_jay-scott-toutes-les-rues-sont-silencieuses","@type":["http://schema.org/Event"],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Spectacle en salle\", \"Admission générale debout\"]"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-28T20:00:00-04:00"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Lavalorama/Jay-Scott.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eAprès le succès retentissant de Sa Plus Grande Première Tournée en 2021-2022, \u003cstrong\u003eJay Scøtt \u003c/strong\u003ele Conquérant s’est lancé dans une tournée mondiale à grand déploiement à travers la province dans le cadre de son très garni Québec World Tour 2023-2024 pour ensuite annoncer son aventureuse tournée Chasse \u0026amp; Pêche de 5 festivals à l’été 2024. Automne 2024 et 2025, place à la tournée officielle de son 2e album en carrière: \u003cstrong\u003eToutes les rues sont silencieuses\u003c/strong\u003e.\u003c/p\u003e\n\u003cp\u003eTout au long du spectacle, Jay Scøtt vous invite, armé de sa guitare, de son ukulélé, de son clavier et de ses séquences, dans son univers mélancolique aux rimes et mélodies vocales accrocheuses.\u003c/p\u003e\n\u003cp\u003e C’est dans un décor intime, chaleureux et une mise en scène imprévisible, frôlant la folie, que vous vivrez une expérience unique et authentique, teintée d’humour. Soyez prêts à chanter et entendre la simple histoire d'un autre humain à la poursuite du bonheur. À bonne franquette !\u003c/p\u003e"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/name":[{"@language":"fr","@value":"Toutes les rues sont silencieuses"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/jay-scott-toutes-les-rues-sont-silencieuses/#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-212"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/jay-scott-toutes-les-rues-sont-silencieuses/"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_jay-scott-toutes-les-rues-sont-silencieuses#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/jay-scott-toutes-les-rues-sont-silencieuses/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:22:45.940-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/jay-scott-toutes-les-rues-sont-silencieuses/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_jay-scott-toutes-les-rues-sont-silencieuses#Offer_0","http://schema.org/price":[{"@value":"10.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_maude-landry-trop-cool","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/maude-landry-trop-cool/"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/name":[{"@language":"fr","@value":"Trop cool"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Maude-Landry.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/maude-landry-trop-cool/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eDans un monde où il faut se démarquer tout en suivant les tendances, Maude Landry se demande pourquoi elle n’a jamais fait partie de la gang des cools. C’est ce qui la rend attachante. Décontractée et pince-sans-rire, elle pose son regard taquin sur elle-même et sur la société, en passant délibérément par quatre chemins. Un spectacle moderne et assumé, pendant lequel on se dit parfois « \u003cem\u003eC’est niaiseux!... Mais fallait y penser ». \u003c/em\u003eMaude vous invite dans un périple au cœur de ses réflexions, ses doutes et ses déboires, pour que vous sortiez de la salle en disant \u003cem\u003e« C’était trop cool!\u003c/em\u003e »\u003c/p\u003e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-03T19:30:00-04:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_maude-landry-trop-cool#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/maude-landry-trop-cool/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:20:51.443-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/maude-landry-trop-cool/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_maude-landry-trop-cool#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"44.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_portraits-chansons-de-joni-mitchell","@type":["http://schema.org/Event"],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Spectacle en salle\", \"Admission générale\"]"}],"http://schema.org/sameAs":[{"@value":"https://www.facebook.com/mariannetrudelmusique"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/watch?v=ipuMCOuTsFs"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/portraits-chansons-de-joni-mitchell/#WebPage"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/portraits-chansons-de-joni-mitchell/"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-116"}],"http://schema.org/name":[{"@language":"fr","@value":"Portraits: chansons de Joni Mitchell"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/MDA/ECC/Joni-Mitchel.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cstrong\u003eMarianne Trudel \u003c/strong\u003eet \u003cstrong\u003eKaren Young\u003c/strong\u003e, deux artistes exceptionnelles, entières et attachantes, se joignent ici pour un concert inoubliable. Un piano, une voix: atmosphérique, intime, et émouvant. Les chansons de Joni Mitchell sont revisitées de façon originale et extrêmement touchante. Liberté, tendresse, spontanéité et complicité musicale caractérisent ce duo fabuleux. Ce concert célèbre les 50 ans de carrière de Karen Young!\u003c/p\u003e\n\u003cul\u003e\u003cli\u003e« En parfaite symbiose dans un spectacle livré en toute simplicité (...) Un moment bouleversant, livré avec beaucoup d’émotions, qu’on aurait aimé voir s’étirer à l’infini » - Normand Provencher, Le Soleil.\u003c/li\u003e\u003c/ul\u003e\n\u003cul\u003e\u003cli\u003e« Wow! Un album d’un tel raffinement, d’une telle liberté, d’un tel respect, d’une telle qualité! » — Sylvain Cormier, Le Devoir \u003c/li\u003e\u003c/ul\u003e\n\u003cul\u003e\u003cli\u003e« Ces interprètes maîtrisent parfaitement ce répertoire et sont parmi nos musiciennes les plus accomplies! » — Alain Brunet, La Presse\u003c/li\u003e\u003c/ul\u003e\n\u003cul\u003e\u003cli\u003e« Quel casting de rêve! » — Ralph Boncy, Voir\u003c/li\u003e\u003c/ul\u003e\n\u003cp\u003e\u003cstrong\u003eMarianne Trudel\u003c/strong\u003e est pianiste, compositrice, improvisatrice, et arrangeuse. Artiste généreuse et attachante, véritable dynamo sur la scène musicale québécoise et canadienne, Marianne Trudel a démontré maintes fois ses talents et son sens aiguisé de la créativité. Sa musique qui transporte et émeut peut difficilement être cataloguée, ce qui en fait une de ses forces: à la fois sophistiquée et accrocheuse, authentique et unique. Récipiendaire de nombreux prix et mentions dont le Prix de l’Artiste de l’année décerné par le Conseil des arts et des lettres du Québec (2023-Laval), elle a présenté sa musique dans les pays suivants : Canada, États-Unis, Mexique, France, Espagne, Allemagne, Angleterre, Écosse, Italie, Hongrie, Lituanie, Pays-Bas, et Chine. Elle possède 11 enregistrements en tant que leader, ayant tous récoltés des critiques élogieuses (nominations aux JUNO Awards, ADISQ, Prix OPUS).\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eKaren Young,\u003c/strong\u003e auteure-compositrice-interprète et arrangeuse, a fait ses débuts professionnels avec un single, la chanson folk Garden of URSH parue en 1971. À la mi '70, elle a développé une passion pour le jazz qui ne la quittera plus. Récipiendaire du prix Oscar-Peterson en 2016 et bénéficiaire d'une bourse de carrière du CALQ en 2008, Karen Young a remporté avec Michel Donato le Prix Félix de l'Album jazz avec Contredanse en 1988, et celui de l'Album classique vocal en 2008 pour son album Âme, corps et désir. Karen Young n'a jamais refusé un défi musical, ayant chanté dans plus de 20 langues, passant de partitions impossibles avec la SMCQ, à des albums pour enfants; sa voix se retrouve sur plus de 40 albums.\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://mariannetrudel.bandcamp.com/album/portraits-songs-of-joni-mitchell\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eEn écoute\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eCrédit photo : Michel Pinault\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"http://www.mariannetrudel.com\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eSite web de Marianne Trudel\u003c/a\u003e \u003ca href=\"https://www.facebook.com/mariannetrudelmusique\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003e Facebook\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://www.laval.ca/calendrier-activites/jazz-marianne-trudel-et-karen-young-ecc/\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eEspace citoyen des Confluents\u003c/a\u003e\u003c/p\u003e"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-1056"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-26T19:30:00-05:00"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_portraits-chansons-de-joni-mitchell#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/portraits-chansons-de-joni-mitchell/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:10:56.884-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/portraits-chansons-de-joni-mitchell/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_portraits-chansons-de-joni-mitchell#Offer_0","http://schema.org/price":[{"@value":"20.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_une-petite-fete","@type":["http://schema.org/Event"],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"[\"Jeune public\", \"Spectacle en salle\", \"Admission générale\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/22f50ba1-3403-48a8-a279-460952d3c134"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/une-petite-fete/"}],"http://schema.org/name":[{"@language":"fr","@value":"Une petite fête - Cabaret de la dissidence"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/MDA/ECC/Une-petite-f%C3%AAte.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-09T10:00:00-04:00"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-116"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/une-petite-fete/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-1056"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cstrong\u003eUne production: Le Carrousel, compagnie de théâtre\u003c/strong\u003e\u003cbr /\u003e\u003c/p\u003e\n\u003cp\u003eDeux personnages fougueux et libres vous reçoivent dans une fête secrète, un peu canaille, un peu délirante, une fête où une gentille délinquance a son espace d’expression. D’ailleurs, cette fête est interdite aux adultes. Bon… d’accord… quelques-uns pourront avoir un passe-droit, mais quelques-uns seulement… parce qu’à ce \u003cem\u003eCabaret de la dissidence\u003c/em\u003e, ce sont les enfants qui décident. \u003c/p\u003e\n\u003cp\u003e\u003cem\u003eUne petite fête \u003c/em\u003eprend la forme d’un cabaret composé d’une série de courtes vignettes pour deux interprètes et un musicien. Pour célébrer la 50e saison du Carrousel, la compagnie déploie toute sa dissidence dans un dispositif autoportant et immersif pouvant accueillir 100 spectateurs à la fois en les plongeant dans un monde merveilleux, aux frontières du carnaval et d’un univers forain. La petite jauge, un éclairage autonome et fabriqué à la main, des costumes inusités et de la musique en direct animent joyeusement ce cabaret un peu fou. \u003cbr /\u003e \u003c/p\u003e\n\u003cp\u003e\u003cem\u003eUne petite fête – Cabaret de la dissidence \u003c/em\u003eest une création du Carrousel, compagnie de théâtre, en coproduction avec le Théâtre français du Centre national des Arts (Ottawa) et la Société de développement culturel de Terrebonne. \u003c/p\u003e\n\u003cp\u003eDéveloppée en collaboration avec le Fonds national de création du Centre national des Arts du Canada. \u003c/p\u003e\n\u003cp\u003e\u003cem\u003eUne petite fête\u003c/em\u003e \u003cem\u003e– Cabaret de la dissidence \u003c/em\u003ea bénéficié d’une résidence au Théâtre Desjardins (LaSalle). \u003c/p\u003e\n\u003cp\u003e\u003cbr /\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://www.lecarrousel.net\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eSite Internet de la compagnie\u003c/a\u003e \u003ca href=\"https://www.laval.ca/calendrier-activites/theatre-petite-fete-ecc/\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eEspace citoyen des Confluents\u003c/a\u003e\u003cbr /\u003e \u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_une-petite-fete#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/une-petite-fete/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:11:03.853-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/une-petite-fete/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_une-petite-fete#Offer_0","http://schema.org/price":[{"@value":"12.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_patrick-groulx-trop-longtemps-seul","@type":["http://schema.org/Event"],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-07T20:00:00-05:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/patrick-groulx-trop-longtemps-seul/"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/patrick-groulx-trop-longtemps-seul/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/name":[{"@language":"fr","@value":"Trop longtemps seul"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cstrong\u003ePatrick Groulx\u003c/strong\u003e est enfin de retour sur scène pour présenter son 5e one-man-show : Trop longtemps seul. Dans ce show qui lui ressemble à 100%, il renoue avec un style d’humour qui rappelle parfois le côté décalé et éclaté qu’il avait à l’époque du Groulx luxe. Toujours aussi franc et irrévérencieux, Patrick embrasse maintenant l’idée de vieillir, alors qu’il ne s’est jamais senti aussi bien, aussi jeune et aussi libre que dans la cinquantaine. \u003c/p\u003e\n\u003cp\u003eTrop longtemps seul est un spectacle qui rejoint toutes les générations, qui fait rire aux larmes, réfléchir par moment et a ce petit côté juvénile unique à la Pat Groulx. Pour son 5e show, il regroupe plusieurs facettes comiques de ses précédents spectacles et offre à son public du Groulx pur et dur. Un spectacle aussi le fun à voir seul qu’en gang! Peut-être y croiserez-vous le Curé Poirier et le Brigadier? \u003c/p\u003e\n\u003cp\u003e« Un jour, mon équipe m’a dit : Pat, tu passes trop de temps tout seul… Ça l’air que ça se reflétait dans mes textes! J’ai éclaté de rire car ils avaient raison. J’ai 51 ans, ma blonde est en Outaouais et mes enfants ont leur propre vie maintenant. Pour la première fois depuis longtemps, je retrouve une certaine liberté et je passe plus de temps seul. Ça m’a amené à réfléchir, observer et philosopher sur un paquet de trucs loufoques et inutiles, mais combien inspirants. Bref, tout ça a donné naissance à ce nouveau spectacle que je suis très excité de vous présenter. » -\tPatrick Groulx\u003c/p\u003e"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Patrick-Groulx.jpg"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_patrick-groulx-trop-longtemps-seul#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/patrick-groulx-trop-longtemps-seul/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:14:12.402-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/patrick-groulx-trop-longtemps-seul/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_patrick-groulx-trop-longtemps-seul#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"48.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_coco-beliveau-cocologie","@type":["http://schema.org/Event"],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-20T20:00:00-04:00"}],"http://schema.org/name":[{"@language":"fr","@value":"Cocologie"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/coco-beliveau-cocologie/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/coco-beliveau-cocologie/"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Coco-Belliveau.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eEn 1692 à Salem, des procès ont été mis en place afin de mettre fin à la sorcellerie. Des centaines d'années plus tard, Coco Belliveau fait le rodage de son prochain spectacle. Coïncidence ? Nous ne croyons pas…. \u003c/p\u003e\n\u003cp\u003eJoignez-vous à cette charmante acadienne neurodivergente (et totalement brûlée) pour une soirée de paroles envoûtantes. Vite, avant qu'elle ne soit clouée au pilori pour ses calembredaines et sa Cocologie aiguisée!\u003c/p\u003e"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_coco-beliveau-cocologie#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/coco-beliveau-cocologie/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:15:34.168-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/coco-beliveau-cocologie/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_coco-beliveau-cocologie#Offer_0","http://schema.org/price":[{"@value":"37.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_osl-concert-bebe-musique","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"https://signelaval.com/fr/repertoire-culturel/orchestre-symphonique-de-laval#Organization"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K5-431"},{"@id":"http://kg.artsdata.ca/resource/K10-533"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003ePensée pour les enfants de \u003cstrong\u003e0 à 5 ans\u003c/strong\u003e et leur famille, cette expérience est l’occasion d’assister à une répétition générale de l’Orchestre, dans une atmosphère douce et détendue. Le public est libre d’écouter à son rythme et de se déplacer dans la salle, sans crainte de déranger les voisin.e.s ou les musicien.ne.s. Une façon bienveillante et accessible de vivre le concert autrement, permettant d’éveiller les sens et la curiosité. Soyez parmi nous pour la prochaine générale en février 2026!\u003c/p\u003e\n\n\u003cp\u003e\u003cstrong\u003eChef d’orchestre : Adam Johnson\u003c/strong\u003e\u003c/p\u003e"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/f56c4b49-240a-447d-8212-5f0c6333e42a"}],"http://schema.org/keywords":[{"@value":"[\"Musique classique\", \"Spectacle en salle\", \"Admission générale\"]"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/osl-concert-bebe-musique/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/OSL/GC-2025-26/OSL_COMOTION_SITE_WEB_BM.jpg"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/osl-concert-bebe-musique/"}],"http://schema.org/name":[{"@language":"fr","@value":"Orchestre symphonique de Laval"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_osl-concert-bebe-musique#Offer_0"}]},{"@id":"https://signelaval.com/fr/repertoire-culturel/orchestre-symphonique-de-laval#Organization","@type":["http://schema.org/Organization"],"http://schema.org/name":[{"@language":"fr","@value":"Orchestre symphonique de Laval"}],"http://schema.org/url":[{"@value":"https://signelaval.com/fr/repertoire-culturel/orchestre-symphonique-de-laval"}]},{"@id":"https://co-motion.ca/spectacles/osl-concert-bebe-musique/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-30T10:16:03.736-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/osl-concert-bebe-musique/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_osl-concert-bebe-musique#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"21.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_marina-orsini-reconstruire-les-saisons","@type":["http://schema.org/Event"],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/marina-orsini-reconstruire-les-saisons/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Chanson francophone\", \"Spectacle en salle\"]"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-03T19:30:00-05:00"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Marina-Orsini.jpg"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"},{"@id":"http://kg.artsdata.ca/resource/Chansonfrancophone"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cstrong\u003eMarina Orsini\u003c/strong\u003e, comédienne et animatrice parmi les plus appréciées du Québec, propose son tout premier spectacle original, \u003cem\u003eReconstruire les saisons\u003c/em\u003e. Auteure et interprète d’une grande sensibilité, elle partagera avec émotion cette oeuvre artistique, accompagnée par le quatuor à cordes \u003cem\u003e\u003cstrong\u003eMommies on the Run\u003c/strong\u003e\u003c/em\u003e, sous la direction musicale de\u003cstrong\u003e Catherine Major\u003c/strong\u003e.\u003c/p\u003e\n\u003cp\u003eLe spectacle, du même titre que son premier album sorti en février 2025, mettra en lumière des thèmes poignants tels que l’amour, le désir, la déchirure, le deuil, la résilience et, bien sûr, la nature, son refuge. Marina Orsini dévoile enfin ses écrits intimes et sincères, dans une œuvre profondément ancrée dans la tradition de la chanson francophone.\u003c/p\u003e"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/marina-orsini-reconstruire-les-saisons/"}],"http://schema.org/name":[{"@language":"fr","@value":"Reconstruire les saisons"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_marina-orsini-reconstruire-les-saisons#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/marina-orsini-reconstruire-les-saisons/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:19:05.116-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/marina-orsini-reconstruire-les-saisons/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_marina-orsini-reconstruire-les-saisons#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"45.75"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_alain-choquette-entre-histoires-et-illusions","@type":["http://schema.org/Event"],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/alain-choquette-entre-histoires-et-illusions/"}],"http://schema.org/keywords":[{"@value":"[\"Magie\", \"Spectacle en salle\"]"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-01T15:00:00-05:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/watch?v=MRng9ta8Kw4"}],"http://schema.org/name":[{"@language":"fr","@value":"Entre histoires et illusions"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/75fd4d7c-18ad-4560-818a-42232b15137c"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Alain-Choquette-2.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/alain-choquette-entre-histoires-et-illusions/#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eMaître de l’insaisissable et raconteur hors pair, \u003cstrong\u003eAlain Choquette\u003c/strong\u003e revient sur scène avec une nouvelle proposition aussi divertissante qu’inspirante, Entre histoires et illusions. Dans ce spectacle, l’illusionniste québécois le plus charismatique et expérimenté offre à un public de tous âges de plonger avec lui dans son grand livre de la magie. Cette fois-ci, ce sont ses récits captivants, allant de l’histoire de la magie (Robert-Houdin, Houdini, Copperfield, Magic Tom...) aux surprenantes expériences vécues au cours de ses 35 années de carrière, qui amèneront le célèbre juge de La magie des stars (TVA) à vous éblouir tant par ses performances insaisissables que par ses connaissances historiques et sa capacité à raconter. Plus de 2 millions de billets vendus en carrière, premier Québécois à présenter un spectacle en résidence à Las Vegas, plusieurs tournées à succès en Europe, création et distribution de grands numéros magiques à l’international : Alain Choquette fait partie des grands noms de la magie à travers le monde et ne manque pas de récits et de numéros pour surprendre, faire rire et éblouir. Ayant une place de choix dans le cœur des Québécois depuis des décennies, Alain veut redonner au public en lui proposant une expérience intime, unique et interactive, qui créera des souvenirs indélébiles. Entre histoires et illusions, un spectacle dans lequel la magie prend tout son sens!\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_alain-choquette-entre-histoires-et-illusions#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/alain-choquette-entre-histoires-et-illusions/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:16:17.745-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/alain-choquette-entre-histoires-et-illusions/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_alain-choquette-entre-histoires-et-illusions#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"49.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_en-theories-une-poetique-de-lespace-temps","@type":["http://schema.org/Event"],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-19T15:00:00-04:00"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cstrong\u003eUn spectacle de théâtre de mouvement acrobatique de DynamO Théâtre.\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eÀ travers le prisme du mouvement et de la poésie, et inspiré de la physique classique et quantique, En théories (une poétique de l’espace) explore les thèmes de l'univers, du temps et de l'identité.\u003c/p\u003e\n\u003cp\u003eUn groupe partage sa fascination pour les étoiles et les mystères de l'univers. 12 interprètes, avec 44 chaises, incarnent des phénomènes physiques et humains, brouillant les frontières de la réalité et des dimensions. Le spectacle mêle acrobaties, mouvements poétiques et métaphores pour interroger notre place dans l’univers et le sens de l’existence.\u003c/p\u003e\n\u003cp\u003eDynamO Théâtre propose une création audacieuse, hommage à la curiosité et à l’émerveillement, qui transcende les normes du théâtre jeune public et offre une expérience immersive, pleine de mystère… oserait-on dire métaphysique?\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eSpectacle présenté en codiffusion avec la Rencontre Théâtre Ados\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://dynamotheatre.qc.ca/\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eSite internet de la compagnie\u003c/a\u003e \u003ca href=\"https://rtados.qc.ca/\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eSite internet de la RTA\u003c/a\u003e\u003cbr /\u003e\u003c/p\u003e\n\u003cp\u003e \u003ca href=\"https://www.laval.ca/calendrier-activites/theatre-acrobatique-theories-mda/\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eMaison des arts de Laval\u003c/a\u003e\u003c/p\u003e"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/en-theories-une-poetique-de-lespace-temps/"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-77"}],"http://schema.org/name":[{"@language":"fr","@value":"En théories (une poétique de l’espace-temps)"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/MDA/Programmation-ville/En-th%C3%A9ories.jpg"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/f080c3b4-4948-4abe-a734-b6ac2df250f4"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/en-theories-une-poetique-de-lespace-temps/#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/keywords":[{"@value":"[\"Théâtre de mouvement acrobatique\", \"Spectacle en salle\"]"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_en-theories-une-poetique-de-lespace-temps#Offer_0"}]},{"@id":"http://kg.footlight.io/resource/f080c3b4-4948-4abe-a734-b6ac2df250f4","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Théâtre de mouvement acrobatique"}],"http://www.w3.org/2004/02/skos/core#broadMatch":[{"@id":"http://kg.artsdata.ca/resource/PerformingArtsEvent"}]},{"@id":"https://co-motion.ca/spectacles/en-theories-une-poetique-de-lespace-temps/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:20:41.097-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/en-theories-une-poetique-de-lespace-temps/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_en-theories-une-poetique-de-lespace-temps#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"17.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_dominic-paquet-nouveau-spectacle","http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/dominic-paquet-nouveau-spectacle/#WebPage"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/dominic-paquet-nouveau-spectacle/"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eAprès avoir vendu plus de 250 000 billets de son dernier spectacle, le populaire humoriste \u003cstrong\u003eDominic Paquet\u003c/strong\u003e est de retour avec un tout nouveau one-man-show : \u003cem\u003eJ’comprends la game\u003c/em\u003e. Le 5e spectacle solo de Dominic se veut un gros clin d'œil à ces fameuses personnes qui, grâce à leur expérience de vie, sont certaines de comprendre des choses qui sont inaccessibles pour le commun des mortels… \u003cem\u003eJ’comprends la game\u003c/em\u003e est un laboratoire humoristique où la folie prend tout son sens alors que Dominic explore, examine et met en évidence toutes sortes de théories farfelues afin de donner des réponses à des questionnements que personne n’a jamais eus! Encore une fois, Dominic Paquet fait rire aux éclats, surprend et séduit par sa capacité à incarner des personnages uniques et à créer des expressions qui marqueront votre imaginaire!\u003c/p\u003e\n\n\u003cblockquote\u003eLe public veut des réponses, donc moi je suis là pour nourrir les esprits. Quand le spectacle se termine, je sens que les gens quittent la salle en se disant « maintenant moi aussi j'comprends la game! ». - Dominic Paquet\u003c/blockquote\u003e\n\n\n\u003cp\u003eLe spectacle est présenté par Alimentation Mon Quartier, qui vous invite à vivre une expérience unique, signée par les artisans et producteurs alimentaires d’ici.\u003c/p\u003e"}],"http://schema.org/name":[{"@language":"fr","@value":"J’comprends la game"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Dominic-Paquet.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-26T19:30:00-05:00"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_dominic-paquet-nouveau-spectacle#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-26T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_dominic-paquet-nouveau-spectacle#2026-02-26T193000-0500"},{"@id":"http://kg.footlight.io/resource/co-motion-ca_dominic-paquet-nouveau-spectacle#2026-09-25T200000-0400"},{"@id":"http://kg.footlight.io/resource/co-motion-ca_dominic-paquet-nouveau-spectacle#2026-09-26T200000-0400"}]},{"@id":"https://co-motion.ca/spectacles/dominic-paquet-nouveau-spectacle/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:14:27.896-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/dominic-paquet-nouveau-spectacle/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_dominic-paquet-nouveau-spectacle#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"52.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_dominic-paquet-nouveau-spectacle#2026-02-26T193000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"J’comprends la game"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-26T19:30:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_dominic-paquet-nouveau-spectacle#2026-09-25T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"J’comprends la game"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-25T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_dominic-paquet-nouveau-spectacle#2026-09-26T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"J’comprends la game"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-26T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_dave-fenley-trio","@type":["http://schema.org/Event"],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/dave-fenley-trio/"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/name":[{"@language":"fr","@value":"Trio"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/dave-fenley-trio/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Dave-Fenley-3.png"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003c/p\u003e\n\u003cp\u003eAvec près de 40 000 billets vendus et plus de 50 représentations à travers le Québec, le chouchou du public québécois est de retour avec un tout nouveau spectacle : \u003cstrong\u003eTRIO\u003c/strong\u003e.\u003c/p\u003e\n\u003cp\u003eRévélé en 2022 grâce à l’émission \u003cem\u003eEn direct de l’Univers\u003c/em\u003e, Dave Fenley charme par sa voix puissante et son style folk-country authentique. Pour cette nouvelle aventure sur scène, il s’entoure de sa mentore \u003cstrong\u003eLise Dion\u003c/strong\u003e à la direction artistique, ainsi que des talentueux musiciens québécois \u003cstrong\u003eGabriella Laberge\u003c/strong\u003e et \u003cstrong\u003eJohn Antony Gagnon Robinette\u003c/strong\u003e.\u003c/p\u003e\n\u003cp\u003eUn spectacle riche en émotions et parsemé de surprises, à ne pas manquer.\u003c/p\u003e\n\u003cp\u003e\u003c/p\u003e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-13T20:00:00-04:00"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_dave-fenley-trio#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/dave-fenley-trio/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:19:15.609-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/dave-fenley-trio/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_dave-fenley-trio#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"58.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_dave-gaudet-intelligence-artificielle","@type":["http://schema.org/Event"],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/dave-gaudet-intelligence-artificielle/"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-25T20:00:00-04:00"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/dave-gaudet-intelligence-artificielle/#WebPage"}],"http://schema.org/name":[{"@language":"fr","@value":"Intelligence Artificielle"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Dave-Gaudet.jpg"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003ch4\u003eLa carrière de l’humoriste à l’énergie débordante et au grand charisme Dave Gaudet est en plein essor depuis la pandémie. \u003c/h4\u003e\n\u003cp dir=\"ltr\"\u003eSon dernier spectacle, \u003cem\u003eLa Légende\u003c/em\u003e, a franchi la barre des 50 000 billets vendus à travers le Québec. Au terme de cette tournée marquante, il annoncera son nouveau spectacle \u003cem\u003eIA (Intelligence Artificielle)\u003c/em\u003e, une création audacieuse inspirée par et conçue en partie avec l’intelligence artificielle. Un concept novateur qui reflète parfaitement sa créativité sans limites et son goût pour l’extraordinaire. La légende ne fait que se concrétiser. Encore. \u003cbr /\u003e\u003cbr /\u003e\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_dave-gaudet-intelligence-artificielle#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/dave-gaudet-intelligence-artificielle/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:21:22.083-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/dave-gaudet-intelligence-artificielle/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_dave-gaudet-intelligence-artificielle#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"45.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_ingrid-st-pierre-seule-au-piano","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/ingrid-st-pierre-seule-au-piano/"}],"http://schema.org/name":[{"@language":"fr","@value":"Seule au piano"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Ingrid-2.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-116"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-1056"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eDes chansons délicatement déshabillées, détricotées, qui se déploient dans leurs apparats originels. Vives, chargées d’histoires, et toutes pleines d’immenses.\u003c/p\u003e\n\n\u003cp\u003eDans ce spectacle solo, Ingrid St-Pierre est portée par l’envie d’exister sur scène librement, en laissant toute la place au cinéma des mots, aux silences, aux détails,aux histoires, aux paysages.\u003c/p\u003e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-21T19:30:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/ingrid-st-pierre-seule-au-piano/#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_ingrid-st-pierre-seule-au-piano#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/ingrid-st-pierre-seule-au-piano/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:15:00.378-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/ingrid-st-pierre-seule-au-piano/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_ingrid-st-pierre-seule-au-piano#Offer_0","http://schema.org/price":[{"@value":"29.35"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_france-damour-nouveau-spectacle","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/France-dAmour-4.jpg"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/france-damour-nouveau-spectacle/"}],"http://schema.org/name":[{"@language":"fr","@value":"Nouveau spectacle"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cstrong\u003eFrance D’Amour\u003c/strong\u003e, une des artistes les plus prolifiques de sa génération, avec ses 14 albums en carrière, saura vous enchanter avec son spectacle à la fois drôle et touchant. Musicienne qui sait autant rocker sa guitare que jazzer intelligemment, elle livre une performance spontanée et rafraichissante pour le coeur, la tête et les oreilles.\u003c/p\u003e\n\u003cp\u003eUn classique ne pas manquer.\u003c/p\u003e"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-09T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/france-damour-nouveau-spectacle/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Spectacle en salle\"]"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_france-damour-nouveau-spectacle#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/france-damour-nouveau-spectacle/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:15:07.967-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/france-damour-nouveau-spectacle/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_france-damour-nouveau-spectacle#Offer_0","http://schema.org/price":[{"@value":"47.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_breen-leboeuf-mes-blues-passent-pu-dans-porte","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/breen-leboeuf-mes-blues-passent-pu-dans-porte/"}],"http://schema.org/name":[{"@language":"fr","@value":"Mes blues passent pu dans' porte"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Breen.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eEn plus d’être l’interprète d’un des plus grands succès de notre histoire musicale, \u003cstrong\u003eBreen LeBoeuf\u003c/strong\u003e est pour plusieurs considéré comme l’une des plus belles voix du Québec. \u003c/p\u003e\n\u003cp\u003e Cette légende du Rock provoque l’enthousiasme d’un public charmé, voir impressionné, par sa performance vocale, l’unique signature de chacune de ses interprétations, ses anecdotes inédites et cette solide énergie qui continue de l’habiter après plus de 55 ans de carrière. \u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e MES BLUES PASSENT PU DANS’ PORTE \u003c/strong\u003enous replonge dans cette époque marquée par le groupe culte Offenbach dont Breen a été l’un des principaux artisans. Pour l’occasion, il a le plaisir et l’honneur de s’entourer de quelques-uns de nos meilleurs musiciens/chanteurs: Réjean Lachance à la guitare, Pierre Beauregard au clavier et sax et Marc Brodeur aux drums. \u003c/p\u003e\n\u003cp\u003e C’est donc en leur compagnie que Breen revisite pour vous les plus grands classiques d’Offenbach et célèbre avec vous ce groupe qui nous a laissé un des plus riches répertoires musicaux d’ici.\u003c/p\u003e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-03T19:30:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/breen-leboeuf-mes-blues-passent-pu-dans-porte/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_breen-leboeuf-mes-blues-passent-pu-dans-porte#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/breen-leboeuf-mes-blues-passent-pu-dans-porte/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:15:13.796-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/breen-leboeuf-mes-blues-passent-pu-dans-porte/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_breen-leboeuf-mes-blues-passent-pu-dans-porte#Offer_0","http://schema.org/price":[{"@value":"50.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_charles-brunet-tres-tres-bon-show","@type":["http://schema.org/Event"],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Charles-Brunet.jpg"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eJ’ai 24 ans. J’suis extra vieux. Vraiment full mature pis profond.\u003c/p\u003e\n\u003cp\u003eJ’comprends un peu rien à la vie mais j’commence à comprendre mon métier.\u003c/p\u003e\n\u003cp\u003eLe nom le dit, c’est un très très bon show. \u003c/p\u003e\n\u003cp\u003eÇa parle d’un peu de tout. Ça parle à tout le monde. Vieux, jeune. Tiktok, Msn. Gauche, Droite. Bonjour, hi. C’est surtout fucking drôle. \u003c/p\u003e\n\u003cp\u003eC’est l'œuvre dont je suis le plus fier de ma jeune carrière.\u003c/p\u003e\n\u003cp\u003eMerci aux gens qui me permettent de continuer à vivre mon rêve de p'tit cul. Je vous le devais et le voici, un très très bon show.\u003c/p\u003e"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/charles-brunet-tres-tres-bon-show/"}],"http://schema.org/name":[{"@language":"fr","@value":"Très Très bon show"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-14T19:30:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/charles-brunet-tres-tres-bon-show/#WebPage"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_charles-brunet-tres-tres-bon-show#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/charles-brunet-tres-tres-bon-show/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-12T10:18:55.568-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/charles-brunet-tres-tres-bon-show/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_charles-brunet-tres-tres-bon-show#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"42.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_souldia-non-conventionnel","@type":["http://schema.org/Event"],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"},{"@id":"http://kg.footlight.io/resource/a72d7b0a-b597-43c4-b2c3-2be59adfb0e0"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/souldia-non-conventionnel/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Rap / Hip hop / R\u0026B\", \"Spectacle en salle\"]"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-14T20:00:00-04:00"}],"http://schema.org/name":[{"@language":"fr","@value":"Nouvelle vie"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Souldia.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003csub\u003eSpectacle festif : les gens seront debout dès la première chanson! Choisissez les premières rangées si vous aimez vous lever, chanter et danser, sinon optez pour un siège plus reculé.\u003c/sub\u003e\u003c/p\u003e\n\u003cp\u003eAprès une année non conventionnelle et chargée en émotions, \u003cstrong\u003eSouldia\u003c/strong\u003e est plus déterminé que jamais à présenter son nouveau show. Haut en couleurs et plus percutant que jamais, le rappeur ose en nous proposant un spectacle énergique complètement hors du commun, passant par des sonorités rock’n’roll, voire même punk, bien agencées à son style cru. Souldia est au sommet de sa forme et sûr de lui pour un nouveau départ sur scène ahurissant!\u003c/p\u003e"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/souldia-non-conventionnel/"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_souldia-non-conventionnel#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/souldia-non-conventionnel/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:20:30.474-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/souldia-non-conventionnel/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_souldia-non-conventionnel#Offer_0","http://schema.org/price":[{"@value":"48.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_lharmonie-vivace-vivace-et-compagnie-ll","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/lharmonie-vivace-vivace-et-compagnie-ll/"}],"http://schema.org/name":[{"@language":"fr","@value":"Vivace et Compagnie ll"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Locations-TMC/Visuel_Harmonie-Laval.png"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T14:30:00-05:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/keywords":[{"@value":"[\"Musique classique\", \"Spectacle en salle\"]"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-213"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cstrong\u003eL'Harmonie Vivace \u003c/strong\u003ereçoit des invités spéciaux pour son prochain concert! Ce spectacle grand public et familial réunira plusieurs disciplines artistiques qui accompagneront les musiciens avec des prestations hautes en couleur. Au programme: EnKdanse, Yannick Lacroix (magicien), Myriam Adam (flûtiste), Philippe Gaglione (artiste du cirque), Théâtre Fêlé, ainsi que l'Harmonie Aurora. Ne manquez pas cette célébration de la culture et du talent lavallois!\u003c/p\u003e"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/lharmonie-vivace-vivace-et-compagnie-ll/#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/f56c4b49-240a-447d-8212-5f0c6333e42a"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_lharmonie-vivace-vivace-et-compagnie-ll#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/lharmonie-vivace-vivace-et-compagnie-ll/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-30T10:22:03.608-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/lharmonie-vivace-vivace-et-compagnie-ll/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_lharmonie-vivace-vivace-et-compagnie-ll#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"35.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_veronic-dicaire-nouveau-spectacle","http://schema.org/name":[{"@language":"fr","@value":"Nouveau spectacle"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/veronic-dicaire-nouveau-spectacle/"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Spectacle en salle\"]"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-05T20:00:00-04:00"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Veronic_Dicaire_DSC0612_HR_V2-%E2%80%93-grande-taille.jpeg"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eVéronic DiCaire –de retour avec son TOUT NOUVEAU SPECTACLE\u003c/p\u003e\n\u003cp\u003eSuite au succès de son dernier spectacle –qui a remporté le Félix du Spectacle de l’Année à l’ADISQ -Véronic est enfin de retour avec un tout nouveau spectacle solo ! L’artiste aux multiples talents a la goût de se faire plaisir et propose au public un spectacle musical rempli des plus grands succès et des plus grandes voix. Il y aura assurément de l’humour et des imitations, mais Véronic veut mettre cette fois-ci les émotions au premier plan. Elle nous prépare donc un spectacle qui lui ressemble et qui promet de nous faire vivre une soirée exceptionnelle.\u003c/p\u003e"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/veronic-dicaire-nouveau-spectacle/#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_veronic-dicaire-nouveau-spectacle#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-06T15:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_veronic-dicaire-nouveau-spectacle#2026-09-05T200000-0400"},{"@id":"http://kg.footlight.io/resource/co-motion-ca_veronic-dicaire-nouveau-spectacle#2026-09-06T150000-0400"}]},{"@id":"https://co-motion.ca/spectacles/veronic-dicaire-nouveau-spectacle/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:13:10.310-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/veronic-dicaire-nouveau-spectacle/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_veronic-dicaire-nouveau-spectacle#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"78.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_veronic-dicaire-nouveau-spectacle#2026-09-05T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Nouveau spectacle"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-05T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_veronic-dicaire-nouveau-spectacle#2026-09-06T150000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Nouveau spectacle"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-06T15:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_andre-sauve-nouveau-spectacle","http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-04T19:30:00-05:00"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/andre-sauve-nouveau-spectacle/"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cstrong\u003eAndré Sauvé\u003c/strong\u003e fait un retour sur scène avec une création audacieuse où son regard unique sur le monde puise son inspiration dans la nature et ses mystères. \u003c/p\u003e\n\u003cp\u003eIl nous livre ses observations dont chaque détail prend une dimension surprenante, ne voyant dans cette nature qui l'entoure qu’une métaphore de l’esprit humain. \u003c/p\u003e\n\u003cp\u003eÀ travers ce voyage, il tisse un lien entre humour et poésie, explorant l’âme humaine à travers le prisme de son environnement. \u003c/p\u003e\n\u003cp\u003eHumour et réflexion s’unissent dans cette expérience fidèle à la touche et au talent inimitable d’André Sauvé.\u003cbr /\u003e\u003c/p\u003e\n\u003cp\u003eMise en scène : \u003cstrong\u003eLouis-Karl Tremblay\u003cbr /\u003e\u003c/strong\u003eDirection artistique : \u003cstrong\u003ePierre Bernard \u003c/strong\u003e\u003c/p\u003e"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/name":[{"@language":"fr","@value":"Naturellement"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Andr%C3%A9-Sauv%C3%A9-2.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/andre-sauve-nouveau-spectacle/#WebPage"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_andre-sauve-nouveau-spectacle#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-17T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_andre-sauve-nouveau-spectacle#2026-03-04T193000-0500"},{"@id":"http://kg.footlight.io/resource/co-motion-ca_andre-sauve-nouveau-spectacle#2026-10-17T200000-0400"}]},{"@id":"https://co-motion.ca/spectacles/andre-sauve-nouveau-spectacle/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:13:49.016-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/andre-sauve-nouveau-spectacle/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_andre-sauve-nouveau-spectacle#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"59.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_andre-sauve-nouveau-spectacle#2026-03-04T193000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Naturellement"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-04T19:30:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_andre-sauve-nouveau-spectacle#2026-10-17T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Naturellement"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-17T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_bruno-pelletier-3-et-moi","@type":["http://schema.org/Event"],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Spectacle en salle\"]"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-10T20:00:00-04:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/bruno-pelletier-3-et-moi/"}],"http://schema.org/name":[{"@language":"fr","@value":"3 et moi"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cstrong\u003eBruno Pelletier\u003c/strong\u003e, l’un des chanteurs les plus appréciés de la francophonie, revient sur scène avec une nouvelle tournée intitulée « Trois et moi ». Ce spectacle, conçu comme un moment de partage privilégié, plonge le public dans un univers musical intime, où l’artiste se dévoile avec authenticité. Entouré de trois musiciens talentueux, Bruno Pelletier propose une interprétation dépouillée de ses plus grands succès et de chansons coups de cœur, choisies avec soin pour cette aventure unique.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e3 \u0026amp; MOI\u003c/strong\u003e \u003c/p\u003e\n\u003cp\u003eUne expérience musicale où simplicité et profondeur se rencontrent. \u003c/p\u003e\n\u003cp\u003eDans ce format épuré, Bruno Pelletier met de l’avant la force des harmonies vocales, des mélodies recherchées, des arrangements délicats, et surtout, une proximité sincère avec son public. \u003c/p\u003e\n\u003cp\u003eBruno vous invite à un rendez-vous convivial, comme une soirée entre amis, où l’on célèbre ensemble le plaisir de la musique. Un moment suspendu dans le temps pour chanter, rire et vibrer. \u003c/p\u003e\n\u003cp\u003eCe tout nouveau spectacle revisite les morceaux phares qui ont marqué sa carrière, mais aussi dévoile des chansons plus intimes, choisies pour leur résonance particulière dans son parcours personnel. Pour les fans, c’est une occasion rêvée de redécouvrir certains titres dans un écrin nouveau, où chaque note résonne avec une émotion pure. \u003c/p\u003e\n\u003cp\u003eCe spectacle intime de Bruno Pelletier nous rappelle l’importance des instants partagés, où la musique devient un langage universel et un lien précieux entre l’artiste et son public.\u003c/p\u003e"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Bruno-Pelletier.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/bruno-pelletier-3-et-moi/#WebPage"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_bruno-pelletier-3-et-moi#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/bruno-pelletier-3-et-moi/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:22:19.226-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/bruno-pelletier-3-et-moi/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_bruno-pelletier-3-et-moi#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"58.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_paul-a-la-maison","@type":["http://schema.org/Event"],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/paul-a-la-maison/#WebPage"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/paul-a-la-maison/"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eMaintenant dans la cinquantaine, Paul est auteur de bande dessinée à temps plein et lance un nouvel ouvrage au Salon du livre de Montréal. Depuis son divorce, il vit seul dans sa maison avec son chien Biscuit. Avec le départ imminent de sa fille pour Londres et l’état de santé de sa mère qui se dégrade, Paul sent bien que le temps l’a rattrapé. À travers les aléas de la vieillesse et du célibat, il est alors confronté au deuil, sous de multiples formes. \u003cbr /\u003e\u003c/p\u003e"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Paul-%C3%A0-la-maison.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-22T19:30:00-04:00"}],"http://schema.org/name":[{"@language":"fr","@value":"Paul à la maison"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/keywords":[{"@value":"[\"Théâtre\", \"Spectacle en salle\"]"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_paul-a-la-maison#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/paul-a-la-maison/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:22:34.772-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/paul-a-la-maison/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_paul-a-la-maison#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"60.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_moi-ma-mere-me-racontait","@type":["http://schema.org/Event"],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/moi-ma-mere-me-racontait/#WebPage"}],"http://schema.org/name":[{"@language":"fr","@value":"Moi, ma mère me racontait"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-116"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/moi-ma-mere-me-racontait/"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/MDA/ECC/Marta-Saenz-de-la-Calzada.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cstrong\u003eRécits de migration\u003cbr /\u003e5 à 7 Conte – Grand public (à partir de 13 ans)\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cem\u003eMoi, ma mère me racontait\u003c/em\u003e relate des histoires mélangeant récits de vie d’immigrants vivant en Abitibi et contes traditionnels de leur pays d’origine. Outre une présentation sur l’Abitibi-Témiscamingue, Marta Saenz de la Calzada vous amène en Yougoslavie avec \u003cem\u003eL’homme de la maison\u003c/em\u003e, au Vietnam avec \u003cem\u003eLa fille rebelle\u003c/em\u003e, au Cameroun avec \u003cem\u003eLe lion, le singe et la hyène\u003c/em\u003e, en Colombie avec \u003cem\u003eJuanito y los paisas\u003c/em\u003e, en Haïti avec \u003cem\u003eLa commère qui parlait trop fort\u003c/em\u003e, en Inde avec \u003cem\u003eLumières de l’Inde\u003c/em\u003e pour terminer son récit avec un conte traditionnel juif, \u003cem\u003eLe bouton\u003c/em\u003e.\u003cbr /\u003e\u003c/p\u003e\n\u003cp\u003e\u003cbr /\u003eLa narration de ces contes est l’occasion de présenter six portraits de néo-québécois qui contribuent chacun à leur façon à bâtir le Québec de demain. Comment leurs histoires enrichissent la culture du pays d'accueil? Comment ces imaginaires se métissent-ils avec celui du Québec?\u003c/p\u003e\n\u003cp\u003e« Par ce spectacle, je voudrais répondre à un besoin social imposé par la croissance constante de l'immigration et l'incompréhension et la peur qu'elle origine. Le besoin de connaître et de s'ouvrir à l'autre devient important et criant dans la société d'accueil. » \u003cstrong\u003eMarta Saenz de la Calzada\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eSpectacle présenté avec la collaboration de \u003ca href=\"https://conte.quebec/circuit-paroles-vivantes/\" target=\"_blank\" rel=\"noreferrer noopener\"\u003eCircuits Paroles Vivantes\u003c/a\u003e.\u003cbr /\u003eDurée: 90 minutes\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://www.laval.ca/calendrier-activites/conte-mere-racontait-ecc/\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eEspace citoyen des Confluents\u003c/a\u003e\u003cstrong\u003e\u003cbr /\u003e\u003c/strong\u003e\u003c/p\u003e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-11T17:00:00-04:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/f614a85a-8bc9-4151-8d6b-494849be0eb3"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-1056"}],"http://schema.org/keywords":[{"@value":"[\"Conte\", \"Spectacle en salle\", \"Admission générale\"]"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_moi-ma-mere-me-racontait#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/moi-ma-mere-me-racontait/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:11:41.655-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/moi-ma-mere-me-racontait/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_moi-ma-mere-me-racontait#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"20.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_gc-echos-mozart-brahms-selon-rivest","@type":["http://schema.org/Event"],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/f56c4b49-240a-447d-8212-5f0c6333e42a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-13T19:30:00-04:00"}],"http://schema.org/keywords":[{"@value":"[\"Musique classique\", \"Spectacle en salle\"]"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/gc-echos-mozart-brahms-selon-rivest/#WebPage"}],"http://schema.org/name":[{"@language":"fr","@value":"Les Grands Concerts"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K5-431"},{"@id":"http://kg.artsdata.ca/resource/K10-533"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/gc-echos-mozart-brahms-selon-rivest/"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/OSL/GC-2025-26/OSL_COMOTION_SITE_WEB_GC5.jpg"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003ePour clore les festivités de son 40ème anniversaire, l’OSL accueillera l’un de ses anciens directeurs artistiques, \u003cstrong\u003eJean-François Rivest\u003c/strong\u003e, à la barre de l’Orchestre. À la tête de l’OSL de 1995 à 2005, il a contribué à façonner son identité musicale en l’amenant à explorer les grandes œuvres du répertoire symphonique. Pour l’occasion, Rivest vous réserve deux chefs d’œuvre emblématiques qu’il a chéris pendant son passage à l’Orchestre : la \u003cstrong\u003eSymphonie n°41 « Jupiter » \u003c/strong\u003ede \u003cstrong\u003eMozart\u003c/strong\u003e, dont l’enregistrement a valu à l’OSL un Félix à l’ADISQ en 2000, et la \u003cstrong\u003eSymphonie n°4\u003c/strong\u003e de \u003cstrong\u003eBrahms\u003c/strong\u003e. Une soirée qui fera, sans nul doute, écho au passé, entre mémoire et célébration.\u003c/p\u003e\n\n\u003cp\u003e\u003cstrong\u003eChef d’orchestre\u003c/strong\u003e: Jean-François Rivest\u003c/p\u003e\n\n\u003cp\u003e\u003cstrong\u003eRépertoire\u003c/strong\u003e :\u003cbr /\u003e\u003cstrong\u003eWolfgang Amadeus Mozart\u003c/strong\u003e, Symphonie nº 41 en do majeur, K 511, « Jupiter »\u003cstrong\u003e\u003cbr /\u003eJohannes Brahms\u003c/strong\u003e, Symphonie nº 4 en mi mineur, op. 98\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_gc-echos-mozart-brahms-selon-rivest#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/gc-echos-mozart-brahms-selon-rivest/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:14:31.908-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/gc-echos-mozart-brahms-selon-rivest/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_gc-echos-mozart-brahms-selon-rivest#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"65.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_les-boys","http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/les-boys/"}],"http://schema.org/name":[{"@language":"fr","@value":"Les Boys"}],"http://schema.org/keywords":[{"@value":"[\"Théâtre\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp dir=\"ltr\"\u003eEn 1997, le monde entier n’en a que pour Titanic. Le monde entier au grand complet ? Non, car au Québec, défiant tous les pronostics, une comédie sur le hockey lui tient tête au sommet du box-office!\u003c/p\u003e\n\u003cp dir=\"ltr\"\u003eRappelons l’histoire pour ceux et celles qui ne la connaîtraient pas. Propriétaire d’une brasserie et entraîneur d’une équipe de hockey très, très amateure, Stan perd une somme d’argent colossale en jouant au poker contre Méo, un Shylock qu’il considère comme son ami.\u003c/p\u003e\n\u003cp dir=\"ltr\"\u003ePour que Stan puisse s’acquitter de sa dette, Méo a une idée : ils vont jouer ça quitte ou double sur la glace, dans le cadre d’une partie de hockey. Si l’équipe de Stan gagne, on efface la dette. Mais si elle perd… Méo devient le propriétaire de sa précieuse brasserie. \u003c/p\u003e\n\n\u003cp\u003eTandis que Méo réunit une bande d’armoires à glace et de repris de justice, Stan ne peut compter que sur ses boys. Son équipe est composée d’hommes provenant de différents horizons, que rien n’unit à part peut-être leur amour du hockey et la bière qu’ils boivent ensemble après les games…\u003c/p\u003e\n\n\u003cp dir=\"ltr\"\u003ePour aller au secours de celui qui est plus que leur coach, ils devront mettre de côté leurs différends et apprendre le véritable sens de l’amitié.\u003cbr /\u003e\u003c/p\u003e\n\u003cp dir=\"ltr\"\u003ePlus de 25 ans après sa sortie, Guillaume Corbeil et Marc St-Martin revisitent le film de Louis Saia en le portant à la scène. C’est l’occasion de retrouver l’équipe de hockey la plus célèbre du Québec (OK, peut-être la deuxième plus célèbre…) et de se rappeler l’importance de la dureté du mental.\u003cbr /\u003e\u003c/p\u003e"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Les-Boys.jpg"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-08T19:30:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/les-boys/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_les-boys#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-09T19:30:00-05:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_les-boys#2026-01-08T193000-0500"},{"@id":"http://kg.footlight.io/resource/co-motion-ca_les-boys#2026-01-09T193000-0500"}]},{"@id":"https://co-motion.ca/spectacles/les-boys/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-08T10:24:57.946-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/les-boys/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_les-boys#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"76.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_les-boys#2026-01-08T193000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Les Boys"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-08T19:30:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_les-boys#2026-01-09T193000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Les Boys"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-09T19:30:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_mona-de-grenoble","@type":["http://schema.org/Event"],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Drag\", \"Spectacle en salle\"]"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"De la poudre aux yeux"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-06-27T20:00:00-04:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/sameAs":[{"@value":"https://www.facebook.com/profile.php?id=100070401769309"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/mona-de-grenoble/"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/mona-de-grenoble/#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eJ’aurais pu appeler ce show d’humour-là \u003cem\u003eÉtalon en talons\u003c/em\u003e ou \u003cem\u003eEntre Bâton Rouge et matante,\u003c/em\u003e mais après j’ai désaoulé pis je me suis rendu compte que vous allez sûrement vous reconnaître dans mon personnage que certains décrivent comme étant “flamboyant, grivois et solvable depuis Big Brother”. Parce que, dans le fond, on le sait, les paillettes, les robes pis les grosses perruques c’est juste «de la poudre aux yeux».\u003c/p\u003e\n\u003cp\u003e\u003c/p\u003e\n\n\n\n\u003cp\u003e\u003ca class=\"notice\"\u003eSuivre l'artiste\u003cbr /\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://www.instagram.com/monadegrenoble/?hl=fr\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eInstagram\u003c/a\u003e \u003ca href=\"https://www.facebook.com/profile.php?id=100070401769309\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eFacebook\u003c/a\u003e\u003c/p\u003e"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Mona-de-Grenoble_2024.jpg"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_mona-de-grenoble#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/mona-de-grenoble/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:11:09.450-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/mona-de-grenoble/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_mona-de-grenoble#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"46.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_diane-tell-ma-tournee-50-iaime-anniversaire","@type":["http://schema.org/Event"],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Chanson francophone\", \"Spectacle en salle\"]"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/diane-tell-ma-tournee-50-iaime-anniversaire/"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-14T19:30:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/diane-tell-ma-tournee-50-iaime-anniversaire/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e« Je n’aime pas les anniversaires. Noël, encore moins... et je suis née pendant le réveillon ! Entre la dinde et les cadeaux. J’aurais pu finir dans la crèche avec le petit Jésus ! Exactement comme Zac dans le film C.R.A.Z.Y. de notre si cher et regretté Jean-Marc Vallée. Chez Bonsound, notre belle maison de concerts, quelqu’un a lancé l’idée : « Diane, ça te dirait de faire une tournée anniversaire ?» Je n’ai pas dit oui tout de suite. Bon. Un 50 i’aime. Quand même. Ça se fête ! Et puis, on sera tranquille. Il n’y aura pas de 100 i’aime ! J’ai mordu à l’hameçon comme un beau doré du lac Blouin...et j’ai signé. Déjà plusieurs concerts programmés au Québec, en Ontario, au Nouveau-Brunswick. C’est fantastique.\u003c/p\u003e\n\u003cp\u003eEn 1977, je lançais mon premier album. Celui où je pose sur la pochette avec Raggedy Ann dans les bras, le pouce dans la bouche et les jambes écartées ! Ah ! J’étais Polissonne à 19 ans. Je le suis toujours un peu. Polissonne, c’est aussi le titre de mon prochain album. Ça promet. Je tournais déjà en 1976. Je chantais partout, seule avec ma guitare : dans les bars, les restaurants, au Maples Inn à Pointe-Claire, à l’Évêché de l’Hôtel Nelson dans le Vieux-Montréal. J’ai même chanté au Forum, en première partie de Chris de Burgh, à l’époque.\u003c/p\u003e\n\u003cp\u003ePour cette série de concerts, je ne serai pas seule. Un extraordinaire band québécois cinq étoiles, dirigé par l’unique et talentueux Julien Fillion, prendra la route à mes côtés. Quelle chance. Le répertoire — puisé dans quinze albums — choisi pour notre bonheur et le vôtre, sera sublimé par le talent de ces jeunes musiciens. Alors... on se donne le mot ? Rendez-vous compte : 50 ans de musique, de chansons, de scène, non-stop. Mais c’est surtout votre fidélité qui mérite d’être célébrée. Allez. C’est ma tournée. Le 50 i’aime anniversaire de notre rencontre. Tu te souviens ?»\u003c/p\u003e"}],"http://schema.org/name":[{"@language":"fr","@value":"Ma Tournée 50 i'aime anniversaire"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Diane-Tell.jpg"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"},{"@id":"http://kg.artsdata.ca/resource/Chansonfrancophone"},{"@id":"http://kg.artsdata.ca/resource/ChansonPerformance"},{"@id":"http://kg.artsdata.ca/resource/Chanson"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_diane-tell-ma-tournee-50-iaime-anniversaire#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/diane-tell-ma-tournee-50-iaime-anniversaire/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:13:33.613-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/diane-tell-ma-tournee-50-iaime-anniversaire/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_diane-tell-ma-tournee-50-iaime-anniversaire#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"49.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_richardson-zephir-punch-creole","http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/richardson-zephir-punch-creole/"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-28T20:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/richardson-zephir-punch-creole/#WebPage"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003ePunch créole, le 2e one-man-show de l’humoriste \u003cstrong\u003eRichardson Zéphir\u003c/strong\u003e, n’est pas qu’un simple spectacle : c’est une célébration où la rigolade, la musique et l’improvisation se rencontrent dans une ambiance aussi chaleureuse que festive. \u003c/p\u003e\n\u003cp\u003eAvec son charisme naturel et sa personnalité attachante, Richardson se déploie sur scène avec ses mimiques, son jeu physique éclatant et ses changements de voix qui donnent à ses anecdotes savoureuses une couleur unique et hilarante. \u003c/p\u003e\n\u003cp\u003eCe spectacle vivant et authentique, à l’image de Richardson, charmera autant les amateurs de stand-up que les fans d’improvisation et les téléspectateurs qui l’ont adoré dans LOL : Qui rira le dernier? ou Big Brother Célébrités. \u003c/p\u003e\n\u003cp\u003eLaissez-vous emporter par l’énergie débordante de Richardson Zéphir et ses blagues livrées comme un bon punch créole, qui vous laisse le sourire aux lèvres et le cœur léger. \u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003ePunch créole : des saveurs d’impro aux arômes de stand-up pur!\u003c/strong\u003e\u003c/p\u003e"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Richardson-Z%C3%A9phir.jpg"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Punch Créole"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_richardson-zephir-punch-creole#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-22T19:30:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_richardson-zephir-punch-creole#2026-02-28T200000-0500"},{"@id":"http://kg.footlight.io/resource/co-motion-ca_richardson-zephir-punch-creole#2026-10-22T193000-0400"}]},{"@id":"https://co-motion.ca/spectacles/richardson-zephir-punch-creole/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:13:58.860-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/richardson-zephir-punch-creole/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_richardson-zephir-punch-creole#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"34.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_richardson-zephir-punch-creole#2026-02-28T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Punch Créole"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-28T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_richardson-zephir-punch-creole#2026-10-22T193000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Punch Créole"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-22T19:30:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_les-eternels-pigistes-flambant-nue","@type":["http://schema.org/Event"],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-03T19:30:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/les-eternels-pigistes-flambant-nue/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/les-eternels-pigistes-flambant-nue/"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eFlambant nue plonge au cœur d’une question universelle : quelle est notre place dans l'univers? Cette interrogation, aussi vieille que l'humanité elle-même, est exacerbée par les récentes avancées scientifiques, notamment la fascinante théorie des univers multiples. Au bout de ce parcours, est-ce que nos personnages trouvent une réponse à cette question?\u003c/p\u003e\n\n\u003cp\u003ePortée par un chœur de personnages vibrants et attachants, ce texte de \u003cstrong\u003ePierre-Michel Tremblay\u003c/strong\u003e mêle profondeur et humour à travers une série de tableaux qui reflètent nos différentes identités dans des situations qui résonnent avec des enjeux de notre époque.\u003c/p\u003e\n\n\u003cp\u003eUne rencontre unique sur scène avec quatre virtuoses de l’interprétation : \u003cstrong\u003eChristian Bégin, Marie Charlebois, Isabelle Vincent\u003c/strong\u003e et \u003cstrong\u003ePier Paquette\u003c/strong\u003e. Ensemble, ils donnent vie à un spectacle touchant, drôle et captivant qui invite à réfléchir… et à rêver.\u003c/p\u003e"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/flambantnue_afficheofficielle_web_blanc_rgb_vf_1.jpg"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"[\"Théâtre\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/name":[{"@language":"fr","@value":"Flambant nue"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_les-eternels-pigistes-flambant-nue#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/les-eternels-pigistes-flambant-nue/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:14:23.343-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/les-eternels-pigistes-flambant-nue/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_les-eternels-pigistes-flambant-nue#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"57.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_histoires-pour-faire-des-cauchemars","@type":["http://schema.org/Event"],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-22T14:00:00-04:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/22f50ba1-3403-48a8-a279-460952d3c134"},{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-77"}],"http://schema.org/keywords":[{"@value":"[\"Jeune public\", \"Théâtre\", \"Spectacle en salle\"]"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Histoires pour faire des cauchemars"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/944120553"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/MDA/Programmation-ville/Jeune-Public/2025-2026/Histoires-pour-faire-des-cauchemars.jpg"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003csub\u003eThéâtre La Bête Humaine \u003c/sub\u003e\u003c/p\u003e\n\u003ch3\u003eEt si on jouait à se faire peur? \u003c/h3\u003e\n\u003cp\u003ePlutôt que de dormir quand arrive le soir, Damien et Patricia « deux petits vauriens à qui on n’apprend rien », préfèrent se raconter des histoires. Des histoires noires et ludiques échangées comme au ping-pong pour mettre en jeu leurs peurs et s’inventer un ailleurs. \u003c/p\u003e\n\u003cp\u003eDans une langue mordante, lyrique, châtiée et provocante, les enfants laissent libre cours à leurs excentricités. Servies par un duo d’acteurs complices, ces histoires d’une cruauté légère et savoureuse, entrecoupées de chansons, alternent entre tensions et appels d’air, mystères et absurdités. \u003c/p\u003e\n\u003cp\u003eCette création aux accents d’insomnie espiègle et au parfum d’enfance fleurant bon la fraternité joue malicieusement avec le noir, les monstres et le pouvoir infini de l’imagination.\u003c/p\u003e\n\u003cp\u003eLes créations de \u003cstrong\u003eLa Bête Humaine\u003c/strong\u003e reposent essentiellement sur des acteurs qui interprètent des textes dramatiques d'auteurs qui souhaitent émettre un point de vue critique sur un sujet de société qui les préoccupe.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eDurée : 45 minutes\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e\u003cstrong\u003eProfitez de votre venue à la Maison des arts pour visiter les expositions en cours à la \u003ca href=\"https://www.laval.ca/culture/maison-des-arts/expositions-salle-alfred-pellan/expositions/\" target=\"_blank\" rel=\"noreferrer noopener\"\u003eSalle Alfred Pellan\u003c/a\u003e.\u003c/strong\u003e\u003cbr /\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"http://theatrelabetehumaine.com\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eSite Web du Théâtre La Bête Humaine\u003c/a\u003e \u003ca href=\"https://www.laval.ca/calendrier-activites/histoires-pour-faire-cauchemars-mda-theatre-jeune-public/\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eMaison des arts de Laval\u003c/a\u003e\u003cbr /\u003e\u003c/p\u003e"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/histoires-pour-faire-des-cauchemars/"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/histoires-pour-faire-des-cauchemars/#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_histoires-pour-faire-des-cauchemars#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/histoires-pour-faire-des-cauchemars/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:14:17.474-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/histoires-pour-faire-des-cauchemars/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_histoires-pour-faire-des-cauchemars#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"17.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_jose-gaudet-nouveau-spectacle","@type":["http://schema.org/Event"],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/b3e28c59-2c93-4210-ab72-47d8555ec99d"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/name":[{"@language":"fr","@value":"Y'était temps"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/Cxozi-nIqvA"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-23T20:00:00-05:00"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/jose-gaudet-nouveau-spectacle/#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eHumoriste, \u003cstrong\u003eJosé Gaudet\u003c/strong\u003e nous fait rire depuis déjà très longtemps ! Il a à son actif plus de 25 ans d’animation radio, deux tournées de spectacles bien récompensées, animation de galas et 10 ans à la barre de talks-shows dont Ça finit bien la semaine. \u003c/p\u003e\n\u003cp\u003eJosé n’arrivera pas sur la pointe des pieds, il prépare un spectacle de stand up pur et dur. Il abordera sa vision des choses sur la vie d’aujourd’hui et ses réflexions sur la société avec son humour authentique, son franc parlé et son énergie éclatante que nous connaissons bien. \u003c/p\u003e\n\u003cp\u003eÀ 53 ans, il arrive avec son premier one man-show. \u003c/p\u003e\n\u003cp\u003eC’est le cas de le dire… Y’était temps! \u003c/p\u003e"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/jose-gaudet-nouveau-spectacle/"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Jos%C3%A9-Gaudet_2025.jpg"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_jose-gaudet-nouveau-spectacle#Offer_0"}]},{"@id":"http://kg.footlight.io/resource/b3e28c59-2c93-4210-ab72-47d8555ec99d","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"José Gaudet"}],"http://schema.org/hasOccupation":[{"@language":"fr","@value":"Humoriste"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}],"http://schema.org/sameAs":[{"@value":"http://kg.artsdata.ca/resource/K5-1035"}]},{"@id":"https://co-motion.ca/spectacles/jose-gaudet-nouveau-spectacle/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-22T10:19:00.386-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/jose-gaudet-nouveau-spectacle/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_jose-gaudet-nouveau-spectacle#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"53.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_mario-tessier-nouveau-spectacle","@type":["http://schema.org/Event"],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/mario-tessier-nouveau-spectacle/#WebPage"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/mario-tessier-nouveau-spectacle/"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-18T19:30:00-04:00"}],"http://schema.org/name":[{"@language":"fr","@value":"Champion"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eL’humoriste Mario Tessier présente son 3e one-man-show intitulé Champion, alors qu’il est de retour avec l’énergie explosive qu’on lui connait. Champion, c’est un concentré d’humour irrésistible qui mélange autodérision et situations improbables. \u003c/p\u003e\n\u003cp\u003eDans ce spectacle, Mario affronte ses propres contradictions avec une dose de folie : suivre un entraînement de boxe professionnel juste après une séance de botox, devenir propriétaire d’une jument alors qu'il n’est jamais monté à cheval ou encore se prendre pour un génie alors que… bon, vous voyez le tableau! \u003c/p\u003e\n\u003cp\u003eAvec Champion, Mario met en lumière ce « grand champion » qui sommeille en chacun de nous, celui qui nous pousse à viser plus haut… ou à nous ridiculiser de la plus drôle des façons. Si vous l’aimez à la radio sur Énergie, attendez de le voir sur scène! Il vous fera encore plus rire grâce à son énergie contagieuse, son efficacité comique légendaire et cette tête de vainqueur qu’on ne peut qu’aimer!\u003c/p\u003e"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Mario-Tessier-2025.jpg"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_mario-tessier-nouveau-spectacle#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/mario-tessier-nouveau-spectacle/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:18:35.770-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/mario-tessier-nouveau-spectacle/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_mario-tessier-nouveau-spectacle#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"47.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_clement-jacques-iris","@type":["http://schema.org/Event"],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/clement-jacques-iris/#WebPage"}],"http://schema.org/name":[{"@language":"fr","@value":"Iris"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/clement-jacques-iris/"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-08T19:30:00-04:00"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cstrong\u003eClement Jacques présente le spectacle IRIS qui fait suite à la sortie de son 5e album en carrière.\u003c/strong\u003e\u003c/p\u003e\n\n\u003cp\u003eUn album qualifié d’exceptionnel de par la qualité de ses textes, de sa musique, de ses interprétations et de sa réalisation si bien menée par David Laflèche. À l’image de ce qui est véhiculé sur l’album, Clement partage sur scène autant de réflexions, révélant ainsi avec sensibilité sa vision des relations humaines et du monde qui l’entoure. À la fois simple et sincère, ce spectacle mis en scène par Daniel Fortinest résolument ancré dans son époque et présente un amalgame harmonieux de nouveau et d’anciens répertoires de l’artiste.\u003c/p\u003e"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Cl%C3%A9ment-jaques.jpg"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Spectacle en salle\"]"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_clement-jacques-iris#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/clement-jacques-iris/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:19:52.445-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/clement-jacques-iris/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_clement-jacques-iris#Offer_0","http://schema.org/price":[{"@value":"38.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_ariane-roy-dogue","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/ariane-roy-dogue/"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/8tUxjQ9MNCU"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/ariane-roy-dogue/#WebPage"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Spectacle en salle\", \"Admission générale debout\"]"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cstrong\u003eCouronnée Révélation de l’année au Gala de l’ADISQ 2022 \u003c/strong\u003eet nommée dans la catégorie Album francophone de l’année aux Prix JUNO 2023, \u003cstrong\u003eAriane Roy\u003c/strong\u003e s’inscrit dans cette nouvelle vague d’artistes qui redéfinissent la chanson québécoise. \u003c/p\u003e\n\u003cp\u003eSuite à l’éclatante série de concerts Le Roy, La Rose et le Lou[p] qui a brillé partout dans la province dans les deux dernières années ainsi qu'à sa première tournée medium plaisir, elle revient sur scène avec son projet solo en nous présentant son deuxième album Dogue, co-réalisé avec Félix Petit. Avec la fougue qu’on lui connaît, elle se montre à nous cette fois sous des jours plus sombres, le verbe plus mordant, la voix plus caustique, dans une lumière résolument contemporaine. Dogue montre les dents, oui pour sourire, mais aux dépends de qui ? Peu nous importe puisque l’on ne peut résister à se coucher devant les crocs, espérant se joindre à la meute.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eSuivre l'artiste \u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://www.arianeroy.ca/\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eSite Web\u003c/a\u003e \u003ca href=\"https://www.instagram.com/arianebenoui/?hl=fr\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eInstagram\u003c/a\u003e \u003cbr /\u003e\u003c/p\u003e"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/name":[{"@language":"fr","@value":"Dogue"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-27T20:00:00-04:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-212"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Ariane-Roy.jpg"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_ariane-roy-dogue#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/ariane-roy-dogue/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:19:57.513-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/ariane-roy-dogue/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_ariane-roy-dogue#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"37.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_sam-breton-nouveau-sam-breton","@type":["http://schema.org/Event"],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/sam-breton-nouveau-sam-breton/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e Vivre un épuisement professionnel, c'est aussi vivre une recharge professionnelle. Autrement dit : travailler mon deuxième one-man-show de façon encore plus efficace, sans perdre de temps avec des niaiseries. Là, j'aimerais bien te vanter mon spectacle pour te convaincre de venir me voir sur scène, mais ça entrerait dans la catégorie « perte de temps », parce que de toute façon, juste avec ma face, tu l'sais déjà si tu vas venir voir mon show ou non. Avoue? ;) \u003c/p\u003e\n\u003cp\u003e \u003c/p\u003e"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/sam-breton-nouveau-sam-breton/"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Sam-Breton-2.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-02T19:30:00-04:00"}],"http://schema.org/name":[{"@language":"fr","@value":"Ga-lé aller"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_sam-breton-nouveau-sam-breton#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/sam-breton-nouveau-sam-breton/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:20:03.604-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/sam-breton-nouveau-sam-breton/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_sam-breton-nouveau-sam-breton#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"46.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_laurence-jalbert-tournee-35e-anniversaire","@type":["http://schema.org/Event"],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cstrong\u003eUne voix née de la mer, portée par le cœur et l’instinct.\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eCélébrez 35 ans de musique et redécouvrez ce premier album mythique de Laurence Jalbert, dans un spectacle haut en émotions et en intensité. Accompagnée de ses musiciens, elle vous fera vibrer au son de ses plus grands succès tirés de cet album dont; \u003cem\u003eTomber\u003c/em\u003e, \u003cem\u003eAu nom de la raison\u003c/em\u003e, \u003cem\u003eLes yeux noirs\u003c/em\u003e, \u003cem\u003eRage. \u003c/em\u003eL’artiste vous réserve en plus quelques surprises qui sauront réconforter et toucher l’âme du public.\u003c/p\u003e\n\u003cp\u003eCe spectacle se veut une célébration de passion, de force et d’un amour profond envers le public depuis plus de 35 ans! \u003c/p\u003e"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/laurence-jalbert-tournee-35e-anniversaire/"}],"http://schema.org/name":[{"@language":"fr","@value":"Tournée 35e anniversaire"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Laurence-Jalbert-2.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-12-01T20:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/laurence-jalbert-tournee-35e-anniversaire/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_laurence-jalbert-tournee-35e-anniversaire#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/laurence-jalbert-tournee-35e-anniversaire/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:18:27.001-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/laurence-jalbert-tournee-35e-anniversaire/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_laurence-jalbert-tournee-35e-anniversaire#Offer_0","http://schema.org/price":[{"@value":"56.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_noel-une-tradition-en-chanson-2026","@type":["http://schema.org/Event"],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cstrong\u003eC’est un rendez-vous dès 2026 pour la 9e édition de Noël une tradition en chanson. \u003c/strong\u003e\u003c/p\u003e\n\n\u003cp\u003ePour plusieurs, ce rendez-vous festif donne le coup d’envoi à la période des festivités de fin d’année. Au fil des ans, ils ont été plus de 100 000 spectateurs à venir entendre les plus belles voix du Québec interpréter des classiques incontournables. \u003c/p\u003e\n\n\u003cp\u003eNoël une tradition en chanson fait vibrer la corde sensible des souvenirs d’enfance en misant sur la nostalgie et le plaisir de célébrer ensemble. \u003c/p\u003e\n\n\u003cp\u003eUne mise en scène de Joël Legendre avec Jacques Roy à la direction musicale. \u003c/p\u003e"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/noel-une-tradition-en-chanson-2026/"}],"http://schema.org/name":[{"@language":"fr","@value":"Noël, une tradition en chanson - 2026"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/noel-une-tradition-en-chanson.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-12-23T19:30:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/noel-une-tradition-en-chanson-2026/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Spectacle en salle\"]"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_noel-une-tradition-en-chanson-2026#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/noel-une-tradition-en-chanson-2026/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:19:20.650-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/noel-une-tradition-en-chanson-2026/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_noel-une-tradition-en-chanson-2026#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"64.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_le-passage-secret","@type":["http://schema.org/Event"],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-77"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/le-passage-secret/#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-08T14:00:00-04:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/22f50ba1-3403-48a8-a279-460952d3c134"},{"@id":"http://kg.footlight.io/resource/12ca997a-7465-47a3-a877-a6b10b04b501"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003csub\u003eDes mots d’la dynamite \u003c/sub\u003e\u003c/p\u003e\n\u003ch3\u003ePeut-on collectionner ses souvenirs?\u003c/h3\u003e\n\u003cp\u003ePar la magie du théâtre d’ombres, \u003cem\u003eLe passage secret\u003c/em\u003e nous fait visiter une forêt, un village et ses habitants, une maison où vit une grand-maman bienveillante avec son chien, son jardin fleuri et un chat.\u003c/p\u003e\n\u003cp\u003eEmpruntant à la formule du cabaret, trois interprètes-marionnettistes chantent, dansent et évoquent des souvenirs et des secrets d’enfance. Elles traversent avec curiosité le 4\u003csup\u003ee\u003c/sup\u003e mur de la scène et lèvent le rideau sur l’imaginaire, la pensée, la nature et la poésie. Nous découvrons avec elles les clés du passage secret entre tous ces mondes.\u003c/p\u003e\n\u003cp\u003eLa compagnie \u003cstrong\u003eDes mots d’la dynamite\u003c/strong\u003e a pour mandat la création, la production et la diffusion d’œuvres interdisciplinaires et d’événements performatifs pour le public adulte et le jeune public. Elle privilégie la rencontre intime et le plus souvent avec des moyens volontairement artisanaux.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eDurée : 42 minutes\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eProfitez de votre venue à la Maison des arts pour visiter les expositions en cours à la \u003ca href=\"https://www.laval.ca/culture/maison-des-arts/expositions-salle-alfred-pellan/expositions/\" target=\"_blank\" rel=\"noreferrer noopener\"\u003eSalle Alfred Pellan\u003c/a\u003e.\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"http://desmotsdladynamite.com\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eSite Web Des mots d’la dynamite\u003c/a\u003e \u003ca href=\"https://www.laval.ca/calendrier-activites/passage-secret-mda-jeune-public-theatre/\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eMaison des arts de Laval\u003c/a\u003e\u003c/p\u003e"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/le-passage-secret/"}],"http://schema.org/name":[{"@language":"fr","@value":"Le passage secret"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/1005207546/0ba7528959"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/MDA/Programmation-ville/Jeune-Public/2025-2026/Le-passage-secret.jpg"}],"http://schema.org/keywords":[{"@value":"[\"Jeune public\", \"Théâtre d’ombres et chansons\", \"Spectacle en salle\"]"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_le-passage-secret#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/le-passage-secret/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:10:42.939-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/le-passage-secret/"}]},{"@id":"http://kg.footlight.io/resource/12ca997a-7465-47a3-a877-a6b10b04b501","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Théâtre d’ombres et chansons"}],"http://www.w3.org/2004/02/skos/core#closeMatch":[{"@id":"http://kg.artsdata.ca/resource/TheatrePerformance"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_le-passage-secret#Offer_0","http://schema.org/price":[{"@value":"17.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_the-musical-box-genesis-live","@type":["http://schema.org/Event"],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"},{"@id":"http://kg.artsdata.ca/resource/Chansonanglophone"},{"@id":"http://kg.footlight.io/resource/8f6b3be1-6023-4ad2-aadb-a259194b9a33"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Chanson anglophone\", \"Spectacle hommage\", \"Spectacle en salle\"]"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-20T19:30:00-05:00"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eEn 1973, GENESIS débarque au Québec pour la 1ère fois. C’est le début d’une longue histoire d’amour entre le groupe britannique et les Québécois, qui sont dès les premières notes du concert immédiatement subjugués.\u003c/p\u003e\n\u003cp\u003eRien ne pouvait préparer les jeunes spectateurs à SUPPER’S READY - une aventure musicale de 23 min. et quintessence du rock théâtre - et à la performance bouleversante d’un jeune PETER GABRIEL et ses mystérieux costumes. Un spectacle qui deviendra l’événement culte d’une génération.\u003c/p\u003e\n\n\n\u003cp\u003eDe ce même spectacle sera tiré l’album GENESIS LIVE (1973), une fenêtre sur l’univers dramatique de GENESIS sur scène, qui inclut les pièces maîtresses des albums TRESSPASS (1970), NURSERY CRYME (1971) et FOXTROT (1972).\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e50 ans plus tard, THE MUSICAL BOX, reconnu internationalement pour la qualité historique de son interprétation de l’oeuvre de GENESIS, présente le concert original de GENESIS LIVE. Une chance unique de remonter dans le temps.\u003c/strong\u003e\u003c/p\u003e"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/the-musical-box-genesis-live/"}],"http://schema.org/name":[{"@language":"fr","@value":"Genesis Live"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/The-Musical-Box.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/the-musical-box-genesis-live/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_the-musical-box-genesis-live#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/the-musical-box-genesis-live/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-18T10:18:29.243-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/the-musical-box-genesis-live/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_the-musical-box-genesis-live#Offer_0","http://schema.org/price":[{"@value":"62.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_viviane-audet-le-piano-et-le-torrent","@type":["http://schema.org/Event"],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/viviane-audet-le-piano-et-le-torrent/"}],"http://schema.org/name":[{"@language":"fr","@value":"Le piano et le torrent"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Viviane-Audet.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-09T20:00:00-04:00"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/viviane-audet-le-piano-et-le-torrent/#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eLe nouveau spectacle de Viviane Audet met en lumière son plus récent album instrumental, Le piano et le torrent — un récit pianistique de quinze pièces, enraciné dans ce petit village côtier de la Baie-des-Chaleurs qui l’a vue naître, grandir et devenir : Maria.\u003c/p\u003e\n\u003cp\u003eDans une mise en scène à la fois délicate et vibrante, où les rires croisent les larmes, Viviane déploie ses talents de pianiste et de comédienne. Avec tendresse et aplomb, elle lève le voile sur son lien intime avec le territoire, là où veillent les goélands, les corneilles… et les souvenirs.\u003c/p\u003e\n\u003cp\u003eCe spectacle révèle ce que Viviane a de plus précieux : sa façon bien à elle de tisser un lien avec le public, son amour profond de la scène, et ce don rare de marier musique et récit.\u003cbr /\u003eUn rendez-vous profondément humain, qui invite à plonger dans un récit de déracinement et de lumière.\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_viviane-audet-le-piano-et-le-torrent#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/viviane-audet-le-piano-et-le-torrent/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:18:21.813-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/viviane-audet-le-piano-et-le-torrent/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_viviane-audet-le-piano-et-le-torrent#Offer_0","http://schema.org/price":[{"@value":"48.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_louis-jean-cormier-les-entretoits","@type":["http://schema.org/Event"],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Les entretoits"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/louis-jean-cormier-les-entretoits/"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eCe nouveau spectacle se veut comme un retour aux sources. Un désir d’aller à l’opposé de la dernière tournée de Karkwa, de s’éloigner du bruit et de retrouver l’essence du musicien que je suis et de ses créations. Évidemment, je replongerai dans le répertoire de ma carrière solo mais j’irai aussi fouiller dans les bacs, dans les souvenirs de jeunesse pour réinventer plusieurs chansons d’autres artistes. Quand je suis seul sur scène, il y a une connexion profonde qui se crée avec vous. C’est une symbiose qui n’a pas d’égal… \u003c/p\u003e\n\u003cp\u003e Et ça me manquait.\u003c/p\u003e"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Spectacle en salle\"]"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/louis-jean-cormier-les-entretoits/#WebPage"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-28T20:00:00-05:00"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Louis-Jean-Cormier.jpg"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_louis-jean-cormier-les-entretoits#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/louis-jean-cormier-les-entretoits/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:16:58.545-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/louis-jean-cormier-les-entretoits/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_louis-jean-cormier-les-entretoits#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"45.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_dej-croissant-musique-bouquet-de-femmes","http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/dej-croissant-musique-bouquet-de-femmes/#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/cfd110d3-63b4-45f7-8772-97609987b338"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/f56c4b49-240a-447d-8212-5f0c6333e42a"}],"http://schema.org/name":[{"@language":"fr","@value":"Déjeuners Croissant-Musique"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003ch5\u003eCatherine St-Arnaud (soprano) et Julien Leblanc (piano)\u003c/h5\u003e\n\u003cp\u003eUn récital présentant un bouquet de musique composée par des femmes sur des poésies en français. Vous y entendrez entre autres des mélodies de Cécile Chaminade, Jeanne Landry, Mel Bonis et Pauline Viardot.\u003c/p\u003e\n\n\u003cp\u003e\u003cstrong\u003eRencontre avec l’art\u003c/strong\u003e: Les semences de nos lendemains. Khadija Baker\u003c/p\u003e\n\n\u003cp\u003e\u003cstrong\u003eApéro-Concert 10h40\u003c/strong\u003e\u003c/p\u003e"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/dej-croissant-musique-bouquet-de-femmes/"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/MDA/Dej-croissant/Catherine_mai-juin-2026.jpg"}],"http://schema.org/keywords":[{"@value":"[\"Musique classique\", \"Spectacle en salle\"]"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-24T10:00:00-04:00"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_dej-croissant-musique-bouquet-de-femmes#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-06-14T10:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_dej-croissant-musique-bouquet-de-femmes#2026-05-24T100000-0400"},{"@id":"http://kg.footlight.io/resource/co-motion-ca_dej-croissant-musique-bouquet-de-femmes#2026-06-07T100000-0400"},{"@id":"http://kg.footlight.io/resource/co-motion-ca_dej-croissant-musique-bouquet-de-femmes#2026-06-14T100000-0400"}]},{"@id":"https://co-motion.ca/spectacles/dej-croissant-musique-bouquet-de-femmes/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:21:16.377-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/dej-croissant-musique-bouquet-de-femmes/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_dej-croissant-musique-bouquet-de-femmes#Offer_0","http://schema.org/price":[{"@value":"33.35"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_dej-croissant-musique-bouquet-de-femmes#2026-05-24T100000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Déjeuners Croissant-Musique"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/cfd110d3-63b4-45f7-8772-97609987b338"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-24T10:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_dej-croissant-musique-bouquet-de-femmes#2026-06-07T100000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Déjeuners Croissant-Musique"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/cfd110d3-63b4-45f7-8772-97609987b338"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-06-07T10:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_dej-croissant-musique-bouquet-de-femmes#2026-06-14T100000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Déjeuners Croissant-Musique"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/cfd110d3-63b4-45f7-8772-97609987b338"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-06-14T10:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_la-cour-des-grands-les-25-ans-de-kain","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/la-cour-des-grands-les-25-ans-de-kain/"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-02T19:30:00-04:00"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Spectacle en salle\"]"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/la-cour-des-grands-les-25-ans-de-kain/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"La cour des grands : les 25 ans de Kaïn"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Ka%C3%AFn.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eL’infatigable groupe \u003cstrong\u003eKaïn \u003c/strong\u003ese paie la traite pour ses 25 ans d’existence et s’offre une place dans la cour des grands, le temps d’un album. Un hommage à ses idoles Québécois de Desjardins à Mandeville, en passant par Plume, Pag, Piché, Séguin et d’autres influences importantes du band. Plus festif et vivant que jamais, le spectacle promet la fête et la grande soif! Kaïn, un incontournable party Québécois.\u003c/p\u003e"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_la-cour-des-grands-les-25-ans-de-kain#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/la-cour-des-grands-les-25-ans-de-kain/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:22:03.404-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/la-cour-des-grands-les-25-ans-de-kain/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_la-cour-des-grands-les-25-ans-de-kain#Offer_0","http://schema.org/price":[{"@value":"48.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_pascale-picard-bigger-kids-bigger-problems","@type":["http://schema.org/Event"],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/pascale-picard-bigger-kids-bigger-problems/#WebPage"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Spectacle en salle\"]"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-04T20:00:00-04:00"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003ch4\u003eL'autrice-compositrice est à son apogée, tant sur scène qu'à l'écriture et à la radio. \u003c/h4\u003e\n\u003cp\u003eAccompagnée de musiciens talentueux, elle vous propose une expérience musicale moderne et authentique. Dotée d'un son résolument actuel, Pascale Picard sait comment captiver son public et le transporter dans son univers singulier.\u003c/p\u003e\n\u003cp\u003eVous assisterez à un spectacle où chaque note, chaque mot est porteur d'émotion et de vérité.Que vous soyez un fan de longue date ou que vous découvriez son talent pour la première fois, ce spectacle vous promet une soirée inoubliable.\u003c/p\u003e"}],"http://schema.org/name":[{"@language":"fr","@value":"Bigger Kids, Bigger Problems"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Pascale-Picard.jpg"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/watch?v=Oj0Oew001jw"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/pascale-picard-bigger-kids-bigger-problems/"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_pascale-picard-bigger-kids-bigger-problems#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/pascale-picard-bigger-kids-bigger-problems/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:18:40.313-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/pascale-picard-bigger-kids-bigger-problems/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_pascale-picard-bigger-kids-bigger-problems#Offer_0","http://schema.org/price":[{"@value":"42.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_frank-and-cos-live-in-your-neighbourhood","@type":["http://schema.org/Event"],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/frank-and-cos-live-in-your-neighbourhood/#WebPage"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-29T20:00:00-05:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/frank-and-cos-live-in-your-neighbourhood/"}],"http://schema.org/name":[{"@language":"fr","@value":"Live in your neighbourhood"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Locations/poster-2.jpg"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eSketch comedy show, Frank and Cos in \"your neighbourhood\" for an unforgettable night of laughter as \u003cstrong\u003eFrank Spadon\u003c/strong\u003ee and \u003cstrong\u003eCosimo Tucci\u003c/strong\u003e bring their hilarious sketches to life live and in person. \u003c/p\u003e\n\u003cp\u003eThis sketch comedy show includes characters like \"Frank and Franca\", performed by Frank Spadone and \"The Son and Neighbourhood\" performed by Cosimo Tucci. Capturing the dynamics and comical situations of living in or next to an immigrant family.\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_frank-and-cos-live-in-your-neighbourhood#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/frank-and-cos-live-in-your-neighbourhood/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-28T10:23:17.648-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/frank-and-cos-live-in-your-neighbourhood/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_frank-and-cos-live-in-your-neighbourhood#Offer_0","http://schema.org/price":[{"@value":"65.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_p-a-methot-pardon","@type":["http://schema.org/Event"],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-20T20:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/p-a-methot-pardon/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/p-a-methot-pardon/"}],"http://schema.org/name":[{"@language":"fr","@value":"Pardon ?!"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Methot.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eLe seul et unique P-A Méthot est enfin de retour sur scène avec un nouveau spectacle intitulé Pardon?!. Raconteur authentique et rassembleur, P-A sait autant toucher le public que le faire rire. Après plus de 500 000 billets vendus pour ses deux premiers spectacles, l’humoriste chouchou revient avec son humour ancré dans le vrai monde et ses histoires savoureuses, transformant chaque situation du quotidien en un moment comique où tout le monde se reconnaît. \u003c/p\u003e\n\u003cp\u003eDans ce nouveau one-man-show, notre habile conteur répond aux grands questionnements de la vie avec une seule et unique question : « PARDON?! ». Un troisième spectacle vrai, authentique et surtout hilarant.\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_p-a-methot-pardon#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/p-a-methot-pardon/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:17:33.201-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/p-a-methot-pardon/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_p-a-methot-pardon#Offer_0","http://schema.org/price":[{"@value":"53.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_zac-bulle-mon-super-spectacle","@type":["http://schema.org/Event"],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2027-01-22T20:00:00-05:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/name":[{"@language":"fr","@value":"Mon super spectacle"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Zac-Bulle.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cstrong\u003eZac Bulle\u003c/strong\u003e, 8 ans et demi, s'est fait connaître grâce à ses vidéos virales où il partage ses plus grandes passions : la mayo, ses aventures quotidiennes, les montages farfelus… ou encore la mayo. Vous pourrez enfin le voir sur scène dans son tout premier « One Petit Gars Show », en tournée à travers le Québec. \u003c/p\u003e\n\u003cp\u003e \u003c/p\u003e\n\u003cp\u003eAvec « Mon super spectacle \u0026#x1f642; », Zac enchaîne ses meilleures niaiseries pour vous impressionner : blagues, dessins, chansons, tours de magie et autres trouvailles étranges, tout droit sorties de sa tête de gamin. Espérons seulement que son papa Jacques ne viendra pas tout gâcher… \u003c/p\u003e\n\u003cp\u003e \u003c/p\u003e\n\u003cp\u003eImaginez : au lieu de quelques secondes de Zac sur votre cellulaire, vous aurez droit à plusieurs minutes (environ 75!!!) de son univers à la fois tout mimi et complètement absurde. Préparez vous à vivre un moment déjanté, imprévisible, hilarant… et très stupide. \u003c/p\u003e\n\u003cp\u003e \u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e(Spectacle réservé aux 13 ans et plus.) \u003c/strong\u003e\u003c/p\u003e"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/zac-bulle-mon-super-spectacle/#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/zac-bulle-mon-super-spectacle/"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_zac-bulle-mon-super-spectacle#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/zac-bulle-mon-super-spectacle/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:17:18.969-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/zac-bulle-mon-super-spectacle/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_zac-bulle-mon-super-spectacle#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"39.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_mathieu-cyr-adulte","@type":["http://schema.org/Event"],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/name":[{"@language":"fr","@value":"Adulte"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-11T20:00:00-04:00"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/mathieu-cyr-adulte/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/mathieu-cyr-adulte/"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Mathieu-Cyr.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e« Véritable phénomène de l’humour québécois, suivi par plus de 620 000 personnes sur les réseaux sociaux, Mathieu Cyr est au sommet de son art dans ce deuxième one man show, où il explose la scène avec un humour décapant, sans filtre, qui fait du bien! \u003c/p\u003e\n\u003cp\u003e \u003cem\u003eAdulte\u003c/em\u003e, c’est un show totalement hilarant qui parle de la nostalgie des années ’90-2000 avec les Walkman Sony jaunes, les backstreet boys et faire du BMX sans casque… l’époque où le monde se parlait au lieu de se texter. \u003c/p\u003e\n\n\n\u003cp\u003eC’est aussi une critique punchée de notre société actuelle, avec les jeunes en «têtes de brocolis», les 147 orientations sexuelles et les chirurgies esthétiques qu’on choisit comme un buffet chinois (que Mathieu a subi d’ailleurs). \u003c/p\u003e\n\u003cp\u003e \u003cem\u003eAdulte,\u003c/em\u003e c’est un clin d’oeil au passé et une critique du présent, à travers la vision d’un gars TDAH et perdu, qui n'a jamais été capable de fitter dans le moule. Un «Feel good show» extrêmement drôle, duquel on ressort avec un mal de joues! \u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_mathieu-cyr-adulte#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/mathieu-cyr-adulte/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:18:15.802-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/mathieu-cyr-adulte/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_mathieu-cyr-adulte#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"39.50"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_coeur-de-pirate-cavale","@type":["http://schema.org/Event"],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/name":[{"@language":"fr","@value":"Cavale"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Spectacle en salle\"]"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-24T20:00:00-04:00"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cstrong\u003eIncontournable de la chanson francophone, Cœur de pirate\u003c/strong\u003e est capable d’introspection et d’exaltation aussi bien que de subversion; avec sa voix intime et affilée, elle séduit comme elle surprend. Icône pop internationale partageant sa vie et sa carrière entre le Québec et l'Europe, elle s’est produite à travers le monde depuis ses fulgurants débuts en 2008 – et, en 2025, elle revient sur scène pour présenter l’album \u003cem\u003e\u003cstrong\u003eCavale\u003c/strong\u003e\u003c/em\u003e. \u003c/p\u003e\n\u003cp\u003eLa voyant passer de la douceur aux envolées, ce nouveau spectacle à la scénographie grandiose présente autant la femme au piano que la star qui danse. On y retrouve sa distinctive manière – à la fois mélancolique et vibrante – de livrer l’émotion tout en invitant la foule à célébrer. Une soirée avec Cœur de pirate, c’est la volatilité des sentiments présentée avec un souci d’orfèvre, entre recueillement et exaltation.\u003c/p\u003e"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/coeur-de-pirate-cavale/"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/coeur-de-pirate-cavale/#WebPage"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/coeur-de-pirate.jpg"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_coeur-de-pirate-cavale#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/coeur-de-pirate-cavale/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:11:16.141-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/coeur-de-pirate-cavale/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_coeur-de-pirate-cavale#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"50.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_sans-quoi-nous-creverons","@type":["http://schema.org/Event"],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/sans-quoi-nous-creverons/"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-77"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-09T19:30:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/sans-quoi-nous-creverons/#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/2fa05002-967b-43c2-b5cb-fc93a25edcb1"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"[\"Danse\", \"Spectacle en salle\"]"}],"http://schema.org/name":[{"@language":"fr","@value":"Sans quoi nous crèverons"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003csub\u003eCompagnie Virginie Brunelle\u003c/sub\u003e\u003cbr /\u003e\u003c/p\u003e\n\u003ch3\u003eEnsemble, dans l’urgence de vivre!\u003c/h3\u003e\n\u003cp\u003eTémoin lucide des enjeux actuels, \u003cstrong\u003eVirginie Brunelle\u003c/strong\u003e pose un regard incisif sur une planète en hypertension. Elle signe une chorégraphie impérieuse, vibrant combat pour l’amour et la solidarité.\u003c/p\u003e\n\u003cp\u003eAvec ses attaques et ses élans fulgurants, la danse exutoire de Virginie Brunelle percute de plein fouet et ausculte une humanité fracturée. Après \u003cem\u003eFables\u003c/em\u003e, où elle explorait le combat des femmes, elle aborde cette fois les thèmes du chaos et de la déshumanisation en créant un rempart scénique où l’union devient un contrepoids salvateur. Sur scène, cinq interprètes incarnent une force collective où la dépense physique devient langage. Chaque souffle, chaque frémissement musculaire s’inscrit dans une trame humaine et sensible, révélant leur profonde complicité. Soutenue par une composition ingénieuse de Laurier Rajotte, \u003cem\u003eSans quoi nous crèverons\u003c/em\u003e résonne comme un cri libérateur contre l’aliénation et la lente érosion de notre condition humaine — une invitation à vivre pleinement.\u003c/p\u003e\n\u003cp\u003eCe spectacle est présenté avec la collaboration de \u003cstrong\u003eLa danse sur les routes du Québec\u003c/strong\u003e.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eDurée : 60 minutes\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e\u003cstrong\u003eProfitez de votre venue à la Maison des arts pour visiter les expositions en cours à la \u003ca href=\"https://www.laval.ca/culture/maison-des-arts/expositions-salle-alfred-pellan/expositions/\" target=\"_blank\" rel=\"noreferrer noopener\"\u003eSalle Alfred Pellan\u003c/a\u003e.\u003c/strong\u003e\u003cbr /\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"http://virginiebrunelle.com\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eSite Web de la Compagnie Virginie Brunelle\u003c/a\u003e \u003ca href=\"https://www.laval.ca/calendrier-activites/sans-quoi-nous-creverons-mda-danse/\" target=\"_blank\" class=\"button btn-primary\" rel=\"noreferrer noopener\"\u003eMaison des arts de Laval\u003c/a\u003e\u003cbr /\u003e\u003c/p\u003e"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/MDA/Programmation-ville/Danse/2025-2026/29.jpg"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_sans-quoi-nous-creverons#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/sans-quoi-nous-creverons/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:17:38.533-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/sans-quoi-nous-creverons/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_sans-quoi-nous-creverons#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"40.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_dej-croissant-musique-les-coups-de-coeur-de-lorraine-desmarais","http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Déjeuners Croissant-Musique"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-01T10:00:00-05:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/cfd110d3-63b4-45f7-8772-97609987b338"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/f56c4b49-240a-447d-8212-5f0c6333e42a"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/MDA/Dej-croissant/Desmarais_mars2026.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/dej-croissant-musique-les-coups-de-coeur-de-lorraine-desmarais/#WebPage"}],"http://schema.org/keywords":[{"@value":"[\"Musique classique\", \"Spectacle en salle\"]"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/dej-croissant-musique-les-coups-de-coeur-de-lorraine-desmarais/"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003eLe piano jazz en piano solo est toujours un retour aux sources pour \u003cstrong\u003eLorraine Desmarais\u003c/strong\u003e. Virtuosité, spontanéité et improvisations sur des thèmes de ses compositeurs préférés et sur ses propres compositions choisies parmi son vaste répertoire de musique jazz.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eCompositeurs\u003c/strong\u003e: Lorraine Desmarais, Frédéric Chopin, Astor Piazzola, Joseph Kosma.\u003c/p\u003e\n\n\u003cp\u003e\u003cstrong\u003eRencontre avec l’art\u003c/strong\u003e: Trames composites. Exposition collective\u003c/p\u003e\n\n\u003cp\u003e\u003cstrong\u003eApéro-Concert 10h40\u003c/strong\u003e\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_dej-croissant-musique-les-coups-de-coeur-de-lorraine-desmarais#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-29T10:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_dej-croissant-musique-les-coups-de-coeur-de-lorraine-desmarais#2026-03-01T100000-0500"},{"@id":"http://kg.footlight.io/resource/co-motion-ca_dej-croissant-musique-les-coups-de-coeur-de-lorraine-desmarais#2026-03-15T100000-0400"},{"@id":"http://kg.footlight.io/resource/co-motion-ca_dej-croissant-musique-les-coups-de-coeur-de-lorraine-desmarais#2026-03-29T100000-0400"}]},{"@id":"https://co-motion.ca/spectacles/dej-croissant-musique-les-coups-de-coeur-de-lorraine-desmarais/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:23:15.473-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/dej-croissant-musique-les-coups-de-coeur-de-lorraine-desmarais/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_dej-croissant-musique-les-coups-de-coeur-de-lorraine-desmarais#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"33.35"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_dej-croissant-musique-les-coups-de-coeur-de-lorraine-desmarais#2026-03-01T100000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Déjeuners Croissant-Musique"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/cfd110d3-63b4-45f7-8772-97609987b338"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-01T10:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_dej-croissant-musique-les-coups-de-coeur-de-lorraine-desmarais#2026-03-15T100000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Déjeuners Croissant-Musique"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/cfd110d3-63b4-45f7-8772-97609987b338"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-15T10:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_dej-croissant-musique-les-coups-de-coeur-de-lorraine-desmarais#2026-03-29T100000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Déjeuners Croissant-Musique"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/cfd110d3-63b4-45f7-8772-97609987b338"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-29T10:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_stephane-archambault-point","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Point"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/keywords":[{"@value":"[\"Musique\", \"Spectacle en salle\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/stephane-archambault-point/"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003cp\u003e\u003cstrong\u003ePersonne ne s’y attendait, pas même lui – Stéphane Archambault\u003c/strong\u003e se remet à la musique, et il était grand temps. De retour sur scène pour la première fois depuis la fin de son populaire groupe \u003cstrong\u003eMes Aïeux\u003c/strong\u003e, il se lance en solo dans un spectacle de 12 nouvelles chansons, augmenté de quelques reprises choisies qui sont en phase avec – ou qui ont influencé – ses compositions. Des récits de ruptures et de renouveaux, de temps et d’alentours, posés dans un arc narratif; une mise à jour de qui et où il est, au je plutôt qu’au nous. Marqué par l’âge d’or français, au phrasé évoquant affablement la musique traditionnelle, Archambault avance une chanson plurielle, généreuse, à la fois révérencieuse et un peu crottée. Un exercice d’impudeur : il enlève ses pelures pour se présenter de façon intime, contemplative, – pour connecter davantage avec un public qu’il souhaite voir repartir avec le sourire. Un spectacle aux réflexions sereines sur l’âge et les écueils communs en même temps qu’une leçon d’humilité pour un artiste qui revient vers vous : se lançant à la fois dans le vide et dans vos bras. Un premier album est attendu pour l’automne – pour des retrouvailles dans une fébrilité partagée.\u003c/p\u003e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-16T20:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/stephane-archambault-point/#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-226"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/St%C3%A9phane-Archambault.jpg"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_stephane-archambault-point#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/stephane-archambault-point/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-14T10:20:20.422-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/stephane-archambault-point/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_stephane-archambault-point#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"45.00"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_pierre-hebert-grandiose","@type":["http://schema.org/Event"],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-201"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/pierre-hebert-grandiose/"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://co-motion.ca/spectacles/pierre-hebert-grandiose/#WebPage"}],"http://schema.org/location":[{"@id":"https://www.co-motion.ca/nos-salles-de-spectacles/salle-andre-mathieu/#Place"}],"http://schema.org/keywords":[{"@value":"[\"Humour\", \"Spectacle en salle\"]"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Grandiose"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/description":[{"@language":"fr","@value":"\u003ch4\u003eTous les matins, on se lève avec le même objectif : que notre vie soit grandiose!\u003c/h4\u003e\n\u003cp\u003eOn en rêve, on s’épuise, on se dénature, on se casse la gueule et parfois, pendant un court instant, on y arrive. C’est à ce moment qu’on réalise que tout ça en valait la peine. Tout ce chemin, tous ces efforts pour vivre ce moment parfait qui veut tout dire, avant que tout se termine pour de bon.\u003c/p\u003e\n\u003cp\u003eQuatre ans après la fin de la tournée Le goût du risque, Pierre Hébert reprend enfin la route avec son nouveau spectacle : Grandiose.\u003c/p\u003e\n\u003cp\u003eOn y retrouve l’humoriste souvent moqueur, parfois touchant, qui porte un regard plein d’humour et d’émerveillement sur le monde qui l’entoure. \u003c/p\u003e"}],"http://schema.org/image":[{"@value":"https://co-motion.ca/uploads/Spectacles/Pierre-Hebert.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-02T20:00:00-04:00"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/co-motion-ca_pierre-hebert-grandiose#Offer_0"}]},{"@id":"https://co-motion.ca/spectacles/pierre-hebert-grandiose/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T10:20:08.103-05:00"}],"http://schema.org/url":[{"@value":"https://co-motion.ca/spectacles/pierre-hebert-grandiose/"}]},{"@id":"http://kg.footlight.io/resource/co-motion-ca_pierre-hebert-grandiose#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"53.50"}]}]
\ No newline at end of file
diff --git a/data/migration_baseline/crowstheatre_com.jsonld b/data/migration_baseline/crowstheatre_com.jsonld
new file mode 100644
index 00000000..cfa60625
--- /dev/null
+++ b/data/migration_baseline/crowstheatre_com.jsonld
@@ -0,0 +1 @@
+[{"@id":"http://kg.footlight.io/resource/crowstheatre-com_the-division","http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K10-42"}],"http://schema.org/url":[{"@value":"https://www.crowstheatre.com/whats-on/view-all/the-division"}],"http://schema.org/name":[{"@language":"en","@value":"The Division | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5fd67788-a442-4dd5-9297-c24627f7d890"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-04-21"}],"http://schema.org/description":[{"@language":"en","@value":"\u003cp\u003eProject: Humanity and Pyretic Productions in Association with Crow’s Theatre \u003cbr /\u003eWritten by Andrew Kushnir \u003cbr /\u003eDirected by Andrew Kushnir\u003c/p\u003e \u003cp\u003eApr 21, 2026 - May 10, 2026 \u003cbr /\u003eStudio Theatre\u003c/p\u003e"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.crowstheatre.com/whats-on/view-all/the-division#WebPage"}],"http://schema.org/image":[{"@value":"https://www.crowstheatre.com//de/cache/apps/shows/1010/w2400_DIV_2400x1350_Centered.jpg"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/crowstheatre-com_the-division#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-05-02"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/crowstheatre-com_the-division#2026-04-21"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_the-division#2026-04-22"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_the-division#2026-04-23"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_the-division#2026-04-24"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_the-division#2026-04-25"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_the-division#2026-04-26"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_the-division#2026-04-28"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_the-division#2026-04-29"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_the-division#2026-04-30"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_the-division#2026-05-01"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_the-division#2026-05-02"}]},{"@id":"https://www.crowstheatre.com/whats-on/view-all/the-division#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"en"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T19:41:42.752-05:00"}],"http://schema.org/url":[{"@value":"https://www.crowstheatre.com/whats-on/view-all/the-division"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_the-division#Offer_0","http://schema.org/url":[{"@value":"https://www.crowstheatre.com/shows-events/schedule?shows=1010"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_the-division#2026-04-21","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"The Division | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5fd67788-a442-4dd5-9297-c24627f7d890"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-04-21"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_the-division#2026-04-22","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"The Division | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5fd67788-a442-4dd5-9297-c24627f7d890"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-04-22"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_the-division#2026-04-23","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"The Division | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5fd67788-a442-4dd5-9297-c24627f7d890"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-04-23"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_the-division#2026-04-24","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"The Division | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5fd67788-a442-4dd5-9297-c24627f7d890"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-04-24"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_the-division#2026-04-25","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"The Division | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5fd67788-a442-4dd5-9297-c24627f7d890"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-04-25"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_the-division#2026-04-26","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"The Division | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5fd67788-a442-4dd5-9297-c24627f7d890"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-04-26"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_the-division#2026-04-28","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"The Division | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5fd67788-a442-4dd5-9297-c24627f7d890"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-04-28"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_the-division#2026-04-29","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"The Division | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5fd67788-a442-4dd5-9297-c24627f7d890"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-04-29"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_the-division#2026-04-30","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"The Division | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5fd67788-a442-4dd5-9297-c24627f7d890"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-04-30"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_the-division#2026-05-01","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"The Division | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5fd67788-a442-4dd5-9297-c24627f7d890"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-05-01"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_the-division#2026-05-02","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"The Division | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5fd67788-a442-4dd5-9297-c24627f7d890"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-05-02"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_primary-trust","http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K10-42"}],"http://schema.org/url":[{"@value":"https://www.crowstheatre.com/whats-on/view-all/primary-trust"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-200"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.crowstheatre.com/whats-on/view-all/primary-trust#WebPage"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-05-26"}],"http://schema.org/name":[{"@language":"en","@value":"Primary Trust | Crow's Theatre"}],"http://schema.org/image":[{"@value":"https://www.crowstheatre.com//de/cache/apps/shows/1011/w2400_PRI_2400x1350_Centered.jpg"}],"http://schema.org/description":[{"@language":"en","@value":"\u003cp\u003eA Crow’s Theatre and the Grand Theatre Co-production \u003cbr /\u003eWritten by Eboni Booth \u003cbr /\u003eDirected by Cherissa Richards\u003c/p\u003e \u003cp\u003eMay 26, 2026 - Jun 21, 2026 \u003cbr /\u003eGuloien Theatre\u003c/p\u003e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/crowstheatre-com_primary-trust#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-06-06"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/crowstheatre-com_primary-trust#2026-05-26"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_primary-trust#2026-05-27"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_primary-trust#2026-05-28"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_primary-trust#2026-05-29"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_primary-trust#2026-05-30"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_primary-trust#2026-05-31"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_primary-trust#2026-06-02"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_primary-trust#2026-06-03"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_primary-trust#2026-06-04"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_primary-trust#2026-06-05"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_primary-trust#2026-06-06"}]},{"@id":"https://www.crowstheatre.com/whats-on/view-all/primary-trust#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"en"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T19:41:38.965-05:00"}],"http://schema.org/url":[{"@value":"https://www.crowstheatre.com/whats-on/view-all/primary-trust"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_primary-trust#Offer_0","http://schema.org/url":[{"@value":"https://www.crowstheatre.com/shows-events/schedule?shows=1011"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_primary-trust#2026-05-26","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"Primary Trust | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-200"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-05-26"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_primary-trust#2026-05-27","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"Primary Trust | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-200"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-05-27"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_primary-trust#2026-05-28","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"Primary Trust | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-200"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-05-28"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_primary-trust#2026-05-29","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"Primary Trust | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-200"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-05-29"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_primary-trust#2026-05-30","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"Primary Trust | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-200"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-05-30"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_primary-trust#2026-05-31","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"Primary Trust | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-200"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-05-31"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_primary-trust#2026-06-02","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"Primary Trust | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-200"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-06-02"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_primary-trust#2026-06-03","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"Primary Trust | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-200"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-06-03"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_primary-trust#2026-06-04","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"Primary Trust | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-200"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-06-04"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_primary-trust#2026-06-05","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"Primary Trust | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-200"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-06-05"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_primary-trust#2026-06-06","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"Primary Trust | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-200"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-06-06"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary","http://schema.org/url":[{"@value":"https://www.crowstheatre.com/whats-on/view-all/mary-mary-mary-mary"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/image":[{"@value":"https://www.crowstheatre.com//de/cache/apps/shows/1012/w2400_MRY_2400x1350_Centered.jpg"}],"http://schema.org/description":[{"@language":"en","@value":"\u003cp\u003eA Crow’s Theatre Production \u003cbr /\u003eWritten by Erin Shields \u003cbr /\u003eDirected by Ellen McDougall\u003c/p\u003e \u003cp\u003eWorld Premiere \u003cbr /\u003eApr 7, 2026 - May 3, 2026 \u003cbr /\u003eGuloien Theatre\u003c/p\u003e"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K10-42"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-04-07"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-200"}],"http://schema.org/name":[{"@language":"en","@value":"Mary, Mary, Mary, Mary | Crow's Theatre"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.crowstheatre.com/whats-on/view-all/mary-mary-mary-mary#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-05-02"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-07"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-08"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-09"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-10"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-11"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-12"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-14"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-15"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-16"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-17"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-18"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-19"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-21"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-22"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-23"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-24"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-25"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-26"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-28"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-29"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-30"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-05-01"},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-05-02"}]},{"@id":"https://www.crowstheatre.com/whats-on/view-all/mary-mary-mary-mary#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"en"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T19:41:33.180-05:00"}],"http://schema.org/url":[{"@value":"https://www.crowstheatre.com/whats-on/view-all/mary-mary-mary-mary"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#Offer_0","http://schema.org/url":[{"@value":"https://www.crowstheatre.com/shows-events/schedule?shows=1012"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-07","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"Mary, Mary, Mary, Mary | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-200"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-04-07"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-08","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"Mary, Mary, Mary, Mary | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-200"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-04-08"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-09","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"Mary, Mary, Mary, Mary | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-200"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-04-09"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-10","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"Mary, Mary, Mary, Mary | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-200"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-04-10"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-11","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"Mary, Mary, Mary, Mary | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-200"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-04-11"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-12","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"Mary, Mary, Mary, Mary | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-200"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-04-12"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-14","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"Mary, Mary, Mary, Mary | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-200"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-04-14"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-15","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"Mary, Mary, Mary, Mary | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-200"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-04-15"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-16","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"Mary, Mary, Mary, Mary | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-200"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-04-16"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-17","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"Mary, Mary, Mary, Mary | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-200"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-04-17"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-18","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"Mary, Mary, Mary, Mary | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-200"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-04-18"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-19","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"Mary, Mary, Mary, Mary | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-200"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-04-19"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-21","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"Mary, Mary, Mary, Mary | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-200"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-04-21"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-22","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"Mary, Mary, Mary, Mary | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-200"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-04-22"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-23","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"Mary, Mary, Mary, Mary | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-200"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-04-23"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-24","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"Mary, Mary, Mary, Mary | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-200"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-04-24"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-25","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"Mary, Mary, Mary, Mary | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-200"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-04-25"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-26","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"Mary, Mary, Mary, Mary | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-200"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-04-26"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-28","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"Mary, Mary, Mary, Mary | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-200"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-04-28"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-29","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"Mary, Mary, Mary, Mary | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-200"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-04-29"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-04-30","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"Mary, Mary, Mary, Mary | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-200"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-04-30"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-05-01","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"Mary, Mary, Mary, Mary | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-200"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-05-01"}]},{"@id":"http://kg.footlight.io/resource/crowstheatre-com_mary-mary-mary-mary#2026-05-02","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"en","@value":"Mary, Mary, Mary, Mary | Crow's Theatre"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K11-200"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-05-02"}]}]
\ No newline at end of file
diff --git a/data/migration_baseline/culture3r_com.jsonld b/data/migration_baseline/culture3r_com.jsonld
new file mode 100644
index 00000000..bc3b150e
--- /dev/null
+++ b/data/migration_baseline/culture3r_com.jsonld
@@ -0,0 +1 @@
+[{"@id":"http://kg.footlight.io/resource/culture3r-com_debout","http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/debout/"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/09fd17c7-a410-4054-813d-f9652697ef9d"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Alex Roy\nDebout"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/06/Alex20Roy_-OS-PJuq2AA7Opf1jYkg_.jpg"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/debout/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-26T20:00:00-05:00"}],"http://schema.org/description":[{"@language":"fr","@value":"Après plus de 100 représentations et près de 25 000 billets vendus pour son premier one man show ‘’Des choses qui arrivent’’ Alex Roy, qui vous a fait hurler de rire durant les premières parties de Lise Dion, Les Grandes Crues et José Gaudet, nous replonge, dans son univers maladroit, surprenant et hilarant avec son tout nouveau spectacle « Debout » ! “Debout” parce que oui, c’est du stand-up, mais pas que ! Dans “Debout”, je vous raconte tout ce qui me pousse à sortir du lit chaque matin : mon métier, mes jumelles (qui n’ont aucune pitié pour la grasse matinée), et un quotidien rempli d’anecdotes aussi farfelues qu’inattendues. Un spectacle drôle, sincère et énergique… parce que rester debout, c’est déjà une victoire ! “Debout”, c’est plus qu’un simple spectacle de stand-up, c’est une plongée dans tout ce qui me donne envie de me lever le matin. Entre rêves, galères, absurdités du quotidien et grandes questions existentielles, je vous embarque dans un voyage drôle et sincère à travers mes motivations (parfois étranges) pour affronter chaque journée. Alors, qu’est-ce qui me tient debout ? Venez le découvrir… et peut-être que vous repartirez avec des raisons en plus de vous lever demain ! Alex Roy est un humoriste qui manie l’art du stand-up avec une énergie débordante et un sens aigu de l’autodérision. Avec son franc-parler et sa capacité à transformer les situations du quotidien en histoires hilarantes, il vous embarque dans un univers où chaque détail devient matière à rire. Que ce soit pour parler de sa famille, de ses expériences de vie ou de ses réflexions improbables, Alex Roy sait comment captiver son public en mêlant sincérité, absurdité et une bonne dose d’impertinence. Préparez-vous à rire… et à vous reconnaître (parfois malgré vous) dans ses anecdotes décapantes !"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_debout#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_debout#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_debout#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_debout#Offer_3"},{"@id":"http://kg.footlight.io/resource/culture3r-com_debout#Offer_4"},{"@id":"http://kg.footlight.io/resource/culture3r-com_debout#Offer_5"},{"@id":"http://kg.footlight.io/resource/culture3r-com_debout#Offer_6"},{"@id":"http://kg.footlight.io/resource/culture3r-com_debout#Offer_7"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-19T20:00:00-05:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culture3r-com_debout#2026-02-26T200000-0500"},{"@id":"http://kg.footlight.io/resource/culture3r-com_debout#2026-11-19T200000-0500"}]},{"@id":"http://kg.footlight.io/resource/09fd17c7-a410-4054-813d-f9652697ef9d","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Alex Roy"}],"http://schema.org/hasOccupation":[{"@value":"Humoriste"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}],"http://schema.org/url":[{"@value":"https://www.sixiemesens.ca/artiste/alex-roy/"}],"http://schema.org/sameAs":[{"@value":"https://www.facebook.com/alexroyfan"}]},{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Humour"}],"http://www.w3.org/2004/02/skos/core#closeMatch":[{"@id":"http://kg.artsdata.ca/resource/ComedyPerformance"}]},{"@id":"https://www.culture3r.com/evenements/debout/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:12:51.167-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/debout/"}]},{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e","@type":["http://schema.org/Place"],"http://schema.org/name":[{"@language":"fr","@value":"Théâtre du Cégep de Trois-Rivières"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"point_of_interest, establishment at 3175 Boulevard Laviolette, Trois-Rivières, QC G8Z 3W2, Canada"}],"http://schema.org/address":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e#PostalAddress"}],"http://schema.org/longitude":[{"@value":"-72.5677566"}],"http://schema.org/latitude":[{"@value":"46.3585624"}],"http://schema.org/sameAs":[{"@value":"https://maps.google.com/?cid=4986869985272758705"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_debout#Offer_0","http://schema.org/price":[{"@value":"39.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_debout#Offer_1","http://schema.org/price":[{"@value":"35.60"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_debout#Offer_2","http://schema.org/price":[{"@value":"33.90"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_debout#Offer_3","http://schema.org/price":[{"@value":"32.20"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_debout#Offer_4","http://schema.org/price":[{"@value":"41.00"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culture3r-com_debout#Offer_5","http://schema.org/price":[{"@value":"37.60"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culture3r-com_debout#Offer_6","http://schema.org/price":[{"@value":"35.90"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culture3r-com_debout#Offer_7","http://schema.org/price":[{"@value":"34.20"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culture3r-com_debout#2026-02-26T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Alex Roy\nDebout"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-26T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_debout#2026-11-19T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Alex Roy\nDebout"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-19T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mehdi-bousaidan","@type":["http://schema.org/Event"],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-272"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/04/Mehdi20Bousaidan_-OMwItZbOhR6z3euYVWJ_.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/mehdi-bousaidan/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/description":[{"@language":"fr","@value":"Dans son tout nouveau spectacle, Mehdi Bousaidan se dévoile comme jamais auparavant. Avec ce troisième one-man show, il choisit de faire une introspection sur sa vie et nous en partage une analyse intime avec l’humour efficace qu’on lui connaît. Ce spectacle est une invitation à jeter un œil à travers la fenêtre de sa vie. C’est un voyage où l’on avance de sa tendre enfance à l’homme qu’il est aujourd’hui en se rapprochant un peu plus de sa réalité. À travers des anecdotes surprenantes, il brouille la frontière entre l’histoire personnelle et l’universel. Il aborde aussi des questions existentielles, comme le mariage, les relations familiales, les peurs et les craintes ainsi que les expériences qui forgent l’âme. C’est un Mehdi qu’on reconnaît, certes, de son sens critique aiguisé, de ses analyses inhabituelles et de ses punchs inattendus, mais c’est aussi un Mehdi qu’on apprend à connaître sous un nouveau visage. Il nous ouvre le livre de sa vie privée pour une toute première fois dans un humour toujours aussi rafraîchissant."}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/mehdi-bousaidan/"}],"http://schema.org/name":[{"@language":"fr","@value":"Mehdi Bousaidan\nLe garçon et la fenêtre"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-20T20:00:00-05:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_mehdi-bousaidan#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_mehdi-bousaidan#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_mehdi-bousaidan#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_mehdi-bousaidan#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/mehdi-bousaidan/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:13:11.496-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/mehdi-bousaidan/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mehdi-bousaidan#Offer_0","http://schema.org/price":[{"@value":"43.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mehdi-bousaidan#Offer_1","http://schema.org/price":[{"@value":"39.40"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mehdi-bousaidan#Offer_2","http://schema.org/price":[{"@value":"37.60"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mehdi-bousaidan#Offer_3","http://schema.org/price":[{"@value":"35.80"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_real-beland-comme-chez-nous","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-242"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/real-beland-comme-chez-nous/#WebPage"}],"http://schema.org/name":[{"@language":"fr","@value":"Réal Béland\nComme Chez nous"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/real-beland-comme-chez-nous/"}],"http://schema.org/description":[{"@language":"fr","@value":"« Comme chez nous », c’est le 6ème show de Réal Béland! Un spectacle qui entre dans l’intimité d’un comique qui comme le dit la rumeur a été conçu dans les coulisses d’une salle de spectacle. Après avoir passé sa vie dans les salles, Réal se sent chez lui lorsqu’il s’arrête en tournée chez vous! Une simplicité désarmante avec ces gags absurdes, son M. Latreille et beaucoup de surprises!!!"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2024/04/RC3A9al20BC3A9land_-NvqlC-zeBSjBzL9dNyA_.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-03T20:00:00-04:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_real-beland-comme-chez-nous#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_real-beland-comme-chez-nous#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_real-beland-comme-chez-nous#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_real-beland-comme-chez-nous#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/real-beland-comme-chez-nous/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:13:01.285-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/real-beland-comme-chez-nous/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_real-beland-comme-chez-nous#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"47.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_real-beland-comme-chez-nous#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"43.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_real-beland-comme-chez-nous#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"41.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_real-beland-comme-chez-nous#Offer_3","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"39.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_plateau-double-futur-danse-capas-label-de-danse","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Plateau double : FUTUR DANSE\nLa danse sur les routes du Québec"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/plateau-double-futur-danse-capas-label-de-danse/"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/04/Plateau20double203A20FUTUR20DANSE_-ON_J89kWbx9bjrxJcJI_.jpg"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/2fa05002-967b-43c2-b5cb-fc93a25edcb1"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/description":[{"@language":"fr","@value":"Pour une seconde année, je vous propose de découvrir les œuvres de deux chorégraphes prometteurs de la relève. Avec le spectacle Homines per Terram Andrian, les danseurs nous emportent dans leur récit qui explore la connexion intime et primordiale entre l’homme et la Terre. Du côté de Nina Chati, préparez-vous à découvrir un solo impressionnant dans lequel les mouvements saccadés et spécifiques à ce type de danse sont esthétiquement surprenants.– Lisa Dugré, programmatrice aux arts de la scène. Le programme double FUTUR DANSE vous invite à découvrir les œuvres d’artistes émergent·e·s, vous entraînant au contact d’une danse surprenante et vibrante. Dans IN NA, un solo pluridisciplinaire captivant, Nina Chati fusionne Popping et danse contemporaine pour explorer la genèse d’un personnage à travers des métamorphoses poétiques. En écho, Homines per Terram d’Adrian W.S. Batt réunit trois danseurs dans une ode chorégraphique à la nature, proposant une vision régénératrice de la masculinité. Une soirée sous le signe de l’audace, à la rencontre de chorégraphes et d’interprètes plein·e·s d’avenir, pour vibrer au rythme de la danse de demain. Votre expérience de spectacle sera enrichie d’une médiation pour ouvrir le dialogue sur l’œuvre proposée : une présentation de la pièce en amont ainsi qu’une discussion de 15 minutes après la représentation. Ces moments d’échanges vous inviteront à tisser des liens, partager vos impressions et nourrir une réflexion collective. La culture devient alors un vecteur de rencontres et de changement. Image de marque et textes : CAPAS • Label de danse Photos: Caroline Thibault Vidéo :Mikhaïl-Amadeus H. Plante FUTUR DANSE est une initiative de La danse sur les routes du Québec Soutenue par le Conseil des arts et des lettres du Québec, Centre de Création Diffusion de Gaspé, Culture Trois-Rivières, Diffusion En Scène – Théâtre Gilles Vigneault, Diffusion Momentum – Carré 150, La SPEC du Haut-Richelieu, Le Petit Théâtre du Vieux Noranda, le Regroupement québécois de la danse, Tangente, Caisse Desjardins de la Culture – Grand Partenaire de Futur Danse Distribution: sujette à changement sans préavis"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-05T20:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/plateau-double-futur-danse-capas-label-de-danse/#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_plateau-double-futur-danse-capas-label-de-danse#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_plateau-double-futur-danse-capas-label-de-danse#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_plateau-double-futur-danse-capas-label-de-danse#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_plateau-double-futur-danse-capas-label-de-danse#Offer_3"}]},{"@id":"http://kg.footlight.io/resource/2fa05002-967b-43c2-b5cb-fc93a25edcb1","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Danse"}],"http://www.w3.org/2004/02/skos/core#closeMatch":[{"@id":"http://kg.artsdata.ca/resource/DancePerformance"}]},{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74","@type":["http://schema.org/Place"],"http://schema.org/address":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74#PostalAddress"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"point_of_interest, establishment at 1425 Pl. de l'Hôtel de Ville, Trois-Rivières, QC G9A 5H3, Canada"}],"http://schema.org/name":[{"@language":"fr","@value":"Salle Anaïs-Allard-Rousseau"}],"http://schema.org/containedInPlace":[{"@id":"http://kg.footlight.io/resource/efff2c7b-43e1-46e9-99c1-d50f6ab0e45a"}],"http://schema.org/latitude":[{"@value":"46.34281919999999"}],"http://schema.org/longitude":[{"@value":"-72.5429229"}],"http://schema.org/sameAs":[{"@value":"https://maps.google.com/?cid=1580439315557727375"}]},{"@id":"https://www.culture3r.com/evenements/plateau-double-futur-danse-capas-label-de-danse/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:13:20.527-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/plateau-double-futur-danse-capas-label-de-danse/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_plateau-double-futur-danse-capas-label-de-danse#Offer_0","http://schema.org/price":[{"@value":"28.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_plateau-double-futur-danse-capas-label-de-danse#Offer_1","http://schema.org/price":[{"@value":"25.70"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_plateau-double-futur-danse-capas-label-de-danse#Offer_2","http://schema.org/price":[{"@value":"24.55"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_plateau-double-futur-danse-capas-label-de-danse#Offer_3","http://schema.org/price":[{"@value":"23.40"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mona-de-grenoble","@type":["http://schema.org/Event"],"http://schema.org/description":[{"@language":"fr","@value":"J’aurais pu appeler ce show d’humour-là “Étalon en talons” ou “Entre Bâton Rouge et matante” mais après j’ai désaoulé pis je me suis rendue compte que vous allez sûrement vous reconnaître dans mon personnage que certains décrivent comme étant “flamboyant, grivois et solvable depuis Big Brother”. Parce que, dans le fond, on le sait, les paillettes, les robes pis les grosses perruques c’est juste “de la poudre aux yeux”. Membres Accès Plus: Veuillez prendre note qu’exceptionnellement les rabais progressifs pour le 19 juin 2026 ne sont pas applicables pour cette représentation. "}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/6804e1a6-d2c6-4962-b0ee-02fed55513cc"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-06-19T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/mona-de-grenoble/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/9475525c-10fa-4a53-b89b-8c7d9ba68726"}],"http://schema.org/name":[{"@language":"fr","@value":"Mona de Grenoble\nDe la poudre aux yeux"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2023/11/Mona20de20Grenoble_-NkWC0e_zmSfxBcV0rnN_.jpg"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/mona-de-grenoble/"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_mona-de-grenoble#Offer_0"}]},{"@id":"http://kg.footlight.io/resource/6804e1a6-d2c6-4962-b0ee-02fed55513cc","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Mona de Grenoble"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}],"http://schema.org/hasOccupation":[{"@value":"Humoriste"}]},{"@id":"https://www.culture3r.com/evenements/mona-de-grenoble/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:13:30.660-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/mona-de-grenoble/"}]},{"@id":"http://kg.footlight.io/resource/9475525c-10fa-4a53-b89b-8c7d9ba68726","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Variétés"}],"http://www.w3.org/2004/02/skos/core#closeMatch":[{"@id":"http://kg.artsdata.ca/resource/VarietyShow"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mona-de-grenoble#Offer_0","http://schema.org/price":[{"@value":"47.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_uqtr-en-spectacle-encouragez-votre-uqtr","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"UQTR en spectacle"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/uqtr-en-spectacle-encouragez-votre-uqtr/"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/12/UQTR20en20spectacle_-OgmhPmo55f8TiPOc_G__.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-05T19:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/uqtr-en-spectacle-encouragez-votre-uqtr/#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/9475525c-10fa-4a53-b89b-8c7d9ba68726"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/description":[{"@language":"fr","@value":"UQTR en spectacle est un évènement annuel de l’Université du Québec à Trois-Rivières qui célèbre les talents étudiants en arts de la scène. Musique, chant, danse, humour et cirque sont à l’honneur, offrant aux étudiantes et étudiants une scène pour briller, performer et créer des liens. L’évènement contribue à faire vibrer et animer la vie sur le campus. Membres Accès Plus: Veuillez prendre note que les rabais progressifs ne sont pas applicables pour cette représentation. "}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_uqtr-en-spectacle-encouragez-votre-uqtr#Offer_0"}]},{"@id":"https://www.culture3r.com/evenements/uqtr-en-spectacle-encouragez-votre-uqtr/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:13:41.186-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/uqtr-en-spectacle-encouragez-votre-uqtr/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_uqtr-en-spectacle-encouragez-votre-uqtr#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"15.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_transformations","@type":["http://schema.org/Event"],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2025-09-01"}],"http://schema.org/description":[{"@language":"fr","@value":"VOYAGEZ AU COEUR DE L’INDUSTRIE PAPETIÈRE! Dans un parcours libre et immersif, remontez le fil du temps pour aller à la rencontre des acteurs d’une épopée marquante. À l’intérieur de l’ancienne usine de filtration de la CIP (Canadian International Paper), vous serez d’abord confrontés à la dure réalité des travailleurs et travailleuses du siècle précédent. Vous explorerez ensuite les enjeux technologiques et environnementaux d’une industrie en constante mutation. De l’usine à la forêt, grâce aux mémoires d’hier et aux perspectives d’avenir, devenez les témoins privilégiés d’un monde porté par les Transformations. Tout au long du trajet, l’authenticité du bâtiment centenaire où se dévoilent témoignages, artéfacts, défis interactifs, décors étonnants et vue imprenable sur le fleuve, contribue à faire de cette visite une expérience multisensorielle, émouvante et suscitant la réflexion. Acheter des billets TARIFS "}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-12-31"}],"http://schema.org/name":[{"@language":"fr","@value":"Transformations"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/transformations/#WebPage"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/transformations/"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/7cfa3a3f-d283-4e28-9f09-2b0756ac2e27"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2024/05/2024-pat-bor-expo-transformations-photo-cr-etienne-boisvert-13-scaled.jpg"}]},{"@id":"https://www.culture3r.com/evenements/transformations/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:13:51.138-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/transformations/"}]},{"@id":"http://kg.footlight.io/resource/7cfa3a3f-d283-4e28-9f09-2b0756ac2e27","@type":["http://schema.org/Place"],"http://schema.org/name":[{"@language":"fr","@value":"Boréalis"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"tourist_attraction, museum, restaurant, point_of_interest, food, establishment at 200 Av. des Draveurs, Trois-Rivières, QC G9A 2J2, Canada"}],"http://schema.org/address":[{"@id":"http://kg.footlight.io/resource/7cfa3a3f-d283-4e28-9f09-2b0756ac2e27#PostalAddress"}],"http://schema.org/longitude":[{"@value":"-72.5322169"}],"http://schema.org/latitude":[{"@value":"46.3485836"}],"http://schema.org/sameAs":[{"@value":"https://maps.google.com/?cid=15696382818797367464"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_simon-gouache-pour-linstant","http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/cd2aff1e-3955-4619-a5f6-26e833e92a75"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-18T20:00:00-04:00"}],"http://schema.org/name":[{"@language":"fr","@value":"Simon Gouache\nPour l’instant"}],"http://schema.org/description":[{"@language":"fr","@value":"« Je vais faire ça, pour l’instant… » C’est ce que Simon Gouache s’est dit quand il s’est lancé en stand-up, il y a maintenant plus de vingt ans. Visiblement, cet instant dure toujours. De ce qui devait être une courte aventure est née une grande passion, qui est au premier plan de ce quatrième spectacle. "}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/simon-gouache-pour-linstant/"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/09/Simon20Gouache_-OZowDBHPQSelldUwSmx_.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/simon-gouache-pour-linstant/#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_simon-gouache-pour-linstant#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_simon-gouache-pour-linstant#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_simon-gouache-pour-linstant#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_simon-gouache-pour-linstant#Offer_3"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-08T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culture3r-com_simon-gouache-pour-linstant#2026-03-18T200000-0400"},{"@id":"http://kg.footlight.io/resource/culture3r-com_simon-gouache-pour-linstant#2026-08-08T200000-0400"}]},{"@id":"http://kg.footlight.io/resource/cd2aff1e-3955-4619-a5f6-26e833e92a75","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Simon Gouache"}],"http://schema.org/hasOccupation":[{"@value":"Humoriste"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}],"http://schema.org/sameAs":[{"@value":"http://kg.artsdata.ca/resource/K12-233"}]},{"@id":"https://www.culture3r.com/evenements/simon-gouache-pour-linstant/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:13:54.033-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/simon-gouache-pour-linstant/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_simon-gouache-pour-linstant#Offer_0","http://schema.org/price":[{"@value":"44.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_simon-gouache-pour-linstant#Offer_1","http://schema.org/price":[{"@value":"40.30"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_simon-gouache-pour-linstant#Offer_2","http://schema.org/price":[{"@value":"38.45"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_simon-gouache-pour-linstant#Offer_3","http://schema.org/price":[{"@value":"36.60"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_simon-gouache-pour-linstant#2026-03-18T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Simon Gouache\nPour l’instant"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-18T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_simon-gouache-pour-linstant#2026-08-08T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Simon Gouache\nPour l’instant"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-08T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_harry-manx-2","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K2-5118"},{"@id":"http://kg.artsdata.ca/resource/K2-3127867"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/11/Harry20Manx_-Of4rveeqdWd6-l72nC1_.jpg"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/harry-manx-2/"}],"http://schema.org/name":[{"@language":"fr","@value":"Harry Manx\nMystic Blues"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/harry-manx-2/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-06T20:00:00-05:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/description":[{"@language":"fr","@value":"Agent de rencontres interculturelles, Harry Manx marie musique classique indienne et blues du sud des États-Unis. Né sur l’Île de Man, le bluesman est un citoyen du monde. Après avoir passé son enfance au Canada, il partage ensuite sa vie entre Europe, Japon, Inde et Brésil avant de revenir s’installer en Amérique du Nord. C’est au fil de ses nombreux voyages qu’il perfectionne son identité sonore et qu’il peaufine ses spectacles sur des coins de rue, dans des bars ou cafés, mais aussi dans plusieurs festivals. La musique indienne captive particulièrement Harry Manx et, au milieu des années 1980, il commence sa tutelle de cinq ans avec le Maître Rajasthani Vishwa Mohan Bhatt qui lui apprend à jouer la fameuse sitar/ guitare Mohan Veena qui devient son instrument fétiche. L’artiste est ainsi qualifié de «liaison essentielle» entre musiques de l’Est et de l’Ouest, créant des histoires musicales mariant la tradition du blues et la profondeur du raga indien classique sous l’appelation «mysticssippi». Fort d’une riche discographie, Harry Manx compte à son actif de nombreuses tournées aux quatre coins du monde. Sa réputation sur scène lui ouvre les portes de festivals internationaux prestigieux et des clubs de blues parmi les plus réputés à travers la planète. Instrumentiste polyvalent maniant aussi bien sitar, banjo, harmonica, guitare lap steel avec un bonheur communicatif, il transporte son plublic au cœur de la «Harry Zone»."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_harry-manx-2#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_harry-manx-2#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_harry-manx-2#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_harry-manx-2#Offer_3"}]},{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Chanson"}],"http://www.w3.org/2004/02/skos/core#closeMatch":[{"@id":"http://kg.artsdata.ca/resource/Chanson"}]},{"@id":"https://www.culture3r.com/evenements/harry-manx-2/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:14:06.320-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/harry-manx-2/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_harry-manx-2#Offer_0","http://schema.org/price":[{"@value":"40.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_harry-manx-2#Offer_1","http://schema.org/price":[{"@value":"36.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_harry-manx-2#Offer_2","http://schema.org/price":[{"@value":"34.75"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_harry-manx-2#Offer_3","http://schema.org/price":[{"@value":"33.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_alexandre-barrette-ouvert","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K13-7"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-10T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/alexandre-barrette-ouvert/#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/name":[{"@language":"fr","@value":"Alexandre Barrette\nOuvert"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/10/Alexandre20Barrette_-OcGYFRQll0SOsntdOAg_.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"Mon nouveau show s’appelle OUVERT. Bien que je sois habituellement pudique sur ma vie privée, je m’ouvre comme jamais dans ce spectacle! Et aussi, quand même important, je crois que vous allez rire beaucoup!"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/alexandre-barrette-ouvert/"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_alexandre-barrette-ouvert#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_alexandre-barrette-ouvert#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_alexandre-barrette-ouvert#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_alexandre-barrette-ouvert#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/alexandre-barrette-ouvert/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:14:18.304-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/alexandre-barrette-ouvert/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_alexandre-barrette-ouvert#Offer_0","http://schema.org/price":[{"@value":"49.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_alexandre-barrette-ouvert#Offer_1","http://schema.org/price":[{"@value":"44.80"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_alexandre-barrette-ouvert#Offer_2","http://schema.org/price":[{"@value":"42.70"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_alexandre-barrette-ouvert#Offer_3","http://schema.org/price":[{"@value":"40.60"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_formes-et-racines","@type":["http://schema.org/Event"],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Formes et racines"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/1272f78a-b32d-481a-9143-df6737cf0580"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-04T10:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/formes-et-racines/#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-453"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/formes-et-racines/"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/10/Formes20et20racines_-Oa_yGhciYirqppAS109_-scaled.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"6 ans et + | 60 minutes Plongez en famille dans l’univers artistique de Caroline Monnet, artiste multidisciplinaire reconnue pour son travail alliant héritage autochtone et esthétique contemporaine. Lors de cet atelier, petits et grands exploreront la géométrie, les motifs inspirés des cultures autochtones et le langage des formes à travers la création collective d’une œuvre visuelle. À partir de matériaux simples et accessibles, les participants seront invités à imaginer des compositions abstraites qui expriment leur propre identité ou leur lien au territoire. Une activité à la fois créative et enrichissante, qui permet de découvrir une démarche artistique engagée, tout en stimulant la curiosité, le dialogue et la collaboration en famille. Les enfants doivent être accompagnés d’un adulte pour cette activité. Voir l'exposition liée "}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_formes-et-racines#Offer_0"}]},{"@id":"http://kg.footlight.io/resource/1272f78a-b32d-481a-9143-df6737cf0580","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Activités"}],"http://www.w3.org/2004/02/skos/core#closeMatch":[{"@id":"http://kg.artsdata.ca/resource/WorkshopEvent"}]},{"@id":"https://www.culture3r.com/evenements/formes-et-racines/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:14:27.550-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/formes-et-racines/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_formes-et-racines#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culture3r-com_silvi-tourigny-rodage","@type":["http://schema.org/Event"],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-23T20:00:00-05:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2024/09/Silvi20Tourigny_-O7FhcbpnHO0rhjL_Mn5_.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/silvi-tourigny-rodage/#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"L’humoriste Silvi Tourigny s’apprête à brûler les planches avec son premier spectacle solo, En feu! Ce one-woman-show est une plongée hilarante dans son vécu, mais surtout dans ses envies, celles d’une femme libérée et assumée. Au cours des dernières années, il s’en est passé des choses dans sa vie : une maison, un enfant, une moto, une séparation, une autre maison, des rencontres douteuses, une autre relation et c’est sans compter sa passion beaucoup trop intense pour les animaux! Silvi et sa fidèle acolyte, Carole, sont plus enflammées que jamais et prêtes à non seulement vous faire rire, mais également à vous donner le goût de danser! Avec son naturel désarmant, la rouquine fait souffler un vent d’énergie et d’humour qui séduit à grands éclats de rire. Comme dirait Carole : « Pas besoin de t’avertir que tu vas avoir du fun parce que Silvi est EN FEU!!! » Vidéo En coulisses avec Silvi Tourigny "}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/silvi-tourigny-rodage/"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K2-2206"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Silvi Tourigny\nEn feu"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_silvi-tourigny-rodage#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_silvi-tourigny-rodage#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_silvi-tourigny-rodage#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_silvi-tourigny-rodage#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/silvi-tourigny-rodage/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-21T23:14:09.738-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/silvi-tourigny-rodage/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_silvi-tourigny-rodage#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"39.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_silvi-tourigny-rodage#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"35.60"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_silvi-tourigny-rodage#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"33.90"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_silvi-tourigny-rodage#Offer_3","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"32.20"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_parabolous-complete-tool-experience","@type":["http://schema.org/Event"],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/parabolous-complete-tool-experience/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/parabolous-complete-tool-experience/"}],"http://schema.org/name":[{"@language":"fr","@value":"Parabolus\nComplete Tool Experience"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/07/Parabolus_-OUtOtwvQvxn4WUnLYWx_.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-06-20T20:00:00-04:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/description":[{"@language":"fr","@value":"-= PARABOLUS – Complete TOOL Experience =- Un hommage puissant et immersif au groupe légendaire TOOL. Parabolus capture l’essence de TOOL avec précision et passion — rendant hommage à leur musique complexe et émotionnelle, à leur art visuel visionnaire, à leur philosophie profonde et à leurs performances scéniques inoubliables. Du son aux instruments, de l’ambiance à l’intensité, nous avons fait les recherches et pris le temps de réunir tout ce qu’il faut pour vous offrir une expérience fidèle à l’original. Suivez la spirale et entrez dans l’univers de TOOL. Préparez-vous à être transformé. Membres Accès Plus: Veuillez prendre note qu’exceptionnellement les rabais progressifs ne sont pas applicables pour cette représentation. "}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_parabolous-complete-tool-experience#Offer_0"}]},{"@id":"https://www.culture3r.com/evenements/parabolous-complete-tool-experience/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:14:36.527-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/parabolous-complete-tool-experience/"}]},{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Musique"}],"http://www.w3.org/2004/02/skos/core#closeMatch":[{"@id":"http://kg.artsdata.ca/resource/MusicPerformance"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_parabolous-complete-tool-experience#Offer_0","http://schema.org/price":[{"@value":"39.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_marjo-jlache-pas","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/4a753c17-28e2-4edb-a969-127de3894a08"}],"http://schema.org/name":[{"@language":"fr","@value":"Marjo\nJ’Lâche pas!"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/10/Marjo_-Obc9vaZLDgfs3GWaSdC_.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-25T20:00:00-04:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/marjo-jlache-pas/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/marjo-jlache-pas/"}],"http://schema.org/description":[{"@language":"fr","@value":"Rockeuse émouvante et authentique, Marjo vous offre ses plus grands succès aux rythmes endiablés, ainsi que certaines de ses plus belles balades. Accompagnée de ses quatre musiciens et fidèle à sa réputation, elle vous électrisera avec son dynamisme et vous transmettra sa passion pour la musique jusqu’à vous faire dresser le poil sur les bras. "}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_marjo-jlache-pas#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_marjo-jlache-pas#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_marjo-jlache-pas#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_marjo-jlache-pas#Offer_3"}]},{"@id":"http://kg.footlight.io/resource/4a753c17-28e2-4edb-a969-127de3894a08","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Marjo"}],"http://schema.org/hasOccupation":[{"@value":"Auteur-compositeur-interprète"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Auteur-compositeur-interprète"}],"http://schema.org/sameAs":[{"@value":"http://www.wikidata.org/entity/Q1396253"},{"@value":"https://www.facebook.com/marjomusique/"}]},{"@id":"https://www.culture3r.com/evenements/marjo-jlache-pas/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:14:45.756-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/marjo-jlache-pas/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_marjo-jlache-pas#Offer_0","http://schema.org/price":[{"@value":"57.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_marjo-jlache-pas#Offer_1","http://schema.org/price":[{"@value":"52.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_marjo-jlache-pas#Offer_2","http://schema.org/price":[{"@value":"49.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_marjo-jlache-pas#Offer_3","http://schema.org/price":[{"@value":"47.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_fallait-etre-la","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/10/Fallait20C3AAtre20lC3A0_-ObDAFDKSNS0fO_eHgdc_.jpg"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/name":[{"@language":"fr","@value":"Fallait être là\nAnimé par Philippe Laprise"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-09T20:00:00-05:00"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/description":[{"@language":"fr","@value":"Fallait être là est un nouveau concept de spectacle humoristique et interactif qui redéfinit les codes de l’impro et de l’humour de scène traditionnel afin de créer des moments déjantés. Philippe Laprise, maître de cérémonie, accueille quatre invités vedettes : Magali Saint-Vincent, Michelle Desrochers, LeLouis Courchesne ainsi que Mathieu Dufour, qui participent à des défis humoristiques décalés, avec comme objectif de déclencher les plus grands fous rires. Fallait être là est un spectacle évènementiel audacieux, hilarant et surprenant, dans lequel le public sera amené à participer à plusieurs niveaux. Assistez à une expérience inédite de laquelle vous sortirez en vous disant : « Fallait être là! ». Fallait être là – Le jeu! 1 animateur, 4 improvisateurs vedettes, 0 reprise"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/fallait-etre-la/"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/fallait-etre-la/#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_fallait-etre-la#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_fallait-etre-la#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_fallait-etre-la#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_fallait-etre-la#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/fallait-etre-la/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-07T23:15:09.031-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/fallait-etre-la/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_fallait-etre-la#Offer_0","http://schema.org/price":[{"@value":"47.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_fallait-etre-la#Offer_1","http://schema.org/price":[{"@value":"43.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_fallait-etre-la#Offer_2","http://schema.org/price":[{"@value":"41.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_fallait-etre-la#Offer_3","http://schema.org/price":[{"@value":"39.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_maxim-martin-bon-chien","@type":["http://schema.org/Event"],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/description":[{"@language":"fr","@value":"Même après cinq one-man show, un livre et une série sur son quotidien avec sa fille, Maxim Martin vous fera découvrir de nouveaux aspects de son attachante personnalité. Toujours aussi authentique, il se dévoile dans cette nouvelle période où il tente par tous les moyens de simplifier sa vie dans une époque où tout lui semble de plus en plus complexe. Bon chien, son sixième one-man show, se veut une fenêtre ouverte sur son chemin parsemé de petites et grandes frustrations vers la zénitude"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-275"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Maxim Martin\nBon chien"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2024/04/Maxim20Martin_-NwKu2rROmdz2ZhLSgAN_.jpeg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/maxim-martin-bon-chien/#WebPage"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-30T20:00:00-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/maxim-martin-bon-chien/"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_maxim-martin-bon-chien#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_maxim-martin-bon-chien#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_maxim-martin-bon-chien#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_maxim-martin-bon-chien#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/maxim-martin-bon-chien/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-28T23:13:03.319-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/maxim-martin-bon-chien/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_maxim-martin-bon-chien#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"43.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_maxim-martin-bon-chien#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"39.40"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_maxim-martin-bon-chien#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"37.60"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_maxim-martin-bon-chien#Offer_3","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"35.80"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jean-francois-otis-avec-plaisir","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/b2692872-17d2-4576-87e3-5fb152802e3e"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/description":[{"@language":"fr","@value":"L’humoriste JF Otis présente officiellement son tout premier one-man-show : Avec plaisir! Ce raconteur hors pair, fier représentant de la région de Rimouski (plus précisément de Saint-Anaclet-de-Lessard) et autoproclamé « spécialiste en prise de décisions moyennes », a véritablement trouvé sa place sur scène avec ce premier spectacle ou il y cultive la fierté de son identité régionale le tout muni d’un charisme désarmant. Son incroyable bagou et son magnétisme aussi naturel qu’irrésistible lui permettent de rallier un large public qui ne peut s’empêcher de l’adopter, lui et son humour authentique. Avec une présence phénoménale sur les réseaux sociaux, JF Otis cumule un total impressionnant de près de 225 000 abonnés sur ses plateformes, dont 130 000 sur TikTok où ses capsules humoristiques ont récolté plus de 3,6 millions de j’aime. Fort de cette popularité virale, JF Otis transpose dès maintenant et avec brio la richesse de son univers sur scène avec son premier one-man-show. L’humour savoureux qui a fait son succès, qu’il s’agisse de ses hilarants conseils sur la vie de couple, de ses anecdotes personnelles et familiales ou ses hommages sentis à des métiers exigeants sont d’ailleurs au cœur d’Avec plaisir! . Toujours rassembleur et profondément attachant, l’humoriste explore avec finesse des sujets variés avec une approche accessible et sans artifice dans un stand-up des plus classiques. « Ce spectacle, c’est une invitation à passer un bon moment ensemble puisque c’est Avec plaisir que je monte sur scène chaque soir pour partager mes histoires, mes observations… et surtout, pour vous entendre rire. Sachez d’ailleurs que mes histoires sont souvent inspirées par ma famille, une véritable galerie de personnages ! Mes parents, ma sœur, ma grand-mère, mon oncle… ils alimentent mon humour au quotidien. Honnêtement, je suis loin d’être le plus drôle de ma gang, maintenant, à vous de venir en juger ! » – JF Otis JF Otis, qui cosigne les textes avec l’humoriste et chevronné auteur Simon Delisle, convie le public à une rencontre chaleureuse, dans une ambiance festive, comme si l’on était tous autour d’un feu de camp en compagnie d’un vieil ami racontant des anecdotes! "}],"http://schema.org/name":[{"@language":"fr","@value":"Jean-François Otis\nAvec plaisir!"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/jean-francois-otis-avec-plaisir/"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/jean-francois-otis-avec-plaisir/#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/11/Jean-FranC3A7ois20Otis_-OeRRd8I7iqKp4miHXD5_.png"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-21T20:00:00-04:00"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_jean-francois-otis-avec-plaisir#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_jean-francois-otis-avec-plaisir#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_jean-francois-otis-avec-plaisir#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_jean-francois-otis-avec-plaisir#Offer_3"}]},{"@id":"http://kg.footlight.io/resource/b2692872-17d2-4576-87e3-5fb152802e3e","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Jean-François Otis"}],"http://schema.org/hasOccupation":[{"@language":"fr","@value":"Humoriste"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}]},{"@id":"https://www.culture3r.com/evenements/jean-francois-otis-avec-plaisir/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:15:08.546-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/jean-francois-otis-avec-plaisir/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jean-francois-otis-avec-plaisir#Offer_0","http://schema.org/price":[{"@value":"39.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jean-francois-otis-avec-plaisir#Offer_1","http://schema.org/price":[{"@value":"35.60"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jean-francois-otis-avec-plaisir#Offer_2","http://schema.org/price":[{"@value":"33.90"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jean-francois-otis-avec-plaisir#Offer_3","http://schema.org/price":[{"@value":"32.20"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_josiane-aubuchon","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/9c562add-7eec-4dfb-b22b-1ee220ec9073"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-08T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/josiane-aubuchon/#WebPage"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/josiane-aubuchon/"}],"http://schema.org/name":[{"@language":"fr","@value":"Josiane Aubuchon\nRodage – Josiane en promenade"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/09/Josiane20Aubuchon_-Oa0X-gRoFx7IN1-_gi__.jpg"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/description":[{"@language":"fr","@value":"Après trois ans en résidence mensuelle à Montréal, Josiane Aubuchon part en promenade à travers la province! Une occasion unique de la voir déployer son talent comique pendant 75 minutes. Fidèle à sa réputation, c’est entièrement décomplexée qu’elle s’offrira au public, tel un grand buffet festif. Chaleureuse et spontanée, impossible de prévoir les thématiques abordées. Josiane a sa recette bien à elle, un peu d’impro, un peu de folie et une touche de ruralité. Après plus d’une trentaine de représentations affichant complètes à Montréal, elle se promène enfin partout au Québec! Membres Accès Plus: Veuillez prendre note qu’exceptionnellement seuls les rabais de 10 % et 15 % est disponible pour ce spectacle."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_josiane-aubuchon#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_josiane-aubuchon#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_josiane-aubuchon#Offer_2"}]},{"@id":"http://kg.footlight.io/resource/9c562add-7eec-4dfb-b22b-1ee220ec9073","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Josiane Aubuchon"}],"http://schema.org/hasOccupation":[{"@language":"fr","@value":"Humoriste"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}],"http://schema.org/sameAs":[{"@value":"https://www.facebook.com/JosianeAububu/"}]},{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec","@type":["http://schema.org/Place"],"http://schema.org/name":[{"@language":"fr","@value":"Salle Louis-Philippe-Poisson"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"point_of_interest, establishment at 1425 Pl. de l'Hôtel de Ville, Trois-Rivières, QC G9A 5H3, Canada"}],"http://schema.org/address":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec#PostalAddress"}],"http://schema.org/longitude":[{"@value":"-72.5427093"}],"http://schema.org/latitude":[{"@value":"46.3427162"}],"http://schema.org/sameAs":[{"@value":"https://maps.google.com/?cid=6412301651512659421"}],"http://schema.org/containedInPlace":[{"@id":"http://kg.artsdata.ca/resource/K5-703"}]},{"@id":"https://www.culture3r.com/evenements/josiane-aubuchon/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:14:58.652-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/josiane-aubuchon/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_josiane-aubuchon#Offer_0","http://schema.org/price":[{"@value":"35.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_josiane-aubuchon#Offer_1","http://schema.org/price":[{"@value":"32.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_josiane-aubuchon#Offer_2","http://schema.org/price":[{"@value":"30.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-luc-pomerleau-moqueur-polyglotte","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2023/01/show_-NJjytklaQGEO-L3uhOt_.jpg"}],"http://schema.org/name":[{"@language":"fr","@value":"Pierre-Luc Pomerleau\nMoqueur polyglotte"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/7d187cec-b726-4f2d-9af4-40a6375a5ba9"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/pierre-luc-pomerleau-moqueur-polyglotte/"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-16T20:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/pierre-luc-pomerleau-moqueur-polyglotte/#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/description":[{"@language":"fr","@value":"DERNIÈRE CHANCE Moqueur polyglotte : Oiseau rare qui se démarque par sa capacité à drôlement bien imiter les voix et les mimiques de ses compères. Pierre-Luc Pomerleau, humoriste à l’efficacité remarquable est en tournée partout au Québec avec son spectacle : Moqueur polyglotte. Sur scène, Pierre-Luc s’amuse à rire de lui-même, notamment de son côté lunatique et maladroit, en mettant de l’avant ses histoires et ses anecdotes personnelles tout en défilant les gags à un rythme impressionnant. L’humoriste à la répartie implacable, qui est aussi l’homme-orchestre derrière les hilarantes et populaires capsules sur le thème de Marketplace (à voir absolument!), aborde des sujets rassembleurs dans lesquels le public se reconnaîtra assurément. Alors que ses anecdotes sont appuyées par une gestuelle démonstrative et une rythmique étonnante, Pierre-Luc sera assurément l’un de vos nouveaux coups de cœur!"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-luc-pomerleau-moqueur-polyglotte#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-luc-pomerleau-moqueur-polyglotte#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-luc-pomerleau-moqueur-polyglotte#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-luc-pomerleau-moqueur-polyglotte#Offer_3"}]},{"@id":"http://kg.footlight.io/resource/7d187cec-b726-4f2d-9af4-40a6375a5ba9","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Pierre-Luc Pomerleau"}],"http://schema.org/hasOccupation":[{"@value":"Humoriste"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}],"http://schema.org/url":[{"@value":"https://www.pierrelucpomerleau.com/"}],"http://schema.org/sameAs":[{"@value":"http://www.wikidata.org/entity/Q97366763"}]},{"@id":"https://www.culture3r.com/evenements/pierre-luc-pomerleau-moqueur-polyglotte/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-14T23:13:54.872-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/pierre-luc-pomerleau-moqueur-polyglotte/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-luc-pomerleau-moqueur-polyglotte#Offer_0","http://schema.org/price":[{"@value":"42.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-luc-pomerleau-moqueur-polyglotte#Offer_1","http://schema.org/price":[{"@value":"38.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-luc-pomerleau-moqueur-polyglotte#Offer_2","http://schema.org/price":[{"@value":"36.75"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-luc-pomerleau-moqueur-polyglotte#Offer_3","http://schema.org/price":[{"@value":"35.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_verdic-ii-les-agents-doubles-productions","@type":["http://schema.org/Event"],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/verdic-ii-les-agents-doubles-productions/#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-03T19:00:00-05:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/verdic-ii-les-agents-doubles-productions/"}],"http://schema.org/description":[{"@language":"fr","@value":"J’ai choisi cette pièce, car il s'agit d’une matière théâtrale qui force la réflexion et qui soulève la poussière qui se pose trop souvent sur notre conscience collective. C’est pour moi un moment de théâtre puissant qui met en lumière la complexité des rapports humains et la force du doute face à une vérité trop vite arrêtée. -Lisa Dugré, programmatrice aux arts de la scène. \n Après l’immense succès de Verdict I présenté en 2022, nous sommes ravis de revenir sur scène pour un deuxième volet mettant en vedette Paul Doucet et Sonia Vachon dans de nouvelles plaidoiries abordant divers débats de société. Que l’on pense à la détresse des proches aidants, aux cas d’atteinte à la réputation ou encore à la violence conjugale, la société québécoise a débattu lorsque ces causes ont été entendues en justice. Ce spectacle offre une expérience interactive, enrichie par une mise en contexte multimédia, où le public est invité à se positionner lors du dernier procès en rendant un verdict, comme le feraient les membres d’un jury. Étape ultime d’un procès, les plaidoiries sont imagées, percutantes et touchantes. Un regard poignant et éclairé sur ce qui nous définit comme société. \n Note : Il n’est pas nécessaire d’avoir vu la pièce Verdict I pour assister à la pièce Verdict II. Votre expérience de spectacle sera enrichie d’une médiation pour ouvrir le dialogue sur l’œuvre proposée : une présentation de la pièce en amont ainsi qu'une discussion de 15 minutes après la représentation. Ces moments d'échanges vous inviteront à tisser des liens, partager vos impressions et nourrir une réflexion collective. La culture devient alors un vecteur de rencontres et de changement. Mise en scène : Michel-Maxime Legault Distribution: Paul Doucet et Sonia Vachon Direction artistique : Pierre Bernard Adaptation : Nathalie Roy et Yves Thériault \n Distribution: sujette à changement sans préavis"}],"http://schema.org/name":[{"@language":"fr","@value":"Verdict II\nLes Agents doubles Productions"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/04/Verdict20II_-ON_DIgXB9eftN0Kg0w8_.jpg"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_verdic-ii-les-agents-doubles-productions#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_verdic-ii-les-agents-doubles-productions#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_verdic-ii-les-agents-doubles-productions#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_verdic-ii-les-agents-doubles-productions#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/verdic-ii-les-agents-doubles-productions/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:15:27.416-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/verdic-ii-les-agents-doubles-productions/"}]},{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Théâtre"}],"http://www.w3.org/2004/02/skos/core#closeMatch":[{"@id":"http://kg.artsdata.ca/resource/TheatrePerformance"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_verdic-ii-les-agents-doubles-productions#Offer_0","http://schema.org/price":[{"@value":"56.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_verdic-ii-les-agents-doubles-productions#Offer_1","http://schema.org/price":[{"@value":"51.10"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_verdic-ii-les-agents-doubles-productions#Offer_2","http://schema.org/price":[{"@value":"48.65"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_verdic-ii-les-agents-doubles-productions#Offer_3","http://schema.org/price":[{"@value":"46.20"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_sam-vigneault-rodage","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Sam Vigneault\nRodage"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2024/08/Sam20Vigneault_-O5UPRBI4VnqbZid-P_j_.jpg"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/4f61be27-f0dd-40ba-b6e9-a24a10bdaddf"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/sam-vigneault-rodage/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/sam-vigneault-rodage/"}],"http://schema.org/description":[{"@language":"fr","@value":"Originaire de Plessisville et fils d’une longue lignée d’agriculteurs, Sam Vigneault s’est taillé une place dans le monde de l’humour grâce à son style, son look et évidemment son humour unique et rafraichissant. Celui qu’on surnomme l’humoriste à l’accordéon a un parcours qui a été marqué par de nombreux moments forts, notamment ses prestations acclamées sur les galas ComediHa!, ses performances à la télévision et bien sûr sa grande victoire du Concours Scène ouverte ComediHa! en 2024. Aujourd’hui, Sam est prêt à récolter les fruits de ses efforts et à vous faire vivre un moment mémorable et hilarant avec son tout premier spectacle en rodage! Membres Accès Plus: Veuillez prendre note qu’exceptionnellement seul le rabais de 10 % et 15 % est disponible pour ce spectacle."}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-24T20:00:00-04:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_sam-vigneault-rodage#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_sam-vigneault-rodage#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_sam-vigneault-rodage#Offer_2"}]},{"@id":"http://kg.footlight.io/resource/4f61be27-f0dd-40ba-b6e9-a24a10bdaddf","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Sam Vigneault"}],"http://schema.org/hasOccupation":[{"@language":"fr","@value":"Humoriste"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}]},{"@id":"https://www.culture3r.com/evenements/sam-vigneault-rodage/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:15:18.045-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/sam-vigneault-rodage/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_sam-vigneault-rodage#Offer_0","http://schema.org/price":[{"@value":"30.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_sam-vigneault-rodage#Offer_1","http://schema.org/price":[{"@value":"27.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_sam-vigneault-rodage#Offer_2","http://schema.org/price":[{"@value":"26.25"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_possessions-theatre-bistouri","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Possessions\nThéâtre Bistouri"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/04/Possessions_-ON_ESckEObGsLqLpp7K_.jpg"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/possessions-theatre-bistouri/"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-12T17:30:00-05:00"}],"http://schema.org/description":[{"@language":"fr","@value":"Formule 5 à 7 Une formule 5 à 7 appréciée de tous, cette fois abordant une œuvre qui explore de façon humoristique les frontières entre le réel et le surréel, plongeant les spectateurs dans une rencontre troublante avec l’au-delà. Un moment où le temps s’arrêtera et suscitera des réflexions en vous. -Lisa Dugré, programmatrice aux arts de la scène. « Mon garçon, mon Marc. C’est moi, Maman. Faut absolument que je te parle. » Marc jongle entre plusieurs emplois pour subvenir aux besoins de sa famille. Il est confronté au vertige d’une rencontre avec sa conseillère financière, Madame Hénault. Cependant, la séance vire à l’étrange lorsqu’Hélène, la mère récemment décédée de Marc, tente de communiquer à travers le corps de Madame Hénault. Une lutte s’ensuit entre les deux femmes, révélant des vérités profondes sur la vie. À travers l’humour, la nouvelle création de François Archambault explore le sens de l’héritage, la recherche de soi et la compréhension de la vie au-delà des contraintes matérielles. Votre expérience de spectacle sera enrichie d’une médiation pour ouvrir le dialogue sur l’œuvre proposée : une présentation de la pièce en amont ainsi qu’une discussion de 15 minutes après la représentation. Ces moments d’échanges vous inviteront à tisser des liens, partager vos impressions et nourrir une réflexion collective. La culture devient alors un vecteur de rencontres et de changement. Texte : François ARCHAMBAULT Mise en scène : Catherine VIDAL Interprétation :Marie-Hélène THIBAULT, Marc-André THIBAULT Décor/costumes : Carol-Anne BOURGON-SICARD Éclairages : Natasha DESCÔTEAUX Musique : Francis ROSSIGNOL Distribution: sujette à changement sans préavis"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/possessions-theatre-bistouri/#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_possessions-theatre-bistouri#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_possessions-theatre-bistouri#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_possessions-theatre-bistouri#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_possessions-theatre-bistouri#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/possessions-theatre-bistouri/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:15:37.933-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/possessions-theatre-bistouri/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_possessions-theatre-bistouri#Offer_0","http://schema.org/price":[{"@value":"29.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_possessions-theatre-bistouri#Offer_1","http://schema.org/price":[{"@value":"26.60"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_possessions-theatre-bistouri#Offer_2","http://schema.org/price":[{"@value":"25.40"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_possessions-theatre-bistouri#Offer_3","http://schema.org/price":[{"@value":"24.20"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_ariane-roy-dogue","@type":["http://schema.org/Event"],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-01T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/ariane-roy-dogue/#WebPage"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/ac49dbf2-ebe1-4493-b504-fb520ab0b89c"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Ariane Roy\nDogue"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/02/Ariane20Roy_-OK1n01RNoxcUisTziqo_.jpg"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/ariane-roy-dogue/"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/description":[{"@language":"fr","@value":"Couronnée Révélation de l’année au Gala de l’ADISQ 2022 et nommée dans la catégorie Album francophone de l’année aux Prix JUNO 2023, Ariane Roy s’inscrit dans cette nouvelle vague d’artistes qui redéfinissent la chanson québécoise. Suite à l’éclatante série de concerts Le Roy, La Rose et le Lou[p] qui a brillé partout dans la province dans les deux dernières années ainsi qu’à sa première tournée medium plaisir, elle revient sur scène avec son projet solo en nous présentant son deuxième album Dogue, co-réalisé avec Félix Petit. Avec la fougue qu’on lui connaît, elle se montre à nous cette fois sous des jours plus sombres, le verbe plus mordant, la voix plus caustique, dans une lumière résolument contemporaine. Dogue montre les dents, oui pour sourire, mais aux dépends de qui ? Peu nous importe puisque l’on ne peut résister à se coucher devant les crocs, espérant se joindre à la meute. "}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_ariane-roy-dogue#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_ariane-roy-dogue#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_ariane-roy-dogue#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_ariane-roy-dogue#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/ariane-roy-dogue/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:15:48.906-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/ariane-roy-dogue/"}]},{"@id":"http://kg.footlight.io/resource/ac49dbf2-ebe1-4493-b504-fb520ab0b89c","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Ariane Roy"}],"http://schema.org/hasOccupation":[{"@value":"Chanteuse"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Chanteuse"}],"http://schema.org/sameAs":[{"@value":"http://www.wikidata.org/entity/Q113483345"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_ariane-roy-dogue#Offer_0","http://schema.org/price":[{"@value":"35.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_ariane-roy-dogue#Offer_1","http://schema.org/price":[{"@value":"32.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_ariane-roy-dogue#Offer_2","http://schema.org/price":[{"@value":"30.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_ariane-roy-dogue#Offer_3","http://schema.org/price":[{"@value":"29.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_philippe-laprise-pourquoi-pas","http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-128"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/philippe-laprise-pourquoi-pas/"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/name":[{"@language":"fr","@value":"Philippe Laprise\nAu sommet"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-14T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/philippe-laprise-pourquoi-pas/#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/10/Philippe20Laprise_-Oa_zFsTFR7IJk2omh8L_.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"Cette année est bien remplie pour Philippe Laprise! Il lance son 5e one-man-show en 25 ans de carrière tout en soulignant ses 50 ans! Si on ajoute à ça le fait qu’il est père de 3 enfants et d’un chien, co-propriétaire d’une équipe de baseball, en couple depuis près de 25 ans et qu’il a presque gagné (mais pas tant que ça) Sortez-moi d’ici et LOL : Qui rira le dernier?, Philippe a vraiment dépassé les attentes de ses proches… et les siennes! Dans ce spectacle solo, Philippe se questionne sur ce que c’est réellement être au sommet à notre époque où tout évolue rapidement. Maintenant rendu à un âge « presque » vénérable, Philippe s’assume et n’a plus rien à perdre. Il nous présente donc Au sommet, un spectacle sans filtre mais toujours avec une bonne dose d’autodérision. La route vers le sommet est longue, surtout quand l’intérieur des cuisses frotte ensemble, alors partons tous maintenant et suivons Philippe au sommet! "}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_philippe-laprise-pourquoi-pas#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_philippe-laprise-pourquoi-pas#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_philippe-laprise-pourquoi-pas#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_philippe-laprise-pourquoi-pas#Offer_3"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-15T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culture3r-com_philippe-laprise-pourquoi-pas#2026-08-14T200000-0400"},{"@id":"http://kg.footlight.io/resource/culture3r-com_philippe-laprise-pourquoi-pas#2026-08-15T200000-0400"}]},{"@id":"https://www.culture3r.com/evenements/philippe-laprise-pourquoi-pas/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:15:58.997-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/philippe-laprise-pourquoi-pas/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_philippe-laprise-pourquoi-pas#Offer_0","http://schema.org/price":[{"@value":"45.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_philippe-laprise-pourquoi-pas#Offer_1","http://schema.org/price":[{"@value":"41.20"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_philippe-laprise-pourquoi-pas#Offer_2","http://schema.org/price":[{"@value":"39.30"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_philippe-laprise-pourquoi-pas#Offer_3","http://schema.org/price":[{"@value":"37.40"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_philippe-laprise-pourquoi-pas#2026-08-14T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Philippe Laprise\nAu sommet"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-14T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_philippe-laprise-pourquoi-pas#2026-08-15T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Philippe Laprise\nAu sommet"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-15T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_silence-on-tourne-3","@type":["http://schema.org/Event"],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-07T10:00:00-05:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/1272f78a-b32d-481a-9143-df6737cf0580"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/09/Silence20on20tourne_-OZpHY96dByajxiswJ7e_.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/silence-on-tourne-3/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/7826846d-0f28-44b4-9bc9-8b61b2536b81"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/silence-on-tourne-3/"}],"http://schema.org/name":[{"@language":"fr","@value":"Silence on tourne!"}],"http://schema.org/description":[{"@language":"fr","@value":"7 ans et + | 75 minutes Faire du cinéma d’animation avec une tablette, c’est possible! Découvrir les étapes de création d’un film, de l’idée de départ jusqu’au tournage image par image. Vous imaginerez un scénario, fabriquerez des décors, puis réaliserez votre propre film d’animation en utilisant une tablette électronique et l’application Animation en volume (CATEATER). Cet atelier vous permettra d’explorer plusieurs médiums comme le dessin, la sculpture, la photographie et l’écriture. Tout comme dans le monde du cinéma, cet atelier mise sur le travail d’équipe, la créativité et le plaisir de créer ensemble. Une belle façon de s’initier à l’art de l’image en mouvement ! Une activité offerte par Sporobole. Les enfants doivent être accompagnés d’un adulte pour cette activité. "}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_silence-on-tourne-3#Offer_0"}]},{"@id":"https://www.culture3r.com/evenements/silence-on-tourne-3/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:16:08.687-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/silence-on-tourne-3/"}]},{"@id":"http://kg.footlight.io/resource/7826846d-0f28-44b4-9bc9-8b61b2536b81","@type":["http://schema.org/Place"],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"art_gallery, movie_theater, point_of_interest, establishment at 150 Rue Fusey, Trois-Rivières, QC G8T 2V8, Canada"}],"http://schema.org/address":[{"@id":"http://kg.footlight.io/resource/7826846d-0f28-44b4-9bc9-8b61b2536b81#PostalAddress"}],"http://schema.org/name":[{"@language":"fr","@value":"Espace Pauline-Julien"}],"http://schema.org/longitude":[{"@value":"-72.52469669999999"}],"http://schema.org/latitude":[{"@value":"46.36674379999999"}],"http://schema.org/sameAs":[{"@value":"https://maps.google.com/?cid=17012217844642872834"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_silence-on-tourne-3#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"10.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_pascal-cameron-2","http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/e75531ec-57cd-4370-a5a9-d1574b931b99"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/pascal-cameron-2/"}],"http://schema.org/name":[{"@language":"fr","@value":"Pascal Cameron\nUn spectacle"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/02/Pascal20Cameron_-OKC3w2U5MFqdMtVpqO5_.jpg"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-12T20:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/pascal-cameron-2/#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"Pascal Cameron présente son premier one man show un spectacle. qui est une représentation humoristique qui veut parler à tout le monde et de tout le monde. Par contre, il est important de noter que c'est un spectacle d'humour, donc les propos de cette représentation seraient inappropriés au Parlement ou à des funérailles mais plus que correct dans un pool party, Noël, un accouchement, la St-Jean ou à crier à travers la fenêtre de ton char quand tu es pogné dans le trafic. \n De plus, un spectacle. a comme objectif premier de faire rire. Fack, va pas me citer comme preuve quand tu t'obstines avec du monde «Pascal Cameron a dit ça l'autre jour ». J'ai aucun diplôme sauf celui du meilleur livreur de journaux de la Montérégie 1999. (Après, j'ai lâché ma job pour me retirer au sommet) Un spectacle. c'est un show de stand up traditionnel avec des blagues fraiches. \n Membres Accès Plus: Veuillez prendre note qu'exceptionnellement pour la représentation du 12 février 2026, seuls les rabais de 10 % et 15 % sont disponibles."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_pascal-cameron-2#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-13T20:00:00-05:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culture3r-com_pascal-cameron-2#2026-02-12T200000-0500"},{"@id":"http://kg.footlight.io/resource/culture3r-com_pascal-cameron-2#2026-02-13T200000-0500"}]},{"@id":"http://kg.footlight.io/resource/e75531ec-57cd-4370-a5a9-d1574b931b99","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Pascal Cameron"}],"http://schema.org/hasOccupation":[{"@value":"Humoriste"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}],"http://schema.org/url":[{"@value":"https://pascalcameron.com/"}],"http://schema.org/sameAs":[{"@value":"https://www.facebook.com/pascalcameronhumoriste/"},{"@value":"http://www.wikidata.org/entity/Q111454377"},{"@value":"http://kg.artsdata.ca/resource/K2-2540"}]},{"@id":"https://www.culture3r.com/evenements/pascal-cameron-2/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:16:18.119-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/pascal-cameron-2/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_pascal-cameron-2#Offer_0","http://schema.org/price":[{"@value":"37.00"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culture3r-com_pascal-cameron-2#2026-02-12T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Pascal Cameron\nUn spectacle"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-12T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_pascal-cameron-2#2026-02-13T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Pascal Cameron\nUn spectacle"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-13T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_alain-choquette-la-memoire-du-temps","http://schema.org/description":[{"@language":"fr","@value":"Maître de l’insaisissable et raconteur hors pair, Alain Choquette revient sur scène avec une nouvelle proposition aussi divertissante qu’inspirante, Entre histoires et illusions. Dans ce\nspectacle, l’illusionniste québécois le plus charismatique et expérimenté offre à un public de tous âges de plonger avec lui dans son grand livre de la magie. Cette fois-ci, ce sont ses récits\ncaptivants, allant de l’histoire de la magie (Robert-Houdin, Houdini, Copperfield, Magic Tom…) aux surprenantes expériences vécues au cours de ses 35 années de carrière, qui amèneront le \ncélèbre juge de La magie des stars (TVA) à vous éblouir tant par ses performances insaisissables que par ses connaissances historiques et sa capacité à raconter. Plus de 2 millions de billets vendus en carrière, premier Québécois à présenter un spectacle en résidence à Las Vegas, plusieurs tournées à succès en Europe, création et distribution de grands numéros magiques à l’international : Alain Choquette fait partie des grands noms de la magie à travers le monde et ne manque pas de récits et de numéros pour surprendre, faire rire et éblouir. Ayant une place de choix dans le cœur des Québécois depuis des décennies, Alain veut redonner au public en lui proposant une expérience intime, unique et interactive, qui créera des souvenirs indélébiles. Entre histoires et illusions, un spectacle dans lequel la magie prend tout son sens!"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-259"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/9475525c-10fa-4a53-b89b-8c7d9ba68726"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/alain-choquette-la-memoire-du-temps/#WebPage"}],"http://schema.org/name":[{"@language":"fr","@value":"Alain Choquette\nEntre histoires et illusions"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/03/Alain20Choquette_-OL-Jw2pY6BeMKtOXBq-_.jpg"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/alain-choquette-la-memoire-du-temps/"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_alain-choquette-la-memoire-du-temps#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_alain-choquette-la-memoire-du-temps#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_alain-choquette-la-memoire-du-temps#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_alain-choquette-la-memoire-du-temps#Offer_3"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-17T20:00:00-05:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culture3r-com_alain-choquette-la-memoire-du-temps#2026-01-17T200000-0500"},{"@id":"http://kg.footlight.io/resource/culture3r-com_alain-choquette-la-memoire-du-temps#2026-01-17T150000-0500"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-17T15:00:00-05:00"}]},{"@id":"https://www.culture3r.com/evenements/alain-choquette-la-memoire-du-temps/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-15T23:17:01.849-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/alain-choquette-la-memoire-du-temps/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_alain-choquette-la-memoire-du-temps#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"49.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_alain-choquette-la-memoire-du-temps#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"44.80"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_alain-choquette-la-memoire-du-temps#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"42.70"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_alain-choquette-la-memoire-du-temps#Offer_3","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"40.60"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_alain-choquette-la-memoire-du-temps#2026-01-17T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Alain Choquette\nEntre histoires et illusions"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-17T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_alain-choquette-la-memoire-du-temps#2026-01-17T150000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Alain Choquette\nEntre histoires et illusions"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-17T15:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_martin-vachon-ahh-caramel","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Martin Vachon\nAhh Caramel…"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-17T20:00:00-04:00"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K2-4876"},{"@id":"http://kg.artsdata.ca/resource/K12-250"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/martin-vachon-ahh-caramel/"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/martin-vachon-ahh-caramel/#WebPage"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2023/05/show_-NTnwTwjb8MMqsVdo5iR_.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"Après un passage remarqué à l’émission Big Brother Célébrités et l’animation de la quotidienne Combien tu m’aimes? sur Noovo, l’humoriste Martin Vachon revient sur scène avec son deuxième spectacle Ahh Caramel… Ce one-man-show offre un savoureux mélange d’anecdotes sucrées nappées d’un généreux coulis d’autodérision, alors que Martin s’inspire de sa vie qui est un perpétuel spectacle qui ne demande qu’à se faire raconter. Avec une énergie tout aussi débordante que contagieuse, Martin offre un stand up délicieux avec une haute teneur en rire qui enchantera vos papilles auditives! Le tout nouveau Vachon… vous allez vous régaler! 100 % de plaisir, 0 % de culpabilité! P-S : Même les diabétiques peuvent se le permettre"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_martin-vachon-ahh-caramel#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_martin-vachon-ahh-caramel#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_martin-vachon-ahh-caramel#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_martin-vachon-ahh-caramel#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/martin-vachon-ahh-caramel/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:16:28.097-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/martin-vachon-ahh-caramel/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_martin-vachon-ahh-caramel#Offer_0","http://schema.org/price":[{"@value":"39.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_martin-vachon-ahh-caramel#Offer_1","http://schema.org/price":[{"@value":"35.60"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_martin-vachon-ahh-caramel#Offer_2","http://schema.org/price":[{"@value":"33.90"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_martin-vachon-ahh-caramel#Offer_3","http://schema.org/price":[{"@value":"32.20"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_couple-ouvert-2","@type":["http://schema.org/Event"],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Couple ouvert"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/couple-ouvert-2/"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2027-02-11T20:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/couple-ouvert-2/#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/description":[{"@language":"fr","@value":"Enregistrement devant public du podcast Couple Ouvert, animé par l’humoriste Thomas Levac et sa blonde plus drôle que lui, Stéphanie Vandelac. Couple Ouvert, c’est simple : Thom et Steph reçoivent un humoriste et racontent des histoires qu’ils ne devraient même pas confier à un psy. Le clou du spectacle est sans contredit le segment des dates. C’est une partie du spectacle où le couple et leur invité lisent les pires dates de leurs auditeurs. Ces dates sont anonymes : les auditeurs se laissent aller, alors on lit des histoires bizarres, choquantes, dégoûtantes, touchantes et rarement bien écrites. Si t’aimes l’humour noir, Couple Ouvert Live, c’est parfait pour toi. Si t’aimes l’humour consensuel, reste chez toi, tout le monde va passer une meilleure soirée"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2024/02/Couple20ouvert_-NrG_CWl6DdvuHc590nN_-scaled.jpg"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_couple-ouvert-2#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_couple-ouvert-2#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_couple-ouvert-2#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_couple-ouvert-2#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/couple-ouvert-2/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:16:47.795-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/couple-ouvert-2/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_couple-ouvert-2#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"35.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_couple-ouvert-2#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"32.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_couple-ouvert-2#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"30.50"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_couple-ouvert-2#Offer_3","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"29.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_les-jolies-choses-catherine-gaudet","@type":["http://schema.org/Event"],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-02T20:00:00-04:00"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/2fa05002-967b-43c2-b5cb-fc93a25edcb1"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/les-jolies-choses-catherine-gaudet/"}],"http://schema.org/name":[{"@language":"fr","@value":"Les jolies choses\nCatherine Gaudet"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/04/Les20jolies20choses_-ON_JQ7o0uaHXBeSd8Wo_.jpg"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/les-jolies-choses-catherine-gaudet/#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"J’admets avoir hésité avant de plonger dans cette œuvre, mais une fois immergée, j’ai su que je venais de vivre une expérience rare, essentielle. Les tableaux qui défilent sont hypnotisants et la performance des danseurs est très physique et rigoureuse. Ce spectacle vient réveiller quelque chose d’indomptable en nous, une révolte silencieuse face à l’ordre établi. Pour moi, cette pièce est synonyme d’audace. C’est souvent dans ces expériences déstabilisantes que naissent les plus belles révélations et que se forgent les souvenirs les plus marquants. – Lisa Dugré, programmatrice aux arts de la scène. Cinq corps s’activent au rythme du métronome. Leurs mouvements mécaniques reprennent, la machine s’échauffe et exige d’eux une rigueur irréprochable. Arrivée à maturation de son langage artistique, la chorégraphe Catherine Gaudet part en quête d’un espace sous les corps où les désirs peuvent renaître malgré le poids de la contrainte. Votre expérience de spectacle sera enrichie d’une médiation pour ouvrir le dialogue sur l’œuvre proposée : une présentation de la pièce en amont ainsi qu’une discussion de 15 minutes après la représentation. Ces moments d’échanges vous inviteront à tisser des liens, partager vos impressions et nourrir une réflexion collective. La culture devient alors un vecteur de rencontres et de changement. Chorégraphie : Catherine Gaudet Interprétation : Scott McCabe, Francis Ducharme, Caroline Gravel, Leïla Mailly, James Phillips Musique: Antoine Berthiaume Aide à la dramaturgie et direction des répétitions : Sophie Michaud Éclairages : Hugo Dalpond Costumes : Marilène Bastien Interprète remplaçante lors de la première Lauren Semeschuk Coproduction Festival TransAmériques + Agora de la danse + Centre Chorégraphique National de Caen + Réseau CanDance (Toronto) + Centre national des Arts (Ottawa) + Harbourfront Center (Toronto) + DLD-Daniel Léveillé Danse Résidence de création DLD-Daniel Léveillé Danse + Agora de la danse Développement DLD-Daniel Léveillé Danse + Marie-Andrée Gougeon Distribution: sujette à changement sans préavis"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_les-jolies-choses-catherine-gaudet#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_les-jolies-choses-catherine-gaudet#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_les-jolies-choses-catherine-gaudet#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_les-jolies-choses-catherine-gaudet#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/les-jolies-choses-catherine-gaudet/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:16:56.875-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/les-jolies-choses-catherine-gaudet/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_les-jolies-choses-catherine-gaudet#Offer_0","http://schema.org/price":[{"@value":"42.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_les-jolies-choses-catherine-gaudet#Offer_1","http://schema.org/price":[{"@value":"38.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_les-jolies-choses-catherine-gaudet#Offer_2","http://schema.org/price":[{"@value":"36.75"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_les-jolies-choses-catherine-gaudet#Offer_3","http://schema.org/price":[{"@value":"35.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_eve-cote-cote-eve","@type":["http://schema.org/Event"],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-06T20:00:00-05:00"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/10/C388ve20CC3B4tC3A9_-OcjzvQV2LXXWvPm4dfh_.jpg"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/5d58f420-ddac-4f12-a24c-6e9bbd8c1f32"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/eve-cote-cote-eve/"}],"http://schema.org/name":[{"@language":"fr","@value":"Ève Côté\nCôté Ève"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/eve-cote-cote-eve/#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/description":[{"@language":"fr","@value":"Après un triomphe cumulant plus de 115 000 billets vendus et des centaines de représentations à travers la province avec les Grandes Crues, Ève Côté présente son premier spectacle solo. Découvrez son univers, sa dégaine gaspésienne et son humour imagé « Accompagner Eve dans la préparation de son premier one woman show est une chance inouïe. Elle est une humoriste qui raconte comme pas une l’unicité et la complexité de l’être humain. Après avoir écouté son matériel des dizaines et des dizaines de fois; tout de cette humoriste me fait encore rire aux larmes. Eve a également tous les talents et mon objectif est de vous en faire profiter au maximum. Ce spectacle est à la fois hilarant, tendre, voire touchant. Elle est vraiment dans une catégorie à part. Venez la découvrir! » – Joël Legendre \n \nEn ces temps de réjouissances, d’effritement du tissu social et d’inflation, il me fait plaisir de vous annoncer mon tout premier one woman show: « Côté Eve »… Faut bien rire avant la fin du monde! – Eve Côté"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_eve-cote-cote-eve#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_eve-cote-cote-eve#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_eve-cote-cote-eve#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_eve-cote-cote-eve#Offer_3"}]},{"@id":"http://kg.footlight.io/resource/5d58f420-ddac-4f12-a24c-6e9bbd8c1f32","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Eve Côté"}],"http://schema.org/hasOccupation":[{"@value":"Humoriste"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}],"http://schema.org/url":[{"@value":"https://evecoteofficiel.com/"}],"http://schema.org/alternateName":[{"@language":"fr","@value":"Ève Côté"}]},{"@id":"https://www.culture3r.com/evenements/eve-cote-cote-eve/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:16:37.135-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/eve-cote-cote-eve/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_eve-cote-cote-eve#Offer_0","http://schema.org/price":[{"@value":"47.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_eve-cote-cote-eve#Offer_1","http://schema.org/price":[{"@value":"43.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_eve-cote-cote-eve#Offer_2","http://schema.org/price":[{"@value":"41.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_eve-cote-cote-eve#Offer_3","http://schema.org/price":[{"@value":"39.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mariana-mazza-impolie","http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-382"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/mariana-mazza-impolie/"}],"http://schema.org/name":[{"@language":"fr","@value":"Mariana Mazza\nFoie Gras"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2023/01/2410-mmaz-mev-e-1080x1080-complet.png"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-05T20:00:00-05:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/description":[{"@language":"fr","@value":"16 ans et + Pour son troisième spectacle, Foie Gras, Mariana Mazza décide de s’exprimer avec sa tête et son cœur… pour faire changement. Dans ce one-woman-show bien personnel, la jeune fille qui a grandi à Montréal-Nord, maintenant rendue une femme embourgeoisée qui vit à Saint-Lambert avec ses caniches et ses toiles invendues, se montre aussi brillante qu’hilarante. Étant habituée à dire des choses uniquement pour provoquer un rire, elle souhaite aller plus loin cette fois et faire un coming out au public : elle est émotive et vulnérable. Le chihuahua sur le Red Bull est officiellement remplacé par le tank au cœur de porcelaine. Comme elle le dit si bien : « C’est moi la meilleure tabarnak! » Oui, Mariana a changé. Mais à quel point? "}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/mariana-mazza-impolie/#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_mariana-mazza-impolie#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_mariana-mazza-impolie#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_mariana-mazza-impolie#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_mariana-mazza-impolie#Offer_3"},{"@id":"http://kg.footlight.io/resource/culture3r-com_mariana-mazza-impolie#Offer_4"},{"@id":"http://kg.footlight.io/resource/culture3r-com_mariana-mazza-impolie#Offer_5"},{"@id":"http://kg.footlight.io/resource/culture3r-com_mariana-mazza-impolie#Offer_6"},{"@id":"http://kg.footlight.io/resource/culture3r-com_mariana-mazza-impolie#Offer_7"},{"@id":"http://kg.footlight.io/resource/culture3r-com_mariana-mazza-impolie#Offer_8"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-08T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culture3r-com_mariana-mazza-impolie#2026-02-05T200000-0500"},{"@id":"http://kg.footlight.io/resource/culture3r-com_mariana-mazza-impolie#2026-02-06T200000-0500"},{"@id":"http://kg.footlight.io/resource/culture3r-com_mariana-mazza-impolie#2026-10-08T200000-0400"}]},{"@id":"https://www.culture3r.com/evenements/mariana-mazza-impolie/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:17:06.727-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/mariana-mazza-impolie/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mariana-mazza-impolie#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"52.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mariana-mazza-impolie#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"47.50"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mariana-mazza-impolie#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"45.25"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mariana-mazza-impolie#Offer_3","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"43.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mariana-mazza-impolie#Offer_4","http://schema.org/price":[{"@value":"38.25"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mariana-mazza-impolie#Offer_5","http://schema.org/price":[{"@value":"53.00"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mariana-mazza-impolie#Offer_6","http://schema.org/price":[{"@value":"48.40"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mariana-mazza-impolie#Offer_7","http://schema.org/price":[{"@value":"46.10"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mariana-mazza-impolie#Offer_8","http://schema.org/price":[{"@value":"43.80"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mariana-mazza-impolie#2026-02-05T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Mariana Mazza\nFoie Gras"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-05T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mariana-mazza-impolie#2026-02-06T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Mariana Mazza\nFoie Gras"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-06T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mariana-mazza-impolie#2026-10-08T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Mariana Mazza\nFoie Gras"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-08T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_ludovick-bourgeois","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/ludovick-bourgeois/"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/11/Ludovick20Bourgeois_-Of4rZm7AloqdtXY77ky_.jpg"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/ludovick-bourgeois/#WebPage"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-277"}],"http://schema.org/name":[{"@language":"fr","@value":"Ludovick Bourgeois\nKaraoké 90"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-19T20:00:00-04:00"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/description":[{"@language":"fr","@value":"Soutenu par un univers visuel éclaté, Karaoké 90 se déploie en trois tableaux : les hits québécois, ceux des BB et les mégatubes mondiaux. De Nirvana à Céline Dion, en passant par U2, Bon Jovi et Mario Pelchat, Ludovick Bourgeois nous entraîne dans une véritable célébration musicale des années 90. Un moment de nostalgie et de plaisir partagé, où chaque soir un spectateur montera sur scène pour chanter aux côtés de l’artiste dans une ambiance survoltée ! "}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_ludovick-bourgeois#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_ludovick-bourgeois#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_ludovick-bourgeois#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_ludovick-bourgeois#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/ludovick-bourgeois/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:17:16.889-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/ludovick-bourgeois/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_ludovick-bourgeois#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"52.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_ludovick-bourgeois#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"47.50"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_ludovick-bourgeois#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"45.25"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_ludovick-bourgeois#Offer_3","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"43.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_pascal-cameron-un-spectacle","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/e75531ec-57cd-4370-a5a9-d1574b931b99"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/10/Pascal20Cameron_-OcG_FNvqMR1bx7lbXMN_.jpg"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/pascal-cameron-un-spectacle/"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/pascal-cameron-un-spectacle/#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-21T20:00:00-05:00"}],"http://schema.org/description":[{"@language":"fr","@value":"Pascal Cameron présente son premier one man show un spectacle. qui est une représentation humoristique qui veut parler à tout le monde et de tout le monde. Par contre, il est important de noter que c’est un spectacle d’humour, donc les propos de cette représentation seraient inappropriés au Parlement ou à des funérailles mais plus que correct dans un pool party, Noël, un accouchement, la St-Jean ou à crier à travers la fenêtre de ton char quand tu es pogné dans le trafic. De plus, un spectacle. a comme objectif premier de faire rire. Fack, va pas me citer comme preuve quand tu t’obstines avec du monde «Pascal Cameron a dit ça l’autre jour ». J’ai aucun diplôme sauf celui du meilleur livreur de journaux de la Montérégie 1999. (Après, j’ai lâché ma job pour me retirer au sommet) Un spectacle. c’est un show de stand up traditionnel avec des blagues fraiches. "}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/name":[{"@language":"fr","@value":"Pascal Cameron\nUn spectacle"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_pascal-cameron-un-spectacle#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_pascal-cameron-un-spectacle#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_pascal-cameron-un-spectacle#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_pascal-cameron-un-spectacle#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/pascal-cameron-un-spectacle/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:17:26.755-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/pascal-cameron-un-spectacle/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_pascal-cameron-un-spectacle#Offer_0","http://schema.org/price":[{"@value":"39.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_pascal-cameron-un-spectacle#Offer_1","http://schema.org/price":[{"@value":"35.60"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_pascal-cameron-un-spectacle#Offer_2","http://schema.org/price":[{"@value":"33.90"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_pascal-cameron-un-spectacle#Offer_3","http://schema.org/price":[{"@value":"32.20"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_alexandre-forest-rodage-2","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Alexandre Forest\nRodage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/alexandre-forest-rodage-2/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/alexandre-forest-rodage-2/"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/09/Alexandre20Forest_-O_SInihegulvTrKIBr2_.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"Alexandre Forest entame le rodage de son premier one man show. C’est l’occasion parfaite de le découvrir et de vous laisser emporter dans son univers. Grâce à un alliage subtil entre autodérision et finesse d’esprit, l’humoriste réussit à capturer avec brio l’essence des petites et grandes incongruences de la vie quotidienne. "}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-07-16T20:00:00-04:00"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_alexandre-forest-rodage-2#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_alexandre-forest-rodage-2#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_alexandre-forest-rodage-2#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_alexandre-forest-rodage-2#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/alexandre-forest-rodage-2/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:17:35.561-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/alexandre-forest-rodage-2/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_alexandre-forest-rodage-2#Offer_0","http://schema.org/price":[{"@value":"30.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_alexandre-forest-rodage-2#Offer_1","http://schema.org/price":[{"@value":"27.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_alexandre-forest-rodage-2#Offer_2","http://schema.org/price":[{"@value":"26.25"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_alexandre-forest-rodage-2#Offer_3","http://schema.org/price":[{"@value":"24.99"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mehdi-bousaidan-mouton","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-272"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/mehdi-bousaidan-mouton/#WebPage"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/04/Mehdi20Bousaidan_-OMwItZbOhR6z3euYVWJ_.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-20T20:00:00-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/mehdi-bousaidan-mouton/"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/name":[{"@language":"fr","@value":"Mehdi Bousaidan\nLe garçon et la fenêtre"}],"http://schema.org/description":[{"@language":"fr","@value":"Dans son tout nouveau spectacle, Mehdi Bousaidan se dévoile comme jamais auparavant. Avec ce troisième one-man show, il choisit de faire une introspection sur sa vie et nous en partage une analyse intime avec l’humour efficace qu’on lui connaît. Ce spectacle est une invitation à jeter un œil à travers la fenêtre de sa vie. C’est un voyage où l’on avance de sa tendre enfance à l’homme qu’il est aujourd’hui en se rapprochant un peu plus de sa réalité. À travers des anecdotes surprenantes, il brouille la frontière entre l’histoire personnelle et l’universel. Il aborde aussi des questions existentielles, comme le mariage, les relations familiales, les peurs et les craintes ainsi que les expériences qui forgent l’âme. C’est un Mehdi qu’on reconnaît, certes, de son sens critique aiguisé, de ses analyses inhabituelles et de ses punchs inattendus, mais c’est aussi un Mehdi qu’on apprend à connaître sous un nouveau visage. Il nous ouvre le livre de sa vie privée pour une toute première fois dans un humour toujours aussi rafraîchissant."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_mehdi-bousaidan-mouton#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_mehdi-bousaidan-mouton#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_mehdi-bousaidan-mouton#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_mehdi-bousaidan-mouton#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/mehdi-bousaidan-mouton/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:17:46.346-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/mehdi-bousaidan-mouton/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mehdi-bousaidan-mouton#Offer_0","http://schema.org/price":[{"@value":"43.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mehdi-bousaidan-mouton#Offer_1","http://schema.org/price":[{"@value":"39.40"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mehdi-bousaidan-mouton#Offer_2","http://schema.org/price":[{"@value":"37.60"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mehdi-bousaidan-mouton#Offer_3","http://schema.org/price":[{"@value":"35.80"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_seminaire-st-joseph","http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/seminaire-st-joseph/"}],"http://schema.org/name":[{"@language":"fr","@value":"Séminaire St-Joseph\nGrease"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2026/01/SC3A9minaire20St-Joseph_-Ok9MtIfYpyQU4EF8xX5_.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"Fiers d’une tradition annuelle qui remonte à 1980, les élèves du Séminaire Saint-Joseph vous présente la comédie musicale Grease. Venez redécouvrir l’univers des adolescents de la fin des années 1950. Des chansons bien rythmées, des danses endiablées, des dialogues où se mêlent humour et tendresse vous seront offerts par plus de 80 artistes passionnés. Membres Accès Plus: Veuillez prendre note qu’exceptionnellement les rabais progressifs ne sont pas applicables pour cette représentation. "}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-09T19:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/seminaire-st-joseph/#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_seminaire-st-joseph#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-11T19:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culture3r-com_seminaire-st-joseph#2026-04-09T190000-0400"},{"@id":"http://kg.footlight.io/resource/culture3r-com_seminaire-st-joseph#2026-04-10T190000-0400"},{"@id":"http://kg.footlight.io/resource/culture3r-com_seminaire-st-joseph#2026-04-11T140000-0400"},{"@id":"http://kg.footlight.io/resource/culture3r-com_seminaire-st-joseph#2026-04-11T190000-0400"}]},{"@id":"https://www.culture3r.com/evenements/seminaire-st-joseph/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:17:49.449-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/seminaire-st-joseph/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_seminaire-st-joseph#Offer_0","http://schema.org/price":[{"@value":"25.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_seminaire-st-joseph#2026-04-09T190000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Séminaire St-Joseph\nGrease"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-09T19:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_seminaire-st-joseph#2026-04-10T190000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Séminaire St-Joseph\nGrease"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-10T19:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_seminaire-st-joseph#2026-04-11T140000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Séminaire St-Joseph\nGrease"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-11T14:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_seminaire-st-joseph#2026-04-11T190000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Séminaire St-Joseph\nGrease"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-11T19:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_louis-t-male-alpha","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/f89e2c38-48f0-4129-bde3-df4bd123cc0b"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/03/Louis20T_-OKaTI9bPv5IDM5E3BNS_.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/louis-t-male-alpha/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/louis-t-male-alpha/"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-19T20:00:00-04:00"}],"http://schema.org/name":[{"@language":"fr","@value":"Louis T\nMâle Alpha"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/description":[{"@language":"fr","@value":"Louis T revient avec un 3e spectacle, à la fois plus mature et tannant. Avec humour, le nouveau quarantenaire nous partage ses réflexions sur la masculinité; ces hommes qui deviennent père à 80 ans, l’insécurité dans le lit, les séparations difficiles ou encore la place qu’occupe les minorités dans l’espace public. Avec un regard bienveillant, quoi que critique, sur la société et les hommes. Louis T arrive à la conclusion que malgré bien des défauts, les hommes modernes ne sont vraiment pas si pires que ça, quoi que… Un spectacle drôle, efficace et brillant!"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_louis-t-male-alpha#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_louis-t-male-alpha#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_louis-t-male-alpha#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_louis-t-male-alpha#Offer_3"}]},{"@id":"http://kg.footlight.io/resource/f89e2c38-48f0-4129-bde3-df4bd123cc0b","@type":["http://schema.org/Person"],"http://schema.org/alternateName":[{"@language":"fr","@value":"Louis T."}],"http://schema.org/sameAs":[{"@value":"http://www.wikidata.org/entity/Q16661350"},{"@value":"https://www.facebook.com/LouisTfanpage"}],"http://schema.org/name":[{"@language":"fr","@value":"Louis T"}],"http://schema.org/hasOccupation":[{"@language":"fr","@value":"Humoriste"}],"http://schema.org/url":[{"@value":"https://louist.ca/"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}]},{"@id":"https://www.culture3r.com/evenements/louis-t-male-alpha/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:17:58.240-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/louis-t-male-alpha/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_louis-t-male-alpha#Offer_0","http://schema.org/price":[{"@value":"42.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_louis-t-male-alpha#Offer_1","http://schema.org/price":[{"@value":"38.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_louis-t-male-alpha#Offer_2","http://schema.org/price":[{"@value":"36.75"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_louis-t-male-alpha#Offer_3","http://schema.org/price":[{"@value":"35.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_francis-legendre-rodage-pousser-drette-2","http://schema.org/description":[{"@language":"fr","@value":"ENFIN ! Je suis tellement fier de vous présenter mon premier spectacle d’humour : Pousser drette. Pousser drette, c’est deux ans de travail acharné, à fignoler des jokes, à bâtir des histoires, à me raconter sur scène à grands coups d’essais pis d’erreurs. À bizouner le rire dans les recoins du Québec, beau temps, mauvais temps, le vent dans le dos pis le soleil dans les yeux. Pousser drette, c’est doux, honnête et (idéalement) drôle. Je suis fier de moi, pis ça me ferait plaisir de partager cette fierté-là avec vous autres. Je sais pas grand-chose dans vie, mais je sais une chose : si j’ai poussé drette, c’est grâce au rire pis à l’amour. Si toi aussi t’as le goût de pousser drette l’espace d’une soirée, viens voir mon spectacle. On va rire, on va se patenter du bonheur, pis qui sait… on va peut-être même se dire collectivement qu’on s’aime un peu."}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/francis-legendre-rodage-pousser-drette-2/#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/name":[{"@language":"fr","@value":"Francis Legendre\nRodage – Pousser drette"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/11/Francis20Legendre_-Of4rEj98JmjIWE3ZijK_.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-28T20:00:00-04:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/francis-legendre-rodage-pousser-drette-2/"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_francis-legendre-rodage-pousser-drette-2#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_francis-legendre-rodage-pousser-drette-2#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_francis-legendre-rodage-pousser-drette-2#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_francis-legendre-rodage-pousser-drette-2#Offer_3"},{"@id":"http://kg.footlight.io/resource/culture3r-com_francis-legendre-rodage-pousser-drette-2#Offer_4"},{"@id":"http://kg.footlight.io/resource/culture3r-com_francis-legendre-rodage-pousser-drette-2#Offer_5"},{"@id":"http://kg.footlight.io/resource/culture3r-com_francis-legendre-rodage-pousser-drette-2#Offer_6"},{"@id":"http://kg.footlight.io/resource/culture3r-com_francis-legendre-rodage-pousser-drette-2#Offer_7"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2027-04-09T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culture3r-com_francis-legendre-rodage-pousser-drette-2#2026-05-28T200000-0400"},{"@id":"http://kg.footlight.io/resource/culture3r-com_francis-legendre-rodage-pousser-drette-2#2027-04-09T200000-0400"}]},{"@id":"https://www.culture3r.com/evenements/francis-legendre-rodage-pousser-drette-2/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:18:10.987-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/francis-legendre-rodage-pousser-drette-2/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_francis-legendre-rodage-pousser-drette-2#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"30.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_francis-legendre-rodage-pousser-drette-2#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"27.50"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_francis-legendre-rodage-pousser-drette-2#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"26.25"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_francis-legendre-rodage-pousser-drette-2#Offer_3","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"25.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_francis-legendre-rodage-pousser-drette-2#Offer_4","http://schema.org/price":[{"@value":"35.00"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culture3r-com_francis-legendre-rodage-pousser-drette-2#Offer_5","http://schema.org/price":[{"@value":"32.00"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culture3r-com_francis-legendre-rodage-pousser-drette-2#Offer_6","http://schema.org/price":[{"@value":"30.50"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culture3r-com_francis-legendre-rodage-pousser-drette-2#Offer_7","http://schema.org/price":[{"@value":"29.00"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culture3r-com_francis-legendre-rodage-pousser-drette-2#2026-05-28T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Francis Legendre\nRodage – Pousser drette"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-28T20:00:00-04:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_francis-legendre-rodage-pousser-drette-2#2027-04-09T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Francis Legendre\nRodage – Pousser drette"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2027-04-09T20:00:00-04:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_recits-de-berges-une-marche-a-travers-le-temps-visite-guidee","@type":["http://schema.org/Event"],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-02-14"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/recits-de-berges-une-marche-a-travers-le-temps-visite-guidee/"}],"http://schema.org/name":[{"@language":"fr","@value":"Tour guidé extérieur"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/08/recits-de-berges-une-marche-a-travers-le-temps-visite-guidee.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"Avec Récits de berges, découvrez l’histoire des pourtours de la rivière Saint-Maurice, des années 60 à aujourd’hui, en marchant dans les sentiers de l’Île Saint-Quentin. Avec humour et entrain, votre guide Jean-Philippe Marcotte vous racontera l’évolution de ces berges à l’aide de photos d’archives et d’anecdotes. Pour les amateur·trice·s de grand air et d’histoire, c’est à ne pas manquer! Vous avez aimé la visite guidée Le Tour de l’Île? Récits de berges se veut une continuité de cette activité. IMPORTANT Point de rencontre: Rendez-vous au Pavillon de la Rosalie (vestiaire pour patineurs), Île Saint-Quentin. Visite extérieure, habillez-vous selon la température. Acheter un billet "}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/recits-de-berges-une-marche-a-travers-le-temps-visite-guidee/#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/c8cac7e7-aced-4da1-85f1-660a037d68cf"}]},{"@id":"https://www.culture3r.com/evenements/recits-de-berges-une-marche-a-travers-le-temps-visite-guidee/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:18:06.852-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/recits-de-berges-une-marche-a-travers-le-temps-visite-guidee/"}]},{"@id":"http://kg.footlight.io/resource/c8cac7e7-aced-4da1-85f1-660a037d68cf","@type":["http://schema.org/Place"],"http://schema.org/name":[{"@language":"fr","@value":"Île St-Quentin"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"natural_feature, establishment at Île Saint-Quentin, Trois-Rivières, QC G9A, Canada"}],"http://schema.org/address":[{"@id":"http://kg.footlight.io/resource/c8cac7e7-aced-4da1-85f1-660a037d68cf#PostalAddress"}],"http://schema.org/longitude":[{"@value":"-72.52622219999999"}],"http://schema.org/latitude":[{"@value":"46.3528259"}],"http://schema.org/sameAs":[{"@value":"https://maps.google.com/?q=%C3%8Ele+Saint-Quentin\u0026ftid=0x4cc7c617d4e41689:0x9601998b5f10b2c"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_martin-perizzolo","@type":["http://schema.org/Event"],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/martin-perizzolo/#WebPage"}],"http://schema.org/name":[{"@language":"fr","@value":"Martin Perizzolo\nPerizzolo le dramatiste"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/c12aadad-71e4-47a6-8517-50cb288ba1e6"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/description":[{"@language":"fr","@value":"Dramatiste, parce que la vie de Martin Perizzolo est un drame et à chaque fois qu’il en raconte des bouts à ses amis, ils ont tous la même réponse : « C’est vraiment drôle, mets ça dans ton show! » « Comedy is tragedy plus time. » L’idée que l’humour est le résultat de la tragédie et du temps circule depuis bien longtemps. Et c’est exactement là où Perizzolo tire son drôle : dans les petits drames de la vie et le gros drama de l’existence. Dans ce spectacle, qui a comme thème inconscient celui des limites ; celles qu’on s’inflige, celles que nos proches bafouent, celles qu’on n’oserait franchir, celles que l’humoriste tente de cerner, sur scène, en abordant des sujets matures par des angles comiques intelligents et surprenants. Cet homme de presque cinquante ans a terminé son adolescence. Ce stand-up décomplexé a complété ses dix milles heures de vols. Et Dramatiste, c’est la sorte d’humoriste qu’il est. Membres Accès Plus: Veuillez prendre note qu’exceptionnellement les rabais progressifs de 10% et 15% sont applicables pour cette représentation. "}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/martin-perizzolo/"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2024/08/Martin20Perizzolo_-O5DmyvlPyqvrcF0HfUq_.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-17T20:00:00-04:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_martin-perizzolo#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_martin-perizzolo#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_martin-perizzolo#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_martin-perizzolo#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/martin-perizzolo/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:18:30.407-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/martin-perizzolo/"}]},{"@id":"http://kg.footlight.io/resource/c12aadad-71e4-47a6-8517-50cb288ba1e6","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Martin Perizzolo"}],"http://schema.org/hasOccupation":[{"@language":"fr","@value":"Humouriste"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humouriste"}],"http://schema.org/url":[{"@value":"https://martinperizzolo.com/"}],"http://schema.org/sameAs":[{"@value":"http://www.wikidata.org/entity/Q3295588"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_martin-perizzolo#Offer_0","http://schema.org/price":[{"@value":"37.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_martin-perizzolo#Offer_1","http://schema.org/price":[{"@value":"33.80"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_martin-perizzolo#Offer_2","http://schema.org/price":[{"@value":"32.20"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_martin-perizzolo#Offer_3","http://schema.org/price":[{"@value":"30.60"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_maude-landry-2","@type":["http://schema.org/Event"],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/maude-landry-2/"}],"http://schema.org/name":[{"@language":"fr","@value":"Maude Landry\nTrop cool"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/09/Maude20Landry_-Oa0FRoKIjUbYBp_LoT9_.jpg"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-493"}],"http://schema.org/description":[{"@language":"fr","@value":"Dans un monde où il faut se démarquer tout en suivant les tendances, Maude Landry se demande pourquoi elle n’a jamais fait partie de la gang des cools. C’est ce qui la rend attachante. Décontractée et pince-sans-rire, elle pose son regard taquin sur elle-même et sur la société, en passant délibérément par quatre chemins. Un spectacle moderne et assumé, pendant lequel on se dit parfois « C’est niaiseux!… Mais fallait y penser ». Maude vous invite dans un périple au cœur de ses réflexions, ses doutes et ses déboires, pour que vous sortiez de la salle en disant « C’était trop cool! » "}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-15T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/maude-landry-2/#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_maude-landry-2#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_maude-landry-2#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_maude-landry-2#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_maude-landry-2#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/maude-landry-2/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:18:20.727-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/maude-landry-2/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_maude-landry-2#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"43.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_maude-landry-2#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"39.40"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_maude-landry-2#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"37.60"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_maude-landry-2#Offer_3","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"35.80"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_yoga-a-borealis-2026-activite-bien-etre","@type":["http://schema.org/Event"],"http://schema.org/description":[{"@language":"fr","@value":"Après le succès de cet automne, une nouvelle session de yoga vous est offerte au cœur de l’exposition de Boréalis. Besoin de vous ressourcer après le joyeux chaos des fêtes? Boréalis vous invite à une session de 8 semaines de yoga sous le thème des éléments de la nature. Dans le décor enchanteur de l’exposition, sous des éclairages apaisants, profitez de tous les bienfaits du viniyoga, un yoga dont la pratique s’adapte aux besoins individuels de chacun. Des séances soigneusement conçues par Jasmine Marin, professeure certifiée, pour évacuer le stress, acquérir du tonus et améliorer votre souplesse. Offrez-vous un moment hors du temps à Boréalis ! À propos de Jasmine Marin Jasmine détient un certificat de 850 heures en tant que professeure et formatrice en viniyoga. Elle est la fondatrice de Jasmine Yoga et développe des programmes de yoga spécifiques pour les enfants du primaire, les athlètes de programmes sportifs du secondaire et les adultes de tous les niveaux. Informations complémentaires Aucune expérience requise. Postures adaptées aux capacités des participant.es de tous les niveaux. Très petit groupe. Tapis de yoga disponibles sur place, au besoin. Acheter un billet "}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-694"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-02-03"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/yoga-a-borealis-2026-activite-bien-etre/#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Activité bien-être"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/08/2025-bor-yoga.png"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/yoga-a-borealis-2026-activite-bien-etre/"}]},{"@id":"https://www.culture3r.com/evenements/yoga-a-borealis-2026-activite-bien-etre/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-31T23:16:44.951-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/yoga-a-borealis-2026-activite-bien-etre/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_ivan-boivin-flamand","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Ivan Boivin-Flamand"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/fb177572-e23b-4069-8025-dad2f951ba77"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/05/Ivan20Boivin-Flamand_-OQKJnIrTWpQLlMzKZQT_.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-13T20:00:00-05:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/description":[{"@language":"fr","@value":"Ivan Boivin Flamand est un auteur-compositeur interprète et guitariste virtuose originaire de la Première Nation atikamekw de Manawan. Dans les dernières années, il s’est démarqué par sa présence sur scène flamboyante aux côtés de Florent Vollant, Scott-Pien Picard, Maten et PAKO, ou encore Samian au Gala ADISQ 2022 ainsi que dans l’orchestre maison de l’ADISQ au Gala 2023. Son énergie contagieuse sur scène et ses solos de guitares épiques sont acclamées par le public. Il est considéré comme une étoile montante et un modèle pour les jeunes des communautés autochtones. Ivan Boivin-Flamand lance maintenant son projet solo avec un premier EP Indie-Pop Rock en anglais, paru à l’automne 2024. Un nouvel EP en langue atikamekw est également prévu en mars 2026."}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/ivan-boivin-flamand/#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/ivan-boivin-flamand/"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_ivan-boivin-flamand#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_ivan-boivin-flamand#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_ivan-boivin-flamand#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_ivan-boivin-flamand#Offer_3"}]},{"@id":"http://kg.footlight.io/resource/fb177572-e23b-4069-8025-dad2f951ba77","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Ivan Boivin-Flamand"}],"http://schema.org/hasOccupation":[{"@language":"fr","@value":"Auteur-compositeur-interprète"}],"http://schema.org/url":[{"@value":"https://etiennefletcher.ca/"}],"http://schema.org/sameAs":[{"@value":"http://kg.artsdata.ca/resource/K2-5759"},{"@value":"http://www.wikidata.org/entity/Q97272097"},{"@value":"https://www.facebook.com/etiennefletchermusic"}],"http://schema.org/disambiguatingDescription":[{"@language":"fr","@value":"Auteur-compositeur-interprète"}]},{"@id":"https://www.culture3r.com/evenements/ivan-boivin-flamand/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:18:39.365-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/ivan-boivin-flamand/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_ivan-boivin-flamand#Offer_0","http://schema.org/price":[{"@value":"30.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_ivan-boivin-flamand#Offer_1","http://schema.org/price":[{"@value":"27.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_ivan-boivin-flamand#Offer_2","http://schema.org/price":[{"@value":"26.25"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_ivan-boivin-flamand#Offer_3","http://schema.org/price":[{"@value":"25.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_gabrielle-caron","@type":["http://schema.org/Event"],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/gabrielle-caron/#WebPage"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/11/Gabrielle20Caron_-OdKI7_QR3KztlPjAVjJ_.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-03T20:00:00-04:00"}],"http://schema.org/name":[{"@language":"fr","@value":"Gabrielle Caron\nJ’ai fait deux humains – Rodage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/gabrielle-caron/"}],"http://schema.org/description":[{"@language":"fr","@value":"Gabrielle Caron est à la barre de ce spectacle d’humour à saveur parentale. Inspiré par le balado qui parle d’accouchement, ici on plonge tête première dans la parentalité. Sans censure, sans tabou et sans gêne. Franche et décomplexée, avec mille anecdotes en poche, Gabrielle adore sa vie de famille. Mais surtout elle sait en rire! Trouvez-vous une gardienne et venez passer une soirée mémorable! (Mais promis, ça ne finira pas trop tard, on doit se lever le lendemain matin pour aller au cours de piscine.) Membres Accès Plus: Veuillez prendre note qu’exceptionnellement seuls les rabais de 10 % et 15 % est disponible pour ce spectacle."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_gabrielle-caron#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_gabrielle-caron#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_gabrielle-caron#Offer_2"}]},{"@id":"https://www.culture3r.com/evenements/gabrielle-caron/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:18:48.812-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/gabrielle-caron/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_gabrielle-caron#Offer_0","http://schema.org/price":[{"@value":"35.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_gabrielle-caron#Offer_1","http://schema.org/price":[{"@value":"32.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_gabrielle-caron#Offer_2","http://schema.org/price":[{"@value":"30.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_trip-de-francois-bellefeuille","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-285"}],"http://schema.org/name":[{"@language":"fr","@value":"TRIP DE FRANÇOIS BELLEFEUILLE"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/12/tripfb26-post.png"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-06T20:30:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/trip-de-francois-bellefeuille/#WebPage"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/trip-de-francois-bellefeuille/"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2cbd428a-4e42-4276-bfe1-de1d14d1ea98"}],"http://schema.org/description":[{"@language":"fr","@value":"FRANÇOIS BELLEFEUILLE débarque à TRIP pour une soirée où les rires ne prendront jamais de pause. À ses côtés pour TRIPPER solide : Louis T et son humour mordant et intelligent, Katherine Levac avec son style franc et hilarant, Marthe Laverdière et son franc-parler irrésistible, ainsi que Mibenson Sylvain, dont le charisme et l’humour bien senti complètent cette brochette explosive. TRIP de François : une soirée, cinq humoristes, et une seule mission… vous faire rire jusqu’à en avoir mal aux joues! BILLETS "}]},{"@id":"https://www.culture3r.com/evenements/trip-de-francois-bellefeuille/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:18:58.131-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/trip-de-francois-bellefeuille/"}]},{"@id":"http://kg.footlight.io/resource/2cbd428a-4e42-4276-bfe1-de1d14d1ea98","@type":["http://schema.org/Place"],"http://schema.org/name":[{"@language":"fr","@value":"Amphithéâtre Cogeco"}],"http://schema.org/address":[{"@id":"http://kg.footlight.io/resource/2cbd428a-4e42-4276-bfe1-de1d14d1ea98#PostalAddress"}],"http://schema.org/disambiguatingDescription":[{"@language":"fr","@value":"L'Amphithéâtre Cogeco est un site de spectacles majestueux situé à Trois-Rivières"}],"http://schema.org/latitude":[{"@value":"46.3470132"}],"http://schema.org/longitude":[{"@value":"-72.5322324"}],"http://schema.org/sameAs":[{"@value":"http://www.wikidata.org/entity/Q18533570"},{"@value":"http://kg.artsdata.ca/resource/K5-463"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_gabrielle-caron-rodage","@type":["http://schema.org/Event"],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/gabrielle-caron-rodage/#WebPage"}],"http://schema.org/name":[{"@language":"fr","@value":"Gabrielle Caron\nJ’ai fait deux humains – Rodage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-03T20:00:00-04:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/gabrielle-caron-rodage/"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/11/Gabrielle20Caron_-OdKI7_QR3KztlPjAVjJ_.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"Gabrielle Caron est à la barre de ce spectacle d’humour à saveur parentale. Inspiré par le balado qui parle d’accouchement, ici on plonge tête première dans la parentalité. Sans censure, sans tabou et sans gêne. Franche et décomplexée, avec mille anecdotes en poche, Gabrielle adore sa vie de famille. Mais surtout elle sait en rire! Trouvez-vous une gardienne et venez passer une soirée mémorable! (Mais promis, ça ne finira pas trop tard, on doit se lever le lendemain matin pour aller au cours de piscine.) Membres Accès Plus: Veuillez prendre note qu’exceptionnellement seuls les rabais de 10 % et 15 % est disponible pour ce spectacle."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_gabrielle-caron-rodage#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_gabrielle-caron-rodage#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_gabrielle-caron-rodage#Offer_2"}]},{"@id":"https://www.culture3r.com/evenements/gabrielle-caron-rodage/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:19:01.513-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/gabrielle-caron-rodage/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_gabrielle-caron-rodage#Offer_0","http://schema.org/price":[{"@value":"35.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_gabrielle-caron-rodage#Offer_1","http://schema.org/price":[{"@value":"32.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_gabrielle-caron-rodage#Offer_2","http://schema.org/price":[{"@value":"30.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_regals-2","@type":["http://schema.org/Event"],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/regals-2/"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/regals-2/#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/4ccaf1c3-26ff-41d6-84e5-b421483eeae3"}],"http://schema.org/name":[{"@language":"fr","@value":"RÉGALS"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-03-14"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-06-14"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/08/2025-av-cerl-expo-guylaine-guay-detail.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"RÉGALS, c’est une exposition signée Guylaine Guay où formes, couleurs, textures et mots se rencontrent dans un festin visuel vibrant. Ce parcours sensoriel et émouvant s’ancre dans l’amour, l’ouverture à l’autre et l’accueil bienveillant. Quelques paillettes, semblables à des poussières de tendresse, viennent ponctuer cette aventure artistique. Cette exposition s’inscrit dans l’univers singulier de Guylaine Guay, où la créativité dialogue avec l’humour et l’humanité. À travers ses oeuvres, l’artiste partage son cheminement personnel, ses inspirations et les fondements de sa démarche, résolument tournée vers l’inclusion, l’acceptation des différences et une joie assumée. Artiste : Guylaine Guay L’artiste remercie L’Ébauchoir-Espace Céramique et Encore Éco. Un festin visuel où formes, couleurs et mots se mêlent pour célébrer l’amour, l’inclusion et la bienveillance, saupoudrés de tendresse et d’humour. Visites guidées Découvrez l’exposition dans une toute nouvelle perspective en réservant une de nos visites guidées. Accompagné·e d’un·e médiateur·trice, approfondissez vos connaissances de l’artiste, de sa démarche et de ses techniques. Une expérience personnalisée et participative qui vous permettra d’explorer les œuvres avec émotion et profondeur. Réserver une visite guidée Voir l'activité liée: Rencontre d'artiste Guylaine Guay "}]},{"@id":"https://www.culture3r.com/evenements/regals-2/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:19:04.410-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/regals-2/"}]},{"@id":"http://kg.footlight.io/resource/4ccaf1c3-26ff-41d6-84e5-b421483eeae3","@type":["http://schema.org/Place"],"http://schema.org/name":[{"@language":"fr","@value":"Centre d'exposition Raymond-Lasnier"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"tourist_attraction, art_gallery, museum, point_of_interest, establishment at 1425 Pl. de l'Hôtel de Ville 3e Étage, Trois-Rivières, QC G9A 5H3, Canada"}],"http://schema.org/address":[{"@id":"http://kg.footlight.io/resource/4ccaf1c3-26ff-41d6-84e5-b421483eeae3#PostalAddress"}],"http://schema.org/longitude":[{"@value":"-72.5423793"}],"http://schema.org/latitude":[{"@value":"46.34285000000001"}],"http://schema.org/sameAs":[{"@value":"https://maps.google.com/?cid=2346479477994560617"}],"http://schema.org/containedInPlace":[{"@id":"http://kg.artsdata.ca/resource/K5-703"}],"http://schema.org/alternateName":[{"@language":"fr","@value":"Centre d'expo Raymond-Lasnier"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_anemone-47-nebulae","@type":["http://schema.org/Event"],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/description":[{"@language":"fr","@value":"Reconnue pour ses productions d’avant-garde, Valérie Milot nous propose un nouveau récital alliant musique et réflexions philosophiques inspirées par l’exploration spatiale. La harpe se positionne comme témoin de l’évolution de l’humain et de sa relation avec l’univers. Nebulæ vise à tisser un lien unique entre l’artiste et les mélomanes. Membres Accès Plus: Veuillez prendre note qu’exceptionnellement les rabais progressifs ne sont pas applicables pour cette représentation. "}],"http://schema.org/name":[{"@language":"fr","@value":"Nebulae\nprésenté par Anémone 47"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/anemone-47-nebulae/#WebPage"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/anemone-47-nebulae/"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/08/AnC3A9mone2047_-OYD4FtBX_H9txQ0ZIEb_.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-17T15:00:00-04:00"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_anemone-47-nebulae#Offer_0"}]},{"@id":"https://www.culture3r.com/evenements/anemone-47-nebulae/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:19:09.529-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/anemone-47-nebulae/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_anemone-47-nebulae#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"45.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_gzaagiin-malenki-je-te-promets-une-foret-voyageurs-immobiles","@type":["http://schema.org/Event"],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/description":[{"@language":"fr","@value":"1 ½ an à 5 ans | 30 minutes | Performance sonore et théâtrale immersive Thèmes: Conscience de la beauté de la nature/ ouverture au monde À noter qu’un petit mot d’introduction est prévu avant le début du spectacle et qu’une discussion animée, non obligatoire, est offerte à la fin de la représentation. Accessibilité Public de la neurodiversité ou avec hypersensibilité, consultez la trousse pour une sortie au théâtre via ce lien. G’Zaagiin Malenki signifie « je t’aime » en langue autochtone. Je voulais programmer ce spectacle, car c’est une déclaration d’amour à la nature, un appel à la prise de conscience pour les adultes sur ce que nous laisserons en héritage à nos enfants. À travers une approche sensorielle, la pièce offre aux plus jeunes un premier pas vers une conscience écologique. – Lisa Dugré, programmatrice aux arts de la scène. Joyeux mélange! Une metteuse en scène d’origine polonaise, une interprète franco-anishinaabe et une musicienne d’Abitibi : trois artistes qui entrent en résonance pour inventer un spectacle forestier enivrant. Au pied des arbres majestueux, on est tous les enfants du monde. Imagine un spectacle, mais en forme de forêt, un rassemblement parents-enfants sous une tranquille canopée. Tu fais partie du décor, tu peux t’asseoir et écouter la voix chaude et authentique d’Emily Marie Séguin, comédienne et musicienne. Tu peux aussi laisser résonner partout dans ton corps l’incroyable palette de sons de la violoncelliste Marie-Hélène Massy Emond… Parce qu’il n’y a pas que les cordes de son instrument en bois qui font de la musique! Et l’eau qui dégèle, ruisselle et alimente la forêt, qui la jouera? Derrière sa console, un rusé bruiteur active ses bols sonores et ses micros pour contribuer au tissage de cette forêt remplie de sons, de textures et de saveurs à déguster avec tous les sens et à tout âge. Équipe création : Émilie Marie Séguin, Marie-Hélène Massy Emond et Milena Buziak Composition et interprétation : Émilie Marie Séguin et Marie-Hélène Massy Émond Direction technique, bruitage et régie de tournée : Emmanuel Cognée Scénographie et éclairages : Chann Delisle Direction technique à la création : David Perreault Ninacs Créateurs invités : François Lachapelle, JF No, David Perreault Ninacs Voyageurs Immobiles, Cie de création, en coproduction avec le Théâtre français du Centre national des Arts et le soutien de la Maison Théâtre et de la Ville de Val-d’Or. Membres Accès Plus: Veuillez prendre note qu’exceptionnellement les rabais progressifs ne sont pas applicable pour cette représentation. Trousse d'accompagnement "}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-26T11:00:00-04:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/gzaagiin-malenki-je-te-promets-une-foret-voyageurs-immobiles/#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"},{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/name":[{"@language":"fr","@value":"G’zaagiin malenki – je te promets une forêt\nVoyageurs Immobiles"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/03/G27zaagiin20malenki20-20je20te20promets20une20forC3AAt_-OLo45-WJWdNSafbaV53_-scaled.jpg"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/gzaagiin-malenki-je-te-promets-une-foret-voyageurs-immobiles/"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_gzaagiin-malenki-je-te-promets-une-foret-voyageurs-immobiles#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_gzaagiin-malenki-je-te-promets-une-foret-voyageurs-immobiles#Offer_1"}]},{"@id":"https://www.culture3r.com/evenements/gzaagiin-malenki-je-te-promets-une-foret-voyageurs-immobiles/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:19:22.990-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/gzaagiin-malenki-je-te-promets-une-foret-voyageurs-immobiles/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_gzaagiin-malenki-je-te-promets-une-foret-voyageurs-immobiles#Offer_0","http://schema.org/price":[{"@value":"14.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_gzaagiin-malenki-je-te-promets-une-foret-voyageurs-immobiles#Offer_1","http://schema.org/price":[{"@value":"10.00"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culture3r-com_anyma-ora","@type":["http://schema.org/Event"],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-15T20:00:00-04:00"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/anyma-ora/"}],"http://schema.org/description":[{"@language":"fr","@value":"ANYMA ORA’ transporte son public dans un univers électro-pop envoûtant, où musique et danse s’entrelacent pour créer un spectacle hors du commun et d’une qualité exceptionnelle. Originaire de Wendake, elle fusionne son héritage autochtone avec des sonorités modernes, offrant un spectacle à la fois mystique et électrisant. Une véritable célébration de la connexion entre le passé et le futur. Sur scène, rythmes électroniques et percussions traditionnelles se mêlent à des tableaux chorégraphiques saisissants, portés par la présence magnétique d’ANYMA ORA’. Avec un premier EP, Humans (2021), acclamé par la critique et plusieurs distinctions marquantes en 2025 (Prix Stingray, ROSEQ et LOJIQ), ANYMA ORA’ s’impose comme une artiste incontournable, à la croisée des mondes et des cultures. À ne pas manquer! "}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/anyma-ora/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/11/Anyma20Ora_-Of4r852ZYu3cUNWMIv1_.jpg"}],"http://schema.org/name":[{"@language":"fr","@value":"Anyma Ora"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_anyma-ora#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_anyma-ora#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_anyma-ora#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_anyma-ora#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/anyma-ora/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:19:35.646-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/anyma-ora/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_anyma-ora#Offer_0","http://schema.org/price":[{"@value":"30.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_anyma-ora#Offer_1","http://schema.org/price":[{"@value":"27.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_anyma-ora#Offer_2","http://schema.org/price":[{"@value":"26.25"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_anyma-ora#Offer_3","http://schema.org/price":[{"@value":"25.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_simon-delisle-tache","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/02e0aa9d-4319-420f-98a3-0418e6138f9f"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/simon-delisle-tache/#WebPage"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2024/04/Simon20Delisle_-NudWefVganKcDaGsR_d_.jpg"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/name":[{"@language":"fr","@value":"Simon Delisle\nTache"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/simon-delisle-tache/"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-12T20:00:00-05:00"}],"http://schema.org/description":[{"@language":"fr","@value":"Suite à son premier one-man show acclamé par la critique, le public et ses pairs, Simon présente enfin son deuxième spectacle : TACHE. À travers le regard unique qu’il porte sur la vie et le monde, avec son humour percutant et rempli d’images puissantes, Simon Delisle s’y révélera sans filtre, abordant la large notion de « tache » de diverses façons… D’abord, au premier degré : son apparence atypique, sa trajectoire particulière et, littéralement, ses taches (vitiligo). Bref, tout ce qu’il a dû développer comme aptitude pour ne pas être celui de qui on rit, mais celui qui fait rire ! Il jasera d’autodérision et de résilience, des forces qui, aussi nobles soient-elles, viennent parfois avec leur part de noirceur… Au niveau plus figuré, on en apprendra davantage sur ces gens, ces « taches », qui ont ralenti son parcours par leur manque d’ouverture, par les raccourcis simplistes… Enfin, il y a les marques, les « taches », que la vie laisse sur chacun, abordant les thèmes du doute, des remises en question et des épreuves. Une espèce de match de boxe humoristique avec une cadence de rires qui donne mal aux joues. À l’aide d’une écriture de haut niveau et d’une confiance à toute épreuve, Simon Delisle sort de ses gonds pour faire exploser la place à coup sûr. « La vie, ce n’est pas un cadeau. C’est une fatalité… Raison de plus pour lui rire en pleine face avec ce nouveau spectacle ! » — Simon Delisle"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_simon-delisle-tache#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_simon-delisle-tache#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_simon-delisle-tache#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_simon-delisle-tache#Offer_3"}]},{"@id":"http://kg.footlight.io/resource/02e0aa9d-4319-420f-98a3-0418e6138f9f","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Simon Delisle"}],"http://schema.org/hasOccupation":[{"@value":"Humoriste"}],"http://schema.org/url":[{"@value":"http://simondelisle.ca/"}],"http://schema.org/sameAs":[{"@value":"http://kg.artsdata.ca/resource/K12-526"}]},{"@id":"https://www.culture3r.com/evenements/simon-delisle-tache/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:19:44.801-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/simon-delisle-tache/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_simon-delisle-tache#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"38.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_simon-delisle-tache#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"34.70"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_simon-delisle-tache#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"33.05"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_simon-delisle-tache#Offer_3","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"31.40"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_chris-klafford-a-sinners-tour","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/chris-klafford-a-sinners-tour/"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/10/Chris20KlC3A4fford_-Ob8OwCXyFW-D4NKDu_4_.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/chris-klafford-a-sinners-tour/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-06T20:00:00-05:00"}],"http://schema.org/name":[{"@language":"fr","@value":"Chris Kläfford\nA Sinner’s tour"}],"http://schema.org/description":[{"@language":"fr","@value":"Chris Kläfford, né en 1989, est un auteur-compositeur-interprète originaire de Lindesberg, en Suède. Il est considéré comme l’une des voix musicales les plus fortes à avoir émergé de son pays d’origine depuis longtemps. Après avoir payé son dû en tant que musicien itinérant pendant plus de dix ans, il a fait une percée bien méritée en remportant l’émission de télévision Swedish Idol en 2017. Au fil des ans, Chris a construit et maintenu une solide carrière. En 2019, il a reçu les éloges des juges et du public dans l’émission America’s Got Talent, où il s’est qualifié pour les demi-finales. Grâce à son talent, à sa productivité et à sa popularité croissante sur les médias sociaux, il a réussi à gagner une base de fans dans le monde entier, plusieurs singles de platine et une exposition record avec une place de numéro 1 sur les ondes radio."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_chris-klafford-a-sinners-tour#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_chris-klafford-a-sinners-tour#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_chris-klafford-a-sinners-tour#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_chris-klafford-a-sinners-tour#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/chris-klafford-a-sinners-tour/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:19:53.505-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/chris-klafford-a-sinners-tour/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_chris-klafford-a-sinners-tour#Offer_0","http://schema.org/price":[{"@value":"47.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_chris-klafford-a-sinners-tour#Offer_1","http://schema.org/price":[{"@value":"43.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_chris-klafford-a-sinners-tour#Offer_2","http://schema.org/price":[{"@value":"41.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_chris-klafford-a-sinners-tour#Offer_3","http://schema.org/price":[{"@value":"39.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_sri-lanka-perle-de-locean-indien-les-grands-explorateurs","http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/description":[{"@language":"fr","@value":"Sri Lanka, perle de l’Océan Indien – Rencontre avec Nicolas Pellissier C’est, depuis la constitution de la république, en 1972, que la traduction littérale la plus juste de « Sri Lanka » serait « île resplendissante ». À l’extrême sud-est de l’Inde, séparée par l’exigu détroit de Palk, elle ressemble à une boucle d’oreille accrochée au lobe indien, cette comparaison paraissant la plus juste du fait de la richesse en pierres précieuses de l’ancienne Ceylan. Cette île dont l’histoire culturelle est aussi importante que passionnante, se caractérise par son abondante végétation, la beauté de ses plages bordées de cocotiers et ses nombreuses ressources minérales et végétales. Sa situation d’insularité en a fait à la fois un refuge pour différents éléments de la culture indienne (le bouddhisme originel et les langues cinghalaise et tamoule préservées) et un pays ouvert sur le monde extérieur. Du fait de sa position géographique dans l’océan Indien, Sri Lanka a été pendant des siècles un lieu de passage incontournable entre l’occident et l’Extrême-Orient. Et donc l’un des pays les plus métissés de l’Asie. C’est un pays paradoxal. Sa population est sociable et hospitalière mais l’île a une autre facette : la tentation d’un repli identitaire de certains groupes sociaux. La guerre civile qui s’est achevée en 2009 après vingt-six ans d’affrontements entre Cinghalais et Tamouls et les heurts entre bouddhistes et musulmans au début du printemps 2018, crise politique et économique majeure à l’été 2022 montrent que l’histoire de Sri Lanka n’est pas un long fleuve tranquille. Biographie Passionné de cinéma et fils d’un réalisateur amateur de films de genre, c’est à 8 ans que Nicolas foule le sol de ses premiers plateaux de tournage. À 18 ans, il participe au tournage d’un long métrage aux États-Unis. Après avoir passé 2 ans en école de cinéma et 6 années d’études en Sciences politiques et en droit, il crée son agence de production audiovisuelle SEETHESOUND FILMS. Il se consacre à la réalisation de nombreux projets audiovisuels divers comme des clips musicaux, des métrages de fiction, des publicités et du journalisme. Lors de son retour de voyage au Laos, il rencontre le réalisateur vétéran Patrick Moreau durant l’une de ses conférences sur ce pays. Ils décident alors de co-réaliser le film sur Sri Lanka, dont le tournage s’est effectué à un rythme effréné sur deux voyages de plusieurs mois. Ce film leur vaudra la reconnaissance de l’ambassade srilankaise de Belgique qui leur commandera plusieurs ouvrages et dvd. Membres Accès Plus: Veuillez prendre note que les rabais progressifs ne sont pas applicables pour cette représentation."}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/sri-lanka-perle-de-locean-indien-les-grands-explorateurs/"}],"http://schema.org/name":[{"@language":"fr","@value":"Sri Lanka, perle de l’Océan Indien\nLes Grands Explorateurs"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/sri-lanka-perle-de-locean-indien-les-grands-explorateurs/#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/22e76d88-593d-4e82-9c46-50cba5c009ed"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/04/Sri20Lanka2C20perle20de20l27OcC3A9an20Indien_-ON03VUx_RYzDOhYPVZz_.jpg"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_sri-lanka-perle-de-locean-indien-les-grands-explorateurs#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-04T16:30:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culture3r-com_sri-lanka-perle-de-locean-indien-les-grands-explorateurs#2026-04-04T163000-0400"},{"@id":"http://kg.footlight.io/resource/culture3r-com_sri-lanka-perle-de-locean-indien-les-grands-explorateurs#2026-04-04T133000-0400"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-04T13:30:00-04:00"}]},{"@id":"https://www.culture3r.com/evenements/sri-lanka-perle-de-locean-indien-les-grands-explorateurs/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:20:03.602-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/sri-lanka-perle-de-locean-indien-les-grands-explorateurs/"}]},{"@id":"http://kg.footlight.io/resource/22e76d88-593d-4e82-9c46-50cba5c009ed","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Projections"}],"http://www.w3.org/2004/02/skos/core#closeMatch":[{"@id":"http://kg.artsdata.ca/resource/ScreeningEvent"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_sri-lanka-perle-de-locean-indien-les-grands-explorateurs#Offer_0","http://schema.org/price":[{"@value":"33.92"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_sri-lanka-perle-de-locean-indien-les-grands-explorateurs#2026-04-04T163000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Sri Lanka, perle de l’Océan Indien\nLes Grands Explorateurs"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-04T16:30:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_sri-lanka-perle-de-locean-indien-les-grands-explorateurs#2026-04-04T133000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Sri Lanka, perle de l’Océan Indien\nLes Grands Explorateurs"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-04T13:30:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_andre-sauve","http://schema.org/description":[{"@language":"fr","@value":"André Sauvé fait un retour sur scène avec une création audacieuse où son regard unique sur le monde puise son inspiration dans la nature et ses mystères. \n Il nous livre ses observations dont chaque détail prend une dimension surprenante, ne voyant dans cette nature qui l'entoure qu’une métaphore de l’esprit humain. \n À travers ce voyage, il tisse un lien entre humour et poésie, explorant l’âme humaine à travers le prisme de son environnement. Humour et réflexion s’unissent dans cette expérience fidèle à la touche et au talent inimitable d’André Sauvé. \n Mise en scène : Louis-Karl Tremblay Direction artistique : Pierre Bernard"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-286"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/andre-sauve/"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/04/AndrC3A920SauvC3A9_-OOwHJKfgsIJMyER5B3p_.jpg"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/name":[{"@language":"fr","@value":"André Sauvé\nNaturellement"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-04T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/andre-sauve/#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_andre-sauve#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_andre-sauve#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_andre-sauve#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_andre-sauve#Offer_3"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-06T20:00:00-05:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culture3r-com_andre-sauve#2026-04-04T200000-0400"},{"@id":"http://kg.footlight.io/resource/culture3r-com_andre-sauve#2026-11-06T200000-0500"}]},{"@id":"https://www.culture3r.com/evenements/andre-sauve/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:20:27.912-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/andre-sauve/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_andre-sauve#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"59.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_andre-sauve#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"53.80"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_andre-sauve#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"51.20"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_andre-sauve#Offer_3","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"48.60"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_andre-sauve#2026-04-04T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"André Sauvé\nNaturellement"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-04T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_andre-sauve#2026-11-06T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"André Sauvé\nNaturellement"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-06T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jo-cormier-animal-2","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Jo Cormier\nMachine"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/11/Jo20Cormier_-OdOzJEx67OdGA6h2Q91_.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-01T20:00:00-04:00"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/b0b27b7c-481e-4041-bc62-40f558274f65"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/jo-cormier-animal-2/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/description":[{"@language":"fr","@value":"L’incomparable humoriste Jo Cormier débarque avec son deuxième one-man-show : MACHINE. Alors que dans son premier spectacle il comparait l’humain à un animal, cette fois-ci il pousse la réflexion sur l’être humain encore plus loin, toujours avec son regard unique. Ou comme dirait Jo : un regard donné par un genre de gars fin trentaine, la bédaine, qui pense avoir 23 ans mentalement, mais qui en a 4 en réalité et qui aime regarder le soleil le plus longtemps possible. Avec la technologie, on optimise nos vies comme si on était des ordinateurs, mais est-ce qu’on se demande encore c’est quoi, être un humain? Dans ce spectacle plus personnel et plus honnête, Jo se montre tout aussi vulnérable que drôle alors qu’il se questionne à savoir si oui ou non il est humain. MACHINE est un spectacle simple, amusant, nono. Un spectacle où personne n’a besoin d’être parfait, seulement humain. "}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/jo-cormier-animal-2/"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_jo-cormier-animal-2#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_jo-cormier-animal-2#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_jo-cormier-animal-2#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_jo-cormier-animal-2#Offer_3"}]},{"@id":"http://kg.footlight.io/resource/b0b27b7c-481e-4041-bc62-40f558274f65","@type":["http://schema.org/Person"],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}],"http://schema.org/name":[{"@language":"fr","@value":"Jo Cormier"}],"http://schema.org/hasOccupation":[{"@value":"Humoriste"}],"http://schema.org/url":[{"@value":"https://jocormier.ca/"}],"http://schema.org/sameAs":[{"@value":"http://kg.artsdata.ca/resource/K5-212"}]},{"@id":"https://www.culture3r.com/evenements/jo-cormier-animal-2/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:20:47.096-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/jo-cormier-animal-2/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jo-cormier-animal-2#Offer_0","http://schema.org/price":[{"@value":"39.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jo-cormier-animal-2#Offer_1","http://schema.org/price":[{"@value":"35.60"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jo-cormier-animal-2#Offer_2","http://schema.org/price":[{"@value":"33.90"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jo-cormier-animal-2#Offer_3","http://schema.org/price":[{"@value":"32.20"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-lapointe","@type":["http://schema.org/Event"],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/pierre-lapointe/#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-283"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Pierre Lapointe\nChansons démodées pour ceux qui ont le coeur abimé"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2024/09/Pierre20Lapointe_-O6aiNK3tcrphjbmqcdw_.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-25T19:00:00-04:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/pierre-lapointe/"}],"http://schema.org/description":[{"@language":"fr","@value":"Déjà par son titre, le nouveau spectacle de Pierre Lapointe promet. C’est pour accompagner son nouvel album, 10 chansons démodées pour ceux qui ont le coeur abimé qu’il a créé cet écrin magnifique. Des arrangements soignés, un décor déroutant et toujours cette façon singulière qu’il a d’occuper la scène, oscillant sans cesse entre un sens de l’humour corrosif et une puissante charge mélancolique. Durant l’écriture de son nouvel opus, Pierre Lapointe n’avait qu’une obsession en tête; écrire des chansons contemporaines qui semblent exister depuis toujours. En allant puiser son inspiration dans le travail des grands noms de la chanson francophone qui l’ont précédé, il confirme plus que jamais son appartenance à une famille, à une tradition. Comme en guise d’hommage à Léveillée, Barbara, Sylvestre, Brel ou encore Aznavour, il a su imposer par ses dernières chansons, une rigueur et une virtuosité propre aux artistes ayant atteint une certaine maîtrise de leur art. Bien que l’album soit caractérisé par des orchestrations mettant de l’avant plusieurs instruments, Pierre a décidé de présenter son travail en tournée sous une forme beaucoup plus épurée. Il sera accompagné par le duo Fortin-Poirier*. L’adaptation des arrangements des chansons sur scène sera assurée par Amélie Fortin. Avec ce choix inhabituel de présenter ses titres avec un piano et 4 mains, Pierre Lapointe promet cette fois de surprendre le public par la rigueur et la simplicité de sa proposition. Les chansons démodées pour ceux qui ont le coeur abimé de Pierre Lapointe sur scène, un spectacle qui présentera des chansons anciennes et nouvelles, une belle façon de s’imprégner de l’oeuvre de celui qui est assurément le chanteur le plus surprenant de sa génération. Référence: *Duo Fortin-Poirier duofortinpoirier.com "}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-lapointe#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-lapointe#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-lapointe#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-lapointe#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/pierre-lapointe/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:21:01.051-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/pierre-lapointe/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-lapointe#Offer_0","http://schema.org/price":[{"@value":"52.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-lapointe#Offer_1","http://schema.org/price":[{"@value":"47.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-lapointe#Offer_2","http://schema.org/price":[{"@value":"45.25"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-lapointe#Offer_3","http://schema.org/price":[{"@value":"43.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jo-cormier-esquisse_rodage","http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/b0b27b7c-481e-4041-bc62-40f558274f65"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/jo-cormier-esquisse_rodage/#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/name":[{"@language":"fr","@value":"Jo Cormier\nEsquisse_Rodage"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/08/Jo20Cormier_-OYl5_XQyRCY-YkFsJbL_.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-06T20:00:00-05:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/jo-cormier-esquisse_rodage/"}],"http://schema.org/description":[{"@language":"fr","@value":"Je vous invite à venir faire partie des premières esquisses de mon prochain show. Je ne vous promets rien, tout au plus des « hi hi ». Un travail au plomb, un rough de charpente, des écharpes et des échardes. J’esquisse mon heure. Tu esquisses un sourire ou deux. Nous esquissons dans le but que ce projet devienne exquis et que tu puisses dire : « J’étais là ». Membres Accès Plus: Veuillez prendre note qu’exceptionnellement seul le rabais de 10 % et 15 % est disponible pour ce spectacle."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_jo-cormier-esquisse_rodage#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-07T20:00:00-05:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culture3r-com_jo-cormier-esquisse_rodage#2026-02-06T200000-0500"},{"@id":"http://kg.footlight.io/resource/culture3r-com_jo-cormier-esquisse_rodage#2026-02-07T200000-0500"}]},{"@id":"https://www.culture3r.com/evenements/jo-cormier-esquisse_rodage/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:21:23.588-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/jo-cormier-esquisse_rodage/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jo-cormier-esquisse_rodage#Offer_0","http://schema.org/price":[{"@value":"35.00"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jo-cormier-esquisse_rodage#2026-02-06T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Jo Cormier\nEsquisse_Rodage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-06T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jo-cormier-esquisse_rodage#2026-02-07T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Jo Cormier\nEsquisse_Rodage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-07T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_borealis-ne-fait-pas-relache-activite-familiale","@type":["http://schema.org/Event"],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/borealis-ne-fait-pas-relache-activite-familiale/#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-694"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/borealis-ne-fait-pas-relache-activite-familiale/"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/10/borealis-ne-fait-pas-relache-oymhqywwhwhbl-jeald.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-03-03"}],"http://schema.org/name":[{"@language":"fr","@value":"Activité familiale de la relâche"}],"http://schema.org/description":[{"@language":"fr","@value":"À Boréalis, relâche rime avec environnement! Venez vivre une expérience muséale et scientifique unique avec votre famille, et apprenez-en plus sur l’environnement qui nous entoure. PROMO VISITE DU MUSÉE INCLUS L’activité se déroulant dans le musée, la visite libre de l’exposition Transformations est incluse dans le prix. Acheter un billet "}]},{"@id":"https://www.culture3r.com/evenements/borealis-ne-fait-pas-relache-activite-familiale/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:21:33.601-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/borealis-ne-fait-pas-relache-activite-familiale/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_sam-breton","http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-255"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/sam-breton/"}],"http://schema.org/name":[{"@language":"fr","@value":"Sam Breton\nGA-LÉ ALLER"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/11/Sam20Breton_-OeWp9xMWkhCvuIUeIYZ_.jpg"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-06-18T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/sam-breton/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/description":[{"@language":"fr","@value":"Vivre un épuisement professionnel, c’est aussi vivre une recharge professionnelle. Autrement dit : travailler mon deuxième one-man-show de façon encore plus efficace, sans perdre de temps avec des niaiseries. Là, j’aimerais bien te vanter mon spectacle pour te convaincre de venir me voir sur scène, mais ça entrerait dans la catégorie « perte de temps », parce que de toute façon, juste avec ma face, tu l’sais déjà si tu vas venir voir mon show ou non. Avoue? 😉 "}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_sam-breton#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_sam-breton#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_sam-breton#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_sam-breton#Offer_3"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-12-03T20:00:00-05:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culture3r-com_sam-breton#2026-06-18T200000-0400"},{"@id":"http://kg.footlight.io/resource/culture3r-com_sam-breton#2026-09-09T200000-0400"},{"@id":"http://kg.footlight.io/resource/culture3r-com_sam-breton#2026-12-03T200000-0500"}]},{"@id":"https://www.culture3r.com/evenements/sam-breton/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:21:37.410-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/sam-breton/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_sam-breton#Offer_0","http://schema.org/price":[{"@value":"46.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_sam-breton#Offer_1","http://schema.org/price":[{"@value":"42.10"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_sam-breton#Offer_2","http://schema.org/price":[{"@value":"40.15"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_sam-breton#Offer_3","http://schema.org/price":[{"@value":"38.20"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_sam-breton#2026-06-18T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Sam Breton\nGA-LÉ ALLER"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-06-18T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_sam-breton#2026-09-09T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Sam Breton\nGA-LÉ ALLER"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-09T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_sam-breton#2026-12-03T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Sam Breton\nGA-LÉ ALLER"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-12-03T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jey-fournier-rodage","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/9c098b46-cc97-4c46-8b83-ea55bea0be56"}],"http://schema.org/name":[{"@language":"fr","@value":"Jey Fournier\nRodage"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/11/Jey20Fournier_-OeWwz4cH-lL3mqwrtPd_.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-23T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/jey-fournier-rodage/#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/jey-fournier-rodage/"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/description":[{"@language":"fr","@value":"Un jour, on se réveille et on réalise qu’on a 30 ans (ou presque). On voit les autres avancer, on essaie de ne pas trop se comparer, mais on a quand même l’impression d’être en retard sur la vie. Entre la séparation, les remises en question et la quête du bonheur, j’ai choisi d’en rire. Tant qu’à être en retard, autant en faire un spectacle. "}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_jey-fournier-rodage#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_jey-fournier-rodage#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_jey-fournier-rodage#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_jey-fournier-rodage#Offer_3"}]},{"@id":"http://kg.footlight.io/resource/9c098b46-cc97-4c46-8b83-ea55bea0be56","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Jey Fournier"}]},{"@id":"https://www.culture3r.com/evenements/jey-fournier-rodage/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:21:47.047-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/jey-fournier-rodage/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jey-fournier-rodage#Offer_0","http://schema.org/price":[{"@value":"35.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jey-fournier-rodage#Offer_1","http://schema.org/price":[{"@value":"32.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jey-fournier-rodage#Offer_2","http://schema.org/price":[{"@value":"30.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jey-fournier-rodage#Offer_3","http://schema.org/price":[{"@value":"29.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_chantal-lamarre-steppettes-et-cornemuse","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Chantal Lamarre\nSteppettes et cornemuse"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/3837fb9b-760f-4769-b390-932d098497c4"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/9475525c-10fa-4a53-b89b-8c7d9ba68726"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2027-04-03T20:00:00-04:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/11/Chantal20Lamarre_-OeX2pTEnQ2WxKbmklpv_.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/chantal-lamarre-steppettes-et-cornemuse/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/chantal-lamarre-steppettes-et-cornemuse/"}],"http://schema.org/description":[{"@language":"fr","@value":"Après 40 ans dans le métier, c’est un retour aux sources, une salle, une scène, un spot; Chantal Lamarre convie le public à une soirée comme dans les bons vieux cabarets d’antan où on pousse la chansonnette et on ose la steppette. Observatrice du monde, oiseau moqueur, animatrice et fille de scène qui, jusqu’à 60 ans, n’a jamais pensé la moindre seconde s’y aventurer seule, « elle chante, elle danse, elle joue la comédie », les « oooo » devraient succéder à des « aaa », avec promesse de rires et, peut-être même, l’indispensable petit « motton » d’émotion; bref, en toute prétention, une formule gagnante pour un variétés aux codes vintages qui exploite des enjeux bien actuels. Le regard dans le rétroviseur pour envisager l’avenir avec moins de lourdeur; la pipelette qui commente les grands évènements avec son sens critique et sa drôlerie, la fille qui parle de société et de culture avec impétuosité et la femme pitre qui n’a jamais eu peur du ridicule se retrouveront, le temps d’une belle soirée, pour sortir de la grisaille, avoir la banane et l’envie de voir un peu la vie autrement… Mesdames, mesdemoiselles, messieurs, et tout ce qui se situe entre les deux, jeunes et nouveaux vieux ; Steppettes et cornemuses, est définitivement à inscrire à votre carnet de sorties! "}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_chantal-lamarre-steppettes-et-cornemuse#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_chantal-lamarre-steppettes-et-cornemuse#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_chantal-lamarre-steppettes-et-cornemuse#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_chantal-lamarre-steppettes-et-cornemuse#Offer_3"}]},{"@id":"http://kg.footlight.io/resource/3837fb9b-760f-4769-b390-932d098497c4","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Chantal Lamarre"}],"http://schema.org/hasOccupation":[{"@language":"fr","@value":"Interprète"}],"http://schema.org/url":[{"@value":"https://chantallamarre.com/"}],"http://schema.org/sameAs":[{"@value":"http://www.wikidata.org/entity/Q2956297"},{"@value":"https://www.facebook.com/chantal.lamarre.steppettes"}]},{"@id":"https://www.culture3r.com/evenements/chantal-lamarre-steppettes-et-cornemuse/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:21:55.824-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/chantal-lamarre-steppettes-et-cornemuse/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_chantal-lamarre-steppettes-et-cornemuse#Offer_0","http://schema.org/price":[{"@value":"50.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_chantal-lamarre-steppettes-et-cornemuse#Offer_1","http://schema.org/price":[{"@value":"45.70"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_chantal-lamarre-steppettes-et-cornemuse#Offer_2","http://schema.org/price":[{"@value":"43.55"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_chantal-lamarre-steppettes-et-cornemuse#Offer_3","http://schema.org/price":[{"@value":"41.40"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_liliane-blanco-binette-rodage","@type":["http://schema.org/Event"],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/6a167be3-2bfe-4d4b-ae89-af38c5524ab8"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-09T20:00:00-04:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/name":[{"@language":"fr","@value":"Liliane Blanco-Binette\nToréador"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/liliane-blanco-binette-rodage/#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"Avec une lucidité désarmante et une autodérision qui frappe juste, Liliane propose un premier One Woman Show où l’humour se mêle à l’intime. Ce n’est pas un simple spectacle, c’est une rencontre. Elle transforme ses vulnérabilités en matière comique, révélant une façon décalée d’observer le monde et d’assumer ses contradictions. Entre confidences crues et complicité partagée, elle entraîne le public dans un moment à la fois fragile et nécessaire, où l’on se reconnaît dans ce qui nous échappe et nous dépasse. "}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/liliane-blanco-binette-rodage/"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/11/Liliane20Blanco-Binette_-Oeq-cj0-t_LQFsLw3nD_.jpg"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_liliane-blanco-binette-rodage#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_liliane-blanco-binette-rodage#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_liliane-blanco-binette-rodage#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_liliane-blanco-binette-rodage#Offer_3"}]},{"@id":"http://kg.footlight.io/resource/6a167be3-2bfe-4d4b-ae89-af38c5524ab8","@type":["http://schema.org/Person"],"http://schema.org/hasOccupation":[{"@language":"fr","@value":"Humoriste"}],"http://schema.org/url":[{"@value":"https://lilianeblancobinette.com/"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}],"http://schema.org/name":[{"@language":"fr","@value":"Liliane Blanco-Binette"}]},{"@id":"https://www.culture3r.com/evenements/liliane-blanco-binette-rodage/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:22:05.281-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/liliane-blanco-binette-rodage/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_liliane-blanco-binette-rodage#Offer_0","http://schema.org/price":[{"@value":"42.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_liliane-blanco-binette-rodage#Offer_1","http://schema.org/price":[{"@value":"38.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_liliane-blanco-binette-rodage#Offer_2","http://schema.org/price":[{"@value":"36.75"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_liliane-blanco-binette-rodage#Offer_3","http://schema.org/price":[{"@value":"35.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_theatre-des-nouveaux-compagnons-lentente-le-theatre-des-nouveaux-compagnons","http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K5-1028"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/theatre-des-nouveaux-compagnons-lentente-le-theatre-des-nouveaux-compagnons/"}],"http://schema.org/description":[{"@language":"fr","@value":"Un samedi matin, une cheffe d’entreprise à la tête d’une chaîne de magasins spécialisés dans les produits de rénovation réunit ses deux enfants à son domicile. L’un est médecin dans un hôpital de renommée internationale, l’autre est policière dans une ville de taille moyenne. La dirigeante doit faire face à certains enjeux et souhaite informer ses enfants de plusieurs éléments clés relatifs à l’entreprise, notamment concernant une entente en négociation avec des acquéreurs potentiels, sous l’encadrement de sa jeune adjointe. Or, cette adjointe détient depuis plusieurs jours des informations susceptibles de remettre en question le projet d’entente, ainsi que de perturber la cheffe d’entreprise et ses enfants. Ce texte, alliant humour et drame, propose une analyse des valeurs clés présentes dans la société. Il examine surtout les principes humanistes généralement admis qui influencent nos comportements et nos actions, dont la fraternité. Lecture dirigée et écrite par Reynald Robinson Membres Accès Plus: Veuillez prendre note qu’exceptionnellement les rabais progressifs ne sont pas applicables pour cette représentation"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/theatre-des-nouveaux-compagnons-lentente-le-theatre-des-nouveaux-compagnons/#WebPage"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/12/ThC3A9C3A2tre20des20Nouveaux20Compagnons20-20L27entente_-OgqtvhrQYFu92MdYWZq_.jpg"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventCancelled"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/name":[{"@language":"fr","@value":"Théâtre des Nouveaux Compagnons – L’entente\nLe Théâtre des Nouveaux Compagnons"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-24T19:30:00-05:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_theatre-des-nouveaux-compagnons-lentente-le-theatre-des-nouveaux-compagnons#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-25T14:00:00-05:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culture3r-com_theatre-des-nouveaux-compagnons-lentente-le-theatre-des-nouveaux-compagnons#2026-01-24T193000-0500"},{"@id":"http://kg.footlight.io/resource/culture3r-com_theatre-des-nouveaux-compagnons-lentente-le-theatre-des-nouveaux-compagnons#2026-01-25T140000-0500"}]},{"@id":"https://www.culture3r.com/evenements/theatre-des-nouveaux-compagnons-lentente-le-theatre-des-nouveaux-compagnons/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-23T23:19:37.278-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/theatre-des-nouveaux-compagnons-lentente-le-theatre-des-nouveaux-compagnons/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_theatre-des-nouveaux-compagnons-lentente-le-theatre-des-nouveaux-compagnons#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"28.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_theatre-des-nouveaux-compagnons-lentente-le-theatre-des-nouveaux-compagnons#2026-01-24T193000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Théâtre des Nouveaux Compagnons – L’entente\nLe Théâtre des Nouveaux Compagnons"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-24T19:30:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_theatre-des-nouveaux-compagnons-lentente-le-theatre-des-nouveaux-compagnons#2026-01-25T140000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Théâtre des Nouveaux Compagnons – L’entente\nLe Théâtre des Nouveaux Compagnons"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-25T14:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_rita-baga","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/rita-baga/"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K13-191"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/name":[{"@language":"fr","@value":"Rita Baga\nSPRAYNET"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2023/01/ritabaga-spraynet-sanstexte-1x1-1.jpg"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-28T20:00:00-05:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/9475525c-10fa-4a53-b89b-8c7d9ba68726"}],"http://schema.org/description":[{"@language":"fr","@value":"Rita Baga, la plus célèbre drag queen québécoise qui s’illustre aussi en Europe, revient chez vous avec sa toute nouvelle tournée musicale SPRAYNET! Rita a conquis le Québec avec Créature, la plus longue tournée solo d’une drag en Amérique, vue par 75 000 spectacteur.trices et récipiendaire d’un Olivier. En 2025, l’artiste drag présente un nouveau spectacle bien différent, mais tout autant flamboyant. Accompagnée par son band sous la direction musicale de Jacob Roberge, SPRAYNET vous convie à un véritable party des années 80. Rita revisitera les plus grands succès de cette décennie qu’elle adore, en plus d’interpréter les chansons de son premier album original à paraître en 2025. Ce spectacle met en valeur la voix puissante de Rita, que plusieurs ont découvert à Chanteurs Masqués. La tournée SPRAYNET est évidemment accentuée de costumes à couper le souffle, de l’humour et du charisme propres à Rita! Préparez votre plus imposante coiffure pour une soirée éblouissante où vous ne pourrez pas vous empêcher de rire, chanter et danser! Pour publics de tous âges – Achetez vite vos billets, nombre de représentations limité. "}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/rita-baga/#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_rita-baga#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_rita-baga#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_rita-baga#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_rita-baga#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/rita-baga/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:22:14.957-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/rita-baga/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_rita-baga#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"47.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_rita-baga#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"43.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_rita-baga#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"41.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_rita-baga#Offer_3","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"39.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jose-gaudet-y-etait-temps","@type":["http://schema.org/Event"],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/description":[{"@language":"fr","@value":"Humoriste, José Gaudet nous fait rire depuis déjà très longtemps ! Il a à son actif plus de 25 ans d’animation radio, deux tournées de spectacles bien récompensées, animation de galas et 10 ans à la barre de talks-shows dont Ça finit bien la semaine. José n’arrivera pas sur la pointe des pieds, il prépare un spectacle de stand up pur et dur. Il abordera sa vision des choses sur la vie d’aujourd’hui et ses réflexions sur la société avec son humour authentique, son franc parlé et son énergie éclatante que nous connaissons bien. À 53 ans, il arrive avec son premier one man-show. C’est le cas de le dire… Y’était temps! "}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2023/11/JosC3A920Gaudet_-NiFZ2J8bLjYJrs3-Xnv_.jpg"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/jose-gaudet-y-etait-temps/"}],"http://schema.org/name":[{"@language":"fr","@value":"José Gaudet\nY’était temps!"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/b3e28c59-2c93-4210-ab72-47d8555ec99d"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-18T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/jose-gaudet-y-etait-temps/#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_jose-gaudet-y-etait-temps#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_jose-gaudet-y-etait-temps#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_jose-gaudet-y-etait-temps#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_jose-gaudet-y-etait-temps#Offer_3"}]},{"@id":"http://kg.footlight.io/resource/b3e28c59-2c93-4210-ab72-47d8555ec99d","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"José Gaudet"}],"http://schema.org/hasOccupation":[{"@language":"fr","@value":"Humoriste"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}],"http://schema.org/sameAs":[{"@value":"http://kg.artsdata.ca/resource/K5-1035"}]},{"@id":"https://www.culture3r.com/evenements/jose-gaudet-y-etait-temps/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:22:33.925-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/jose-gaudet-y-etait-temps/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jose-gaudet-y-etait-temps#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"52.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jose-gaudet-y-etait-temps#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"47.50"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jose-gaudet-y-etait-temps#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"45.25"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jose-gaudet-y-etait-temps#Offer_3","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"43.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_clement-jacques-iris","@type":["http://schema.org/Event"],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/clement-jacques-iris/#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-24T20:00:00-04:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/clement-jacques-iris/"}],"http://schema.org/name":[{"@language":"fr","@value":"Clement Jacques\nIris"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/03/Clement20Jacques_-OMNBITNNvLuexRLmltT_-scaled.jpg"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/description":[{"@language":"fr","@value":"Clement Jacques présente le spectacle IRIS qui fait suite à la sortie de son 5e album en carrière. Un album qualifié d’exceptionnel de par la qualité de ses textes, de sa musique, de ses interprétations et de sa réalisation si bien menée par David Laflèche. À l’image de ce qui est véhiculé sur l’album, Clement partage sur scène autant de réflexions, révélant ainsi avec sensibilité sa vision des relations humaines et du monde qui l’entoure. À la fois simple et sincère, ce spectacle mis en scène par Daniel Fortin (Cirque du Soleil) est résolument ancré dans son époque et présente un amalgame harmonieux de nouveau et d’anciens répertoires de l’artiste."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_clement-jacques-iris#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_clement-jacques-iris#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_clement-jacques-iris#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_clement-jacques-iris#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/clement-jacques-iris/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:22:48.389-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/clement-jacques-iris/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_clement-jacques-iris#Offer_0","http://schema.org/price":[{"@value":"39.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_clement-jacques-iris#Offer_1","http://schema.org/price":[{"@value":"35.60"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_clement-jacques-iris#Offer_2","http://schema.org/price":[{"@value":"33.90"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_clement-jacques-iris#Offer_3","http://schema.org/price":[{"@value":"32.20"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_billy-tellier-hypocrites-3","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/spec-qc-ca_artiste_billy-tellier"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/billy-tellier-hypocrites-3/"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/name":[{"@language":"fr","@value":"Billy Tellier\nKicker la ruche"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2024/09/Billy20Tellier_-O7AfdoTizZlkhpLBq04_.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-20T20:00:00-04:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/description":[{"@language":"fr","@value":"Fier du succès de sa dernière tournée avec plus de 50 000 billets vendus et près de 200 représentations, Billy Tellier présente son 3e one-man-show Kicker la ruche. Billy, qui a ce talent inouï de toucher la cible avec des sujets rassembleurs, présente cette fois-ci un spectacle encore plus punché et rythmé dans lequel il livre ses états d’âme sur sa peur de faire les mauvais choix… Toute sa vie, Billy a tenté de vivre en bonne petite abeille ouvrière, en faisant ce qu’on attendait de lui et en prenant les décisions les plus sages afin d’impacter le moins possible les gens qui l’entourent. Dans ce nouveau spectacle, il se demande s’il ne serait pas temps de remettre les choses en question, de brasser son quotidien, bref de kicker la ruche. Mais est-ce vraiment une bonne idée pour quelqu’un qui angoisse en choisissant entre crémeuse ou traditionnelle? "}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/billy-tellier-hypocrites-3/#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_billy-tellier-hypocrites-3#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_billy-tellier-hypocrites-3#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_billy-tellier-hypocrites-3#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_billy-tellier-hypocrites-3#Offer_3"}]},{"@id":"http://kg.footlight.io/resource/spec-qc-ca_artiste_billy-tellier","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@value":"Billy Tellier"}],"http://schema.org/sameAs":[{"@value":"http://kg.artsdata.ca/resource/K12-252"}],"http://schema.org/image":[{"@value":"https://spec.qc.ca/uploads/artistes/billy-tellier-2.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"Révélation Juste pour rire 2004, les succès de Billy Tellier ne font que se multiplier depuis le début de sa carrière, autant sur scène, qu’à la radio et à la télévision. Après des prestations chaleureusement applaudies à Juste pour rire, au ComédiHa (Grand rire de Québec), à Zoofest ou encore aux Parlementeries, Billy Tellier a lancé son premier one-man-show, La loi du plus fort, à l’hiver 2013. Mis en scène par Christian Viau, le spectacle, maintenant disponible sur DVD, a été acclamé tant par la critique que le public. En 2016 et 2017, il a co-animé pour deux années consécutives un Gala ComédiHa aux côtés de Mario Jean. Puis, en 2017 et 2018 dans le cadre du Festival Juste pour rire, il a présenté Le petit monde de Billy Tellier Live à la Place des arts, un spectacle conçu spécialement pour les fans de ses capsules radio et où il a fait salle comble à chacune de ces soirées uniques. Dès l’automne 2019, Billy Tellier sera de retour sur les planches partout au Québec avec son deuxième one-man-show : Hypocrite(s). Avec celui-ci, Billy saura mettre en lumière le petit côté sombre de tous et chacun dans un spectacle servant d’exutoire collectif et qui fera le plus grand bien! À la barre de l’émission matinale du 96,9 CKOI depuis 2012, Billy co-anime présentement Debout les comiques avec Martin Cloutier, Patrice Bélanger et Tammy Verge. Au Gala Les Olivier 2017, Billy est d’ailleurs reparti avec la statuette dans la catégorie Capsule ou sketch radio humoristique de l’année pour ses hilarantes capsules du Petit monde de Billy, diffusées à CKOI. Un CD Best of de ses populaires capsules est disponible sur iTunes. Du côté de la télévision, il a notamment assuré avec brio l’animation des émissions Trouve-moi ça (2014), Des champions du web (2014-2015) et Le poker show (2008), toutes diffusées sur V/TQS. Puis, il a également été comédien dans quelques séries, dont La vie parfaite (Radio-Canada) et Colocs.TV (MusiquePlus), qu’il a scénarisé avec Alexandre Barrette en plus d’y jouer. Finalement, Billy a fait de nombreuses apparitions et chroniques sur des émissions telles Juste pour rire en direct, Brassard en direct d’aujourd’hui, Atomes crochus, Prière de ne pas envoyer de fleurs, Nous avons les images, Vrak la vie, etc. Également prisé comme auteur, il a écrit et script-édité le premier one-man-show d’Alexandre Barrette, a écrit pour plusieurs éditions des galas Artis et Les Olivier, a été script-éditeur sur le spectacle Rechargé de Rachid Badouri et a écrit sur le spectacle de Maxim Martin, Enfin."}]},{"@id":"https://www.culture3r.com/evenements/billy-tellier-hypocrites-3/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:23:02.563-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/billy-tellier-hypocrites-3/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_billy-tellier-hypocrites-3#Offer_0","http://schema.org/price":[{"@value":"44.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_billy-tellier-hypocrites-3#Offer_1","http://schema.org/price":[{"@value":"40.30"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_billy-tellier-hypocrites-3#Offer_2","http://schema.org/price":[{"@value":"38.45"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_billy-tellier-hypocrites-3#Offer_3","http://schema.org/price":[{"@value":"36.60"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_phil-roy-philou","http://schema.org/description":[{"@language":"fr","@value":"Après les spectacles « MONSIEUR » et « PHILOU », Phil Roy amorce le rodage de son troisième one-man-show en carrière. Venez assister à la création de ce spectacle. "}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/11/Phil20Roy_-OeWq_y7Onobgh64sPRi_.jpg"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K13-180"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/phil-roy-philou/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/phil-roy-philou/"}],"http://schema.org/name":[{"@language":"fr","@value":"Phil Roy\nRodage – No 3"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-07-24T20:00:00-04:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_phil-roy-philou#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_phil-roy-philou#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_phil-roy-philou#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_phil-roy-philou#Offer_3"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-07-25T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culture3r-com_phil-roy-philou#2026-07-24T200000-0400"},{"@id":"http://kg.footlight.io/resource/culture3r-com_phil-roy-philou#2026-07-25T200000-0400"}]},{"@id":"https://www.culture3r.com/evenements/phil-roy-philou/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:23:13.160-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/phil-roy-philou/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_phil-roy-philou#Offer_0","http://schema.org/price":[{"@value":"35.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_phil-roy-philou#Offer_1","http://schema.org/price":[{"@value":"32.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_phil-roy-philou#Offer_2","http://schema.org/price":[{"@value":"30.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_phil-roy-philou#Offer_3","http://schema.org/price":[{"@value":"29.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_phil-roy-philou#2026-07-24T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Phil Roy\nRodage – No 3"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-07-24T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_phil-roy-philou#2026-07-25T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Phil Roy\nRodage – No 3"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-07-25T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_troupe-du-theatre-aphasique-suivez-le-fil","@type":["http://schema.org/Event"],"http://schema.org/description":[{"@language":"fr","@value":"Écrite et joué par une douzaine de membres du Théâtre Aphasique, la pièce Suivez le fil invite à un voyage captivant dans l’univers intérieur de trois personnages réunis lors de séances d’hypnose. Mise en scène par Richard Gaulin. "}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/troupe-du-theatre-aphasique-suivez-le-fil/#WebPage"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-11T19:30:00-04:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/12/Troupe20du20ThC3A9C3A2tre20Aphasique_-OgcXdiHcywEjbkU8Kc0_.png"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/troupe-du-theatre-aphasique-suivez-le-fil/"}],"http://schema.org/name":[{"@language":"fr","@value":"Troupe du Théâtre Aphasique\nSuivez le fil"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_troupe-du-theatre-aphasique-suivez-le-fil#Offer_0"}]},{"@id":"https://www.culture3r.com/evenements/troupe-du-theatre-aphasique-suivez-le-fil/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:22:24.291-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/troupe-du-theatre-aphasique-suivez-le-fil/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_troupe-du-theatre-aphasique-suivez-le-fil#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culture3r-com_louis-morissette-en-rodage","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/a5ed65ba-049e-420e-968b-ffae8096f8d1"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/name":[{"@language":"fr","@value":"Louis Morissette\nSous Pression"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2026/01/Louis20Morissette_-OiNWHmAgHZwsvRcrqja_.png"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-21T20:00:00-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/louis-morissette-en-rodage/"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/louis-morissette-en-rodage/#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/description":[{"@language":"fr","@value":"Maintenant dans la cinquantaine, il porte un regard humoristique et caustique sur son parcours, le pouvoir des échecs professionnels, et la société de performance. Avec une authenticité déroutante, il se met à nu et pose la question : après quoi on court?"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_louis-morissette-en-rodage#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_louis-morissette-en-rodage#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_louis-morissette-en-rodage#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_louis-morissette-en-rodage#Offer_3"}]},{"@id":"http://kg.footlight.io/resource/a5ed65ba-049e-420e-968b-ffae8096f8d1","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Louis Morissette"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste et comédien"}],"http://schema.org/url":[{"@value":"https://louismorissette.ca/"}],"http://schema.org/hasOccupation":[{"@value":"Humoriste et comédien"}],"http://schema.org/sameAs":[{"@value":"http://kg.artsdata.ca/resource/K12-288"},{"@value":"http://www.wikidata.org/entity/Q3262749"},{"@value":"https://www.facebook.com/LouisMoPageOfficielle"}]},{"@id":"https://www.culture3r.com/evenements/louis-morissette-en-rodage/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-19T23:23:33.227-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/louis-morissette-en-rodage/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_louis-morissette-en-rodage#Offer_0","http://schema.org/price":[{"@value":"52.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_louis-morissette-en-rodage#Offer_1","http://schema.org/price":[{"@value":"47.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_louis-morissette-en-rodage#Offer_2","http://schema.org/price":[{"@value":"45.25"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_louis-morissette-en-rodage#Offer_3","http://schema.org/price":[{"@value":"43.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_maude-landry-linvolution","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-493"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/maude-landry-linvolution/"}],"http://schema.org/name":[{"@language":"fr","@value":"Maude Landry\nTrop cool"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/09/Maude20Landry_-Oa0FRoKIjUbYBp_LoT9_.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-15T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/maude-landry-linvolution/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/description":[{"@language":"fr","@value":"Dans un monde où il faut se démarquer tout en suivant les tendances, Maude Landry se demande pourquoi elle n’a jamais fait partie de la gang des cools. C’est ce qui la rend attachante. Décontractée et pince-sans-rire, elle pose son regard taquin sur elle-même et sur la société, en passant délibérément par quatre chemins. Un spectacle moderne et assumé, pendant lequel on se dit parfois « C’est niaiseux!… Mais fallait y penser ». Maude vous invite dans un périple au cœur de ses réflexions, ses doutes et ses déboires, pour que vous sortiez de la salle en disant « C’était trop cool! » "}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_maude-landry-linvolution#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_maude-landry-linvolution#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_maude-landry-linvolution#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_maude-landry-linvolution#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/maude-landry-linvolution/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:23:24.380-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/maude-landry-linvolution/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_maude-landry-linvolution#Offer_0","http://schema.org/price":[{"@value":"43.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_maude-landry-linvolution#Offer_1","http://schema.org/price":[{"@value":"39.40"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_maude-landry-linvolution#Offer_2","http://schema.org/price":[{"@value":"37.60"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_maude-landry-linvolution#Offer_3","http://schema.org/price":[{"@value":"35.80"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_martin-petit-un-meilleur-monde-avec-martin-petitun-meilleur-monde-avec-martin-petit","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2024/10/Martin20Petit_-O9PksuqIKPUhElKpl9O_.jpg"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-29T16:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/martin-petit-un-meilleur-monde-avec-martin-petitun-meilleur-monde-avec-martin-petit/#WebPage"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/martin-petit-un-meilleur-monde-avec-martin-petitun-meilleur-monde-avec-martin-petit/"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-246"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/description":[{"@language":"fr","@value":"Un monde meilleur devient réalité avec le 5Ème one-man show de Martin Petit! Les politiciens nous le promettent à tous les 4 ans, les religions à la fin de nos jours mais Martin Petit a décidé que c’était maintenant. Une invitation à regarder le monde avec enthousiasme et entrain, et rendre ce monde meilleur encore meilleur! "}],"http://schema.org/name":[{"@language":"fr","@value":"Martin Petit\nUn meilleur monde avec Martin Petit"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_martin-petit-un-meilleur-monde-avec-martin-petitun-meilleur-monde-avec-martin-petit#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_martin-petit-un-meilleur-monde-avec-martin-petitun-meilleur-monde-avec-martin-petit#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_martin-petit-un-meilleur-monde-avec-martin-petitun-meilleur-monde-avec-martin-petit#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_martin-petit-un-meilleur-monde-avec-martin-petitun-meilleur-monde-avec-martin-petit#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/martin-petit-un-meilleur-monde-avec-martin-petitun-meilleur-monde-avec-martin-petit/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:24:07.525-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/martin-petit-un-meilleur-monde-avec-martin-petitun-meilleur-monde-avec-martin-petit/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_martin-petit-un-meilleur-monde-avec-martin-petitun-meilleur-monde-avec-martin-petit#Offer_0","http://schema.org/price":[{"@value":"47.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_martin-petit-un-meilleur-monde-avec-martin-petitun-meilleur-monde-avec-martin-petit#Offer_1","http://schema.org/price":[{"@value":"43.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_martin-petit-un-meilleur-monde-avec-martin-petitun-meilleur-monde-avec-martin-petit#Offer_2","http://schema.org/price":[{"@value":"41.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_martin-petit-un-meilleur-monde-avec-martin-petitun-meilleur-monde-avec-martin-petit#Offer_3","http://schema.org/price":[{"@value":"39.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_theatre-des-nouveaux-compagnons-bebe-a-bord-le-theatre-des-nouveaux-compagnons","http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K5-1028"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Théâtre des Nouveaux Compagnons – Bébé à bord\nLe Théâtre des Nouveaux Compagnons"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/theatre-des-nouveaux-compagnons-bebe-a-bord-le-theatre-des-nouveaux-compagnons/#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-16T19:30:00-04:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/theatre-des-nouveaux-compagnons-bebe-a-bord-le-theatre-des-nouveaux-compagnons/"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/09/ThC3A9C3A2tre20des20Nouveaux20Compagnons20-20BC3A9bC3A920C3A020bord_-OZetPqfLVcZs-HgVPiT_.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"Philippe et Christine viennent d’emménager dans une maison aux mille et une rénovations à compléter. Depuis quelque temps, Philippe ne pense qu’à une chose : il souhaite ardemment avoir des enfants. Le problème, c’est que Christine n’est pas prête. Alors qu’il essaie par tous les moyens de la convaincre, les enjeux s’intensifient avec l’arrivée de Valérie, la sœur de Christine, et Alain, son nouveau chum. Ces derniers lui font une proposition surprenante. Le temps presse! Jusqu’à quel point Philippe veut-il des enfants? Jusqu’où sommes-nous prêts à aller pour devenir parents? Drôlement loin pour certains! Texte de Claude Montminy Mise en scène de Steve Bernier Assisté de Julie Balleux Membres Accès Plus: Veuillez prendre note qu’exceptionnellement les rabais progressifs ne sont pas applicables pour cette représentation"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_theatre-des-nouveaux-compagnons-bebe-a-bord-le-theatre-des-nouveaux-compagnons#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-19T14:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culture3r-com_theatre-des-nouveaux-compagnons-bebe-a-bord-le-theatre-des-nouveaux-compagnons#2026-04-16T193000-0400"},{"@id":"http://kg.footlight.io/resource/culture3r-com_theatre-des-nouveaux-compagnons-bebe-a-bord-le-theatre-des-nouveaux-compagnons#2026-04-17T193000-0400"},{"@id":"http://kg.footlight.io/resource/culture3r-com_theatre-des-nouveaux-compagnons-bebe-a-bord-le-theatre-des-nouveaux-compagnons#2026-04-18T140000-0400"},{"@id":"http://kg.footlight.io/resource/culture3r-com_theatre-des-nouveaux-compagnons-bebe-a-bord-le-theatre-des-nouveaux-compagnons#2026-04-18T193000-0400"},{"@id":"http://kg.footlight.io/resource/culture3r-com_theatre-des-nouveaux-compagnons-bebe-a-bord-le-theatre-des-nouveaux-compagnons#2026-04-19T140000-0400"}]},{"@id":"https://www.culture3r.com/evenements/theatre-des-nouveaux-compagnons-bebe-a-bord-le-theatre-des-nouveaux-compagnons/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:23:45.267-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/theatre-des-nouveaux-compagnons-bebe-a-bord-le-theatre-des-nouveaux-compagnons/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_theatre-des-nouveaux-compagnons-bebe-a-bord-le-theatre-des-nouveaux-compagnons#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"28.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_theatre-des-nouveaux-compagnons-bebe-a-bord-le-theatre-des-nouveaux-compagnons#2026-04-16T193000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Théâtre des Nouveaux Compagnons – Bébé à bord\nLe Théâtre des Nouveaux Compagnons"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-16T19:30:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_theatre-des-nouveaux-compagnons-bebe-a-bord-le-theatre-des-nouveaux-compagnons#2026-04-17T193000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Théâtre des Nouveaux Compagnons – Bébé à bord\nLe Théâtre des Nouveaux Compagnons"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-17T19:30:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_theatre-des-nouveaux-compagnons-bebe-a-bord-le-theatre-des-nouveaux-compagnons#2026-04-18T140000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Théâtre des Nouveaux Compagnons – Bébé à bord\nLe Théâtre des Nouveaux Compagnons"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-18T14:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_theatre-des-nouveaux-compagnons-bebe-a-bord-le-theatre-des-nouveaux-compagnons#2026-04-18T193000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Théâtre des Nouveaux Compagnons – Bébé à bord\nLe Théâtre des Nouveaux Compagnons"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-18T19:30:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_theatre-des-nouveaux-compagnons-bebe-a-bord-le-theatre-des-nouveaux-compagnons#2026-04-19T140000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Théâtre des Nouveaux Compagnons – Bébé à bord\nLe Théâtre des Nouveaux Compagnons"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-19T14:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_ostr-a-la-jazz-serie-paysages-musicaux","@type":["http://schema.org/Event"],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/ostr-a-la-jazz-serie-paysages-musicaux/#WebPage"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/ostr-a-la-jazz-serie-paysages-musicaux/"}],"http://schema.org/name":[{"@language":"fr","@value":"OSTR – À la jazz!\nSérie Paysages musicaux"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/05/OSTR20-20C38020la20jazz_-OQFBH-2RfiiDpvYGnZ__.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-03T14:00:00-04:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/description":[{"@language":"fr","@value":"Préparez-vous à vivre un concert où les frontières s’effacent, où les styles se rencontrent et s’embrassent avec finesse. Le classique flirte avec le jazz dans ce concert énergisant! Mélodies classiques, rythmes jazzés, complicité entre les musiciens : tout est réuni pour offrir un moment à la fois passionné et enveloppant, aussi raffiné qu’audacieux. Ce programme séduira autant les mélomanes classiques que les amoureux de jazz, dans une atmosphère vivante et chaleureuse. Richard Capolla, contrebasse | Jean-Philippe Tanguay, flûte | François St-Jean, percussions | Jonathan Turgeon, piano Membres Accès Plus: Veuillez prendre note que les rabais progressifs ne s’appliquent pas pour cette représentation."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_ostr-a-la-jazz-serie-paysages-musicaux#Offer_0"}]},{"@id":"https://www.culture3r.com/evenements/ostr-a-la-jazz-serie-paysages-musicaux/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:23:56.197-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/ostr-a-la-jazz-serie-paysages-musicaux/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_ostr-a-la-jazz-serie-paysages-musicaux#Offer_0","http://schema.org/price":[{"@value":"44.84"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_5-a-7-la-route-du-rire-2","http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/name":[{"@language":"fr","@value":"5 à 7 La route du rire"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/5-a-7-la-route-du-rire-2/"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-05T17:30:00-05:00"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/09/520C3A02072020La20route20du20rire_-Oa0ZTnvqnqsviWWjNPe_.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"Qui a dit que les spectacles d’humour ne devaient être vus qu’à partir de 20 h? Culture Trois-Rivières croit plutôt aux bienfaits de l’humour directement après le travail avec les 5 à 7 La route du rire! Ces 5 à 7 festifs mettront en scène de 2 à 4 humoristes de la relève par soir qui, à tour de rôle, animeront la scène de leur folie sans limite. Des soirées de stand-up comiques de qualité, avec une représentation à saveur de comédie club où l’art de l’humour est ramené à l’essentiel : un micro, une scène, une rencontre! En plus, une consommation est offerte gratuitement à l’achat d’un billet (sur produits sélectionnés lors de la représentation) Membres Accès Plus: Veuillez prendre note qu’exceptionnellement les rabais progressifs ne sont pas applicables pour cette représentation."}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/5-a-7-la-route-du-rire-2/#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_5-a-7-la-route-du-rire-2#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-23T17:30:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culture3r-com_5-a-7-la-route-du-rire-2#2026-02-05T173000-0500"},{"@id":"http://kg.footlight.io/resource/culture3r-com_5-a-7-la-route-du-rire-2#2026-03-05T173000-0500"},{"@id":"http://kg.footlight.io/resource/culture3r-com_5-a-7-la-route-du-rire-2#2026-04-23T173000-0400"}]},{"@id":"https://www.culture3r.com/evenements/5-a-7-la-route-du-rire-2/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:23:35.763-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/5-a-7-la-route-du-rire-2/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_5-a-7-la-route-du-rire-2#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"27.50"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_5-a-7-la-route-du-rire-2#2026-02-05T173000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"5 à 7 La route du rire"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-05T17:30:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_5-a-7-la-route-du-rire-2#2026-03-05T173000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"5 à 7 La route du rire"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-05T17:30:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_5-a-7-la-route-du-rire-2#2026-04-23T173000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"5 à 7 La route du rire"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-23T17:30:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_francois-bellefeuille-sauvage","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-285"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/francois-bellefeuille-sauvage/"}],"http://schema.org/description":[{"@language":"fr","@value":"François Bellefeuille est de retour sur scène avec son troisième spectacle : Sauvage. Hilarant, incisif et imprévisible, il livre une performance de stand-up à la fois intelligente et éclatée, repoussant les limites de son style inimitable. L’humoriste, dont la maîtrise du ridicule est à son apogée, porte un regard tout aussi mordant sur le monde, et vous entraîne dans un univers où la réalité côtoie l’absurde. Préparez-vous à avoir mal aux abdos; François Bellefeuille est au sommet de son art! Jusqu’au prochain spectacle… on l’espère! "}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/francois-bellefeuille-sauvage/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/name":[{"@language":"fr","@value":"François Bellefeuille\nSauvage"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/06/FranC3A7ois20Bellefeuille_-OS-Vv975UahCbp4dJya_.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-15T20:00:00-04:00"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_francois-bellefeuille-sauvage#Offer_0"}]},{"@id":"https://www.culture3r.com/evenements/francois-bellefeuille-sauvage/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:23:27.434-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/francois-bellefeuille-sauvage/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_francois-bellefeuille-sauvage#Offer_0","http://schema.org/price":[{"@value":"52.00"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culture3r-com_theatre-des-gens-de-la-place-embrasse-le-theatre-des-gens-de-la-place","http://schema.org/name":[{"@language":"fr","@value":"Théâtre des gens de la place – Embrasse\nLe Théâtre des gens de la place"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/06/ThC3A9C3A2tre20des20gens20de20la20place20-20Embrasse_-OSL3THB2SX0V03hOXML_.png"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/theatre-des-gens-de-la-place-embrasse-le-theatre-des-gens-de-la-place/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/theatre-des-gens-de-la-place-embrasse-le-theatre-des-gens-de-la-place/"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-19T19:30:00-05:00"}],"http://schema.org/description":[{"@language":"fr","@value":"Le jeune Hugo vit et travaille avec sa mère, Béatrice, propriétaire d’un magasin de tissus dans un centre commercial en région. Son univers est bousculé lorsque Béatrice est arrêtée pour avoir frappé publiquement une jeune professeure de l’endroit qui l’accuse de violenter son fils. Passionné de mode, rêvant d’étudier dans une grande école de couture, Hugo décide de créer le vêtement que portera sa mère lors de son procès pour voie de fait. Ce costume lui donnera, à ses yeux, les allures d’une mère parfaite, alors qu’il est guidé dans ce projet par une vision fantasmée du designer français Yves Saint Laurent."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_theatre-des-gens-de-la-place-embrasse-le-theatre-des-gens-de-la-place#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-22T14:00:00-05:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culture3r-com_theatre-des-gens-de-la-place-embrasse-le-theatre-des-gens-de-la-place#2026-02-19T193000-0500"},{"@id":"http://kg.footlight.io/resource/culture3r-com_theatre-des-gens-de-la-place-embrasse-le-theatre-des-gens-de-la-place#2026-02-20T193000-0500"},{"@id":"http://kg.footlight.io/resource/culture3r-com_theatre-des-gens-de-la-place-embrasse-le-theatre-des-gens-de-la-place#2026-02-21T193000-0500"},{"@id":"http://kg.footlight.io/resource/culture3r-com_theatre-des-gens-de-la-place-embrasse-le-theatre-des-gens-de-la-place#2026-02-21T140000-0500"},{"@id":"http://kg.footlight.io/resource/culture3r-com_theatre-des-gens-de-la-place-embrasse-le-theatre-des-gens-de-la-place#2026-02-22T140000-0500"}]},{"@id":"https://www.culture3r.com/evenements/theatre-des-gens-de-la-place-embrasse-le-theatre-des-gens-de-la-place/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:24:26.707-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/theatre-des-gens-de-la-place-embrasse-le-theatre-des-gens-de-la-place/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_theatre-des-gens-de-la-place-embrasse-le-theatre-des-gens-de-la-place#Offer_0","http://schema.org/price":[{"@value":"28.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_theatre-des-gens-de-la-place-embrasse-le-theatre-des-gens-de-la-place#2026-02-19T193000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Théâtre des gens de la place – Embrasse\nLe Théâtre des gens de la place"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-19T19:30:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_theatre-des-gens-de-la-place-embrasse-le-theatre-des-gens-de-la-place#2026-02-20T193000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Théâtre des gens de la place – Embrasse\nLe Théâtre des gens de la place"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-20T19:30:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_theatre-des-gens-de-la-place-embrasse-le-theatre-des-gens-de-la-place#2026-02-21T193000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Théâtre des gens de la place – Embrasse\nLe Théâtre des gens de la place"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-21T19:30:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_theatre-des-gens-de-la-place-embrasse-le-theatre-des-gens-de-la-place#2026-02-21T140000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Théâtre des gens de la place – Embrasse\nLe Théâtre des gens de la place"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-21T14:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_theatre-des-gens-de-la-place-embrasse-le-theatre-des-gens-de-la-place#2026-02-22T140000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Théâtre des gens de la place – Embrasse\nLe Théâtre des gens de la place"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-22T14:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_45-de-la-taupiniere-theatre-des-petites-ames","@type":["http://schema.org/Event"],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/description":[{"@language":"fr","@value":"2 ½ à 5 ans et + | 40 minutes | Théâtre et marionnettes Thèmes: Émerveillement/aventure/amitié À noter qu’un petit mot d’introduction est prévu avant le début du spectacle et qu’une discussion animée, non obligatoire, est offerte à la fin de la représentation. \n J’ai été touchée par cette pièce qui ouvre une belle porte vers l’imaginaire de nos enfants. Dès les premiers instants, les marionnettes attachantes capteront leur attention et les inviteront à découvrir la richesse du monde animal et la nécessité d’en prendre soin. Une rencontre intime, qui suscitera la curiosité des petits comme des grands. -Lisa Dugré, programmatrice aux arts de la scène. \n Ils sont deux. Oui, deux. Sur la butte tout en haut, ils sont deux. Qui sont-ils? On ne le sait pas tout à fait. Des géants? Pas tout à fait. Sont-ils perdus? Pas tout à fait non plus. Sous leurs pieds, ils sont deux... fois deux…fois deux. Petits, petits, tout petits. Que sont-ils? On ne le sait pas tout à fait… …mais… on finira peut-être par le savoir… et vous aussi! Crédits : Théâtre des Petites Âmes et le collectif de la colline en co-production avec le Collège Lionel-Groulx, la ville de Blainville et le Petit Théâtre du Nord. Idéation texte et mise en scène : Isabelle Payant, en co-création avec les interprètes Interprètes (en alternance): Chloe Deriaz, Charlie Gauvreau, Kym Gosselin, Héloïse Lalumière, Félix Poirier, Camille Provencher, Frédéric Rhéaume, Delphine Ricard, Lou Séguin, Amélie Tremblay Décor: Clémentine Verheagen et Isabelle Payant Costumes et marionnettes : Isabelle Payant Lumière : Nancy Longchamp Musique : Thomas Tremblay, Adam Plante Régie : Delphine Ricard ou Chloe Deriaz \n Membres Accès Plus: Veuillez prendre note qu'exceptionnellement les rabais progressifs ne sont pas applicable pour cette représentation."}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/name":[{"@language":"fr","@value":"45, de la Taupinière\nThéâtre des Petites Âmes"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/45-de-la-taupiniere-theatre-des-petites-ames/"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/03/452C20de20la20TaupiniC3A8re_-OLo4_Um1nacmyKIEzXq_.jpg"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-31T11:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/45-de-la-taupiniere-theatre-des-petites-ames/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_45-de-la-taupiniere-theatre-des-petites-ames#Offer_0"}]},{"@id":"https://www.culture3r.com/evenements/45-de-la-taupiniere-theatre-des-petites-ames/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:24:17.155-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/45-de-la-taupiniere-theatre-des-petites-ames/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_45-de-la-taupiniere-theatre-des-petites-ames#Offer_0","http://schema.org/price":[{"@value":"14.50"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culture3r-com_dave-gaudet-ia","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/04/Dave20Gaudet_-OOcA2NcE2R26s2SJiqg_.jpg"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/d5cb0feb-55f2-426b-9f00-6d5c46ed7696"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-22T20:00:00-05:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/dave-gaudet-ia/"}],"http://schema.org/name":[{"@language":"fr","@value":"Dave Gaudet\nIA (Intelligence Artificielle)"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/dave-gaudet-ia/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/description":[{"@language":"fr","@value":"La carrière de l’humoriste à l’énergie débordante et au grand charisme Dave Gaudet est en plein essor depuis la pandémie. Son dernier spectacle La Légende près de 50 000 billets vendus et sa tournée s’est achevé le 12 avril 2025 à Laval, à la Place Bell. Le positionnant comme premier humoriste québécois à avoir présenté son spectacle dans cet aréna. Au terme de la représentation du 12 avril, il a annoncé son nouveau spectacle IA (Intelligence Artificielle), un spectacle inspiré par et créé en partie avec l’intelligence artificielle, un concept bien aligné avec sa créativité et sa folie des grandeurs. Encore une fois, un spectacle unique. Le légende ne fait que se concrétiser. Encore."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_dave-gaudet-ia#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_dave-gaudet-ia#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_dave-gaudet-ia#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_dave-gaudet-ia#Offer_3"}]},{"@id":"http://kg.footlight.io/resource/d5cb0feb-55f2-426b-9f00-6d5c46ed7696","@type":["http://schema.org/Person"],"http://schema.org/sameAs":[{"@value":"https://www.facebook.com/davegaudetofficiel/"}],"http://schema.org/name":[{"@language":"fr","@value":"Dave Gaudet"}],"http://schema.org/hasOccupation":[{"@value":"Humoriste"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}],"http://schema.org/url":[{"@value":"https://davegaudet.com/"}]},{"@id":"https://www.culture3r.com/evenements/dave-gaudet-ia/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-20T23:23:31.538-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/dave-gaudet-ia/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_dave-gaudet-ia#Offer_0","http://schema.org/price":[{"@value":"45.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_dave-gaudet-ia#Offer_1","http://schema.org/price":[{"@value":"41.20"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_dave-gaudet-ia#Offer_2","http://schema.org/price":[{"@value":"39.30"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_dave-gaudet-ia#Offer_3","http://schema.org/price":[{"@value":"37.40"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_dave-fenley-unplugged","@type":["http://schema.org/Event"],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/e6d56f73-5349-4077-b0e1-87bb18713a22"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/dave-fenley-unplugged/"}],"http://schema.org/name":[{"@language":"fr","@value":"Dave Fenley\nTrio"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/02/Dave20Fenley_-OK764rJSqSkfCSTk728_.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/dave-fenley-unplugged/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-25T20:00:00-04:00"}],"http://schema.org/description":[{"@language":"fr","@value":"**Spectacle présenté en anglais** Avec près de 40 000 billets vendus et plus de 50 spectacles au Québec, le coup de cœur des québécois, Dave Fenley revient avec un tout nouveau spectacle en formule trio ! Le chanteur américain folkcountry à la voix impressionnante qui a été découvert en 2022 via la populaire émission En direct de l’Univers au spécial de Lise Dion, s’entoure de deux musiciens québécois pour cette nouvelle aventure. De nouvelles chansons, de nouvelles interprétations des chansons populaires parsemé de savoureuses anecdotes vous attendent dans ce tout nouveau spectacle de Dave Fenley ! Membres Accès Plus: Veuillez prendre note qu’exceptionnellement les rabais progressifs de 10% et 15% sont applicables seulement."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_dave-fenley-unplugged#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_dave-fenley-unplugged#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_dave-fenley-unplugged#Offer_2"}]},{"@id":"http://kg.footlight.io/resource/e6d56f73-5349-4077-b0e1-87bb18713a22","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"en","@value":"Dave Fenley"}],"http://schema.org/hasOccupation":[{"@value":"Singer"}],"http://schema.org/url":[{"@value":"https://www.davefenley.com/"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Singer"}],"http://schema.org/sameAs":[{"@value":"https://www.facebook.com/davefenleylovesyou"}]},{"@id":"https://www.culture3r.com/evenements/dave-fenley-unplugged/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:24:39.456-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/dave-fenley-unplugged/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_dave-fenley-unplugged#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"58.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_dave-fenley-unplugged#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"52.90"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_dave-fenley-unplugged#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"50.35"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_living-the-dream-hommage-a-dream-theater","@type":["http://schema.org/Event"],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-21T20:00:00-04:00"}],"http://schema.org/name":[{"@language":"fr","@value":"Living The Dream\nHommage à Dream Theater"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/11/Living20The20Dream_-OdtXeWLE5cEA_Z7F09Z_.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/living-the-dream-hommage-a-dream-theater/#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/description":[{"@language":"fr","@value":"Gens de Trois-Rivières! Notre unique hommage à Dream Theater débarquera dans votre région le 21 mars prochain! Nous vous présenterons un répertoire varié qui navigue à travers les différentes ères et plusieurs des albums de ce groupe qui nous passionne. Soucieux d’offrir une expérience renouvelée, nous avons préparé des nouvelles pièces spécifiquement pour notre passage chez vous. Qu’est-ce qui vous attend?! Une prestation organique et en personne d’une durée de 2h30! Nous avons travaillé dur afin vous présenter du métal progressif d’une complexité et d’une beauté inégalée. Tous les adeptes du groupe légendaire y trouveront certainement leur compte! Partagez à vos familles et amis! Membres Accès Plus: Veuillez prendre note qu’exceptionnellement les rabais progressifs ne sont pas applicable pour cette représentation. "}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/living-the-dream-hommage-a-dream-theater/"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_living-the-dream-hommage-a-dream-theater#Offer_0"}]},{"@id":"https://www.culture3r.com/evenements/living-the-dream-hommage-a-dream-theater/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:24:57.368-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/living-the-dream-hommage-a-dream-theater/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_living-the-dream-hommage-a-dream-theater#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"38.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_sinem-kara-2","@type":["http://schema.org/Event"],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/name":[{"@language":"fr","@value":"Sinem Kara\nRodage"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/sinem-kara-2/"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/11/Sinem20Kara_-Of4p1g471YXVPgL-ycc_.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"Après plus de 70 représentations de son 1er spectacle intitulé « Sinem Kara », elle prépare son 2e one woman show dont la sortie est prévue en 2026. C'est donc le rodage de ce nouveau spectacle qui est présenté ce soir. \n Membres Accès Plus: Veuillez prendre note qu'exceptionnellement seul le rabais de 10 % et 15 % est disponible pour ce spectacle."}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-19T20:00:00-04:00"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/sinem-kara-2/#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_sinem-kara-2#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_sinem-kara-2#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_sinem-kara-2#Offer_2"}]},{"@id":"https://www.culture3r.com/evenements/sinem-kara-2/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:25:07.482-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/sinem-kara-2/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_sinem-kara-2#Offer_0","http://schema.org/price":[{"@value":"35.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_sinem-kara-2#Offer_1","http://schema.org/price":[{"@value":"32.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_sinem-kara-2#Offer_2","http://schema.org/price":[{"@value":"30.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_productions-de-la-42e-rue","http://schema.org/name":[{"@language":"fr","@value":"Les Productions de la 42e Rue\nSuivant!"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/11/Les20Productions20de20la2042e20Rue_-OeIW-HjnrkGWpWaR2iC_.jpg"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/productions-de-la-42e-rue/"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-31T14:00:00-05:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/productions-de-la-42e-rue/#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"Onze candidats se présentent à des auditions : onze personnalités attachantes, onze univers éclatés, onze rêves de Broadway. Sur scène, ils se dévoilent avec fougue, humour et authenticité, chacun espérant décrocher une place dans une revue musicale. Leurs couleurs se déploient, leurs voix résonnent, et le répertoire — soigneusement choisi — nous fait voyager entre émotion et éclats de rire. Mais ce n’est que le début… Après une courte transition, le rideau se lève sur la revue elle-même : celle pour laquelle ils ont tout donné, un tourbillon de numéros chorégraphiés, de chansons emblématiques et de moments mémorables. « Suivant! », une comédie musicale à ne pas manquer cet hiver! Membres Accès Plus: Veuillez prendre note qu’exceptionnellement les rabais progressifs ne sont pas applicable pour cette représentation. "}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_productions-de-la-42e-rue#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T14:00:00-05:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culture3r-com_productions-de-la-42e-rue#2026-01-31T140000-0500"},{"@id":"http://kg.footlight.io/resource/culture3r-com_productions-de-la-42e-rue#2026-01-31T193000-0500"},{"@id":"http://kg.footlight.io/resource/culture3r-com_productions-de-la-42e-rue#2026-02-01T140000-0500"}]},{"@id":"https://www.culture3r.com/evenements/productions-de-la-42e-rue/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-30T23:23:30.690-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/productions-de-la-42e-rue/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_productions-de-la-42e-rue#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"33.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_productions-de-la-42e-rue#2026-01-31T140000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Les Productions de la 42e Rue\nSuivant!"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-31T14:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_productions-de-la-42e-rue#2026-01-31T193000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Les Productions de la 42e Rue\nSuivant!"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-31T19:30:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_productions-de-la-42e-rue#2026-02-01T140000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Les Productions de la 42e Rue\nSuivant!"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T14:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mike-clay","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/mike-clay/"}],"http://schema.org/name":[{"@language":"fr","@value":"Mike Clay"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/11/Mike20Clay_-Of5-7Gfjs-8Ioz35ocB_.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/mike-clay/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-27T21:00:00-04:00"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/description":[{"@language":"fr","@value":"* FORMULE DEBOUT * Mike Clay revient avec un projet festif : Michael Samba. Préparez-vous à danser et vivre une soirée où la chaleur des rythmes tropicaux rencontre la créativité québécoise. Sur scène, Mike Clay devient Michael Samba; son alter ego qui s’inspire des trois univers principaux dans sa vie, soit ses pèlerinages à la recherche de vagues inexplorées pour les surfer, ses méditations matinales et ses voyages à travers le monde en tant que globe-trotteur. Michael Samba souligne son style irrésistiblement groove : un mélange de musique tropicale, de joyeuse dérision et une énergie solaire vibrante. Bien que solo, il s’est remplir la scène avec notamment son célèbre «petit piano», sa cloche à vache et ses nombreux «free-style» plus dansants les uns que les autres ! "}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_mike-clay#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_mike-clay#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_mike-clay#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_mike-clay#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/mike-clay/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:24:48.561-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/mike-clay/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mike-clay#Offer_0","http://schema.org/price":[{"@value":"30.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mike-clay#Offer_1","http://schema.org/price":[{"@value":"27.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mike-clay#Offer_2","http://schema.org/price":[{"@value":"26.25"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mike-clay#Offer_3","http://schema.org/price":[{"@value":"25.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_patrick-groulx-rodage","http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-186"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-20T20:00:00-04:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/name":[{"@language":"fr","@value":"Patrick Groulx\nTrop longtemps seul"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/patrick-groulx-rodage/"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/10/Patrick20Groulx_-ObXjXtJtcD19V0bc4hR_.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/patrick-groulx-rodage/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/description":[{"@language":"fr","@value":"Patrick Groulx est enfin de retour sur scène pour présenter son 5e one-man-show : Trop longtemps seul. Dans ce show qui lui ressemble à 100%, il renoue avec un style d’humour qui rappelle parfois le côté décalé et éclaté qu’il avait à l’époque du Groulx luxe. Toujours aussi franc et irrévérencieux, Patrick embrasse maintenant l’idée de vieillir, alors qu’il ne s’est jamais senti aussi bien, aussi jeune et aussi libre que dans la cinquantaine. Trop longtemps seul est un spectacle qui rejoint toutes les générations, qui fait rire aux larmes, réfléchir par moment et a ce petit côté juvénile unique à la Pat Groulx. Pour son 5e show, il regroupe plusieurs facettes comiques de ses précédents spectacles et offre à son public du Groulx pur et dur. Un spectacle aussi le fun à voir seul qu’en gang! Peut-être y croiserez-vous le Curé Poirier et le Brigadier? « Un jour, mon équipe m’a dit : Pat, tu passes trop de temps tout seul… Ça l’air que ça se reflétait dans mes textes! J’ai éclaté de rire car ils avaient raison. J’ai 51 ans, ma blonde est en Outaouais et mes enfants ont leur propre vie maintenant. Pour la première fois depuis longtemps, je retrouve une certaine liberté et je passe plus de temps seul. Ça m’a amené à réfléchir, observer et philosopher sur un paquet de trucs loufoques et inutiles, mais combien inspirants. Bref, tout ça a donné naissance à ce nouveau spectacle que je suis très excité de vous présenter. » – Patrick Groulx"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_patrick-groulx-rodage#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_patrick-groulx-rodage#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_patrick-groulx-rodage#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_patrick-groulx-rodage#Offer_3"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-22T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culture3r-com_patrick-groulx-rodage#2026-08-20T200000-0400"},{"@id":"http://kg.footlight.io/resource/culture3r-com_patrick-groulx-rodage#2026-08-21T200000-0400"},{"@id":"http://kg.footlight.io/resource/culture3r-com_patrick-groulx-rodage#2026-08-22T200000-0400"}]},{"@id":"https://www.culture3r.com/evenements/patrick-groulx-rodage/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:25:26.683-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/patrick-groulx-rodage/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_patrick-groulx-rodage#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"45.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_patrick-groulx-rodage#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"41.20"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_patrick-groulx-rodage#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"39.30"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_patrick-groulx-rodage#Offer_3","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"37.40"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_patrick-groulx-rodage#2026-08-20T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Patrick Groulx\nTrop longtemps seul"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-20T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_patrick-groulx-rodage#2026-08-21T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Patrick Groulx\nTrop longtemps seul"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-21T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_patrick-groulx-rodage#2026-08-22T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Patrick Groulx\nTrop longtemps seul"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-22T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_cathy-gauthier-classique","@type":["http://schema.org/Event"],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/cathy-gauthier-classique/"}],"http://schema.org/name":[{"@language":"fr","@value":"Cathy Gauthier\nRodage"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K13-38"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/11/Cathy20Gauthier_-OeWwTMNCz9J7e5GxucJ_.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"Venez assister au processus créatif du 5e spectacle de Cathy Gauthier. De par vos réactions, vous serez les juges de ce qui se retrouvera dans le show final! Fous rires garantis ou argent remis! "}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-24T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/cathy-gauthier-classique/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_cathy-gauthier-classique#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_cathy-gauthier-classique#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_cathy-gauthier-classique#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_cathy-gauthier-classique#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/cathy-gauthier-classique/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:25:17.628-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/cathy-gauthier-classique/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_cathy-gauthier-classique#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"39.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_cathy-gauthier-classique#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"35.60"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_cathy-gauthier-classique#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"33.90"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_cathy-gauthier-classique#Offer_3","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"32.20"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_stephane-archambault-point","@type":["http://schema.org/Event"],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-14T20:00:00-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/stephane-archambault-point/"}],"http://schema.org/name":[{"@language":"fr","@value":"Stéphane Archambault\nPoint"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/04/StC3A9phane20Archambault_-OMw1YRcxCMgNSxf37NM_.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/stephane-archambault-point/#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"Personne ne s’y attendait, pas même lui – Stéphane Archambault se remet à la musique, et il était grand temps. De retour sur scène pour la première fois depuis la fin de son populaire groupe Mes Aïeux, il se lance en solo dans un spectacle de 12 nouvelles chansons, augmenté de quelques reprises choisies qui sont en phase avec – ou qui ont influencé – ses compositions. Des récits de ruptures et de renouveaux, de temps et d’alentours, posés dans un arc narratif; une mise à jour de qui et où il est, au je plutôt qu’au nous. Marqué par l’âge d’or français, au phrasé évoquant affablement la musique traditionnelle, Archambault avance une chanson plurielle, généreuse, à la fois révérencieuse et un peu crottée. Un exercice d’impudeur : il enlève ses pelures pour se présenter de façon intime, contemplative, – pour connecter davantage avec un public qu’il souhaite voir repartir avec le sourire. Un spectacle aux réflexions sereines sur l’âge et les écueils communs en même temps qu’une leçon d’humilité pour un artiste qui revient vers vous : se lançant à la fois dans le vide et dans vos bras. Un premier album est attendu pour l’automne – pour des retrouvailles dans une fébrilité partagée. "}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_stephane-archambault-point#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_stephane-archambault-point#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_stephane-archambault-point#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_stephane-archambault-point#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/stephane-archambault-point/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:25:36.295-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/stephane-archambault-point/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_stephane-archambault-point#Offer_0","http://schema.org/price":[{"@value":"42.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_stephane-archambault-point#Offer_1","http://schema.org/price":[{"@value":"38.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_stephane-archambault-point#Offer_2","http://schema.org/price":[{"@value":"36.75"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_stephane-archambault-point#Offer_3","http://schema.org/price":[{"@value":"35.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_francis-legendre-rodage-pousser-drette","http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/description":[{"@language":"fr","@value":"ENFIN ! Je suis tellement fier de vous présenter mon premier spectacle d’humour : Pousser drette. Pousser drette, c’est deux ans de travail acharné, à fignoler des jokes, à bâtir des histoires, à me raconter sur scène à grands coups d’essais pis d’erreurs. À bizouner le rire dans les recoins du Québec, beau temps, mauvais temps, le vent dans le dos pis le soleil dans les yeux. Pousser drette, c’est doux, honnête et (idéalement) drôle. Je suis fier de moi, pis ça me ferait plaisir de partager cette fierté-là avec vous autres. Je sais pas grand-chose dans vie, mais je sais une chose : si j’ai poussé drette, c’est grâce au rire pis à l’amour. Si toi aussi t’as le goût de pousser drette l’espace d’une soirée, viens voir mon spectacle. On va rire, on va se patenter du bonheur, pis qui sait… on va peut-être même se dire collectivement qu’on s’aime un peu. Membres Accès Plus: Veuillez prendre note qu’exceptionnellement, pour les 29 et 30 janvier 2026, seuls les rabais de 10 % et 15 % est disponible pour ce spectacle."}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/11/Francis20Legendre_-Of9PWXfbsdVzyxMORyD_.jpg"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/francis-legendre-rodage-pousser-drette/"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/francis-legendre-rodage-pousser-drette/#WebPage"}],"http://schema.org/name":[{"@language":"fr","@value":"Francis Legendre\nRodage – Pousser drette"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-29T20:00:00-05:00"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_francis-legendre-rodage-pousser-drette#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-30T20:00:00-05:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culture3r-com_francis-legendre-rodage-pousser-drette#2026-01-29T200000-0500"},{"@id":"http://kg.footlight.io/resource/culture3r-com_francis-legendre-rodage-pousser-drette#2026-01-30T200000-0500"}]},{"@id":"https://www.culture3r.com/evenements/francis-legendre-rodage-pousser-drette/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-28T23:23:58.272-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/francis-legendre-rodage-pousser-drette/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_francis-legendre-rodage-pousser-drette#Offer_0","http://schema.org/price":[{"@value":"28.00"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culture3r-com_francis-legendre-rodage-pousser-drette#2026-01-29T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Francis Legendre\nRodage – Pousser drette"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-29T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_francis-legendre-rodage-pousser-drette#2026-01-30T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Francis Legendre\nRodage – Pousser drette"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-30T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jay-kutcher-king-of-the-bar","@type":["http://schema.org/Event"],"http://schema.org/description":[{"@language":"fr","@value":"Jay Kutcher débarque avec un new country qui frappe fort. Son spectacle King of the Bar est un véritable party digne des bars de Nashville, où il enchaîne les chansons de son premier album, des inédits de son prochain et des covers de classiques country qui font chanter la foule à l’unisson. Avec un album prévu pour 2026, Jay est prêt à s’imposer comme l’une des figures incontournables du country canadien. "}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-26T20:00:00-05:00"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/jay-kutcher-king-of-the-bar/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/jay-kutcher-king-of-the-bar/"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/11/Jay20Kutcher_-Of4vOd9JV7EcX1zJEqX_.jpg"}],"http://schema.org/name":[{"@language":"fr","@value":"Jay Kutcher\nKing of the Bar"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_jay-kutcher-king-of-the-bar#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_jay-kutcher-king-of-the-bar#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_jay-kutcher-king-of-the-bar#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_jay-kutcher-king-of-the-bar#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/jay-kutcher-king-of-the-bar/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:25:45.674-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/jay-kutcher-king-of-the-bar/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jay-kutcher-king-of-the-bar#Offer_0","http://schema.org/price":[{"@value":"40.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jay-kutcher-king-of-the-bar#Offer_1","http://schema.org/price":[{"@value":"36.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jay-kutcher-king-of-the-bar#Offer_2","http://schema.org/price":[{"@value":"34.75"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jay-kutcher-king-of-the-bar#Offer_3","http://schema.org/price":[{"@value":"33.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_les-ptites-vues-special-films-dici-3","@type":["http://schema.org/Event"],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Les P’tites vues – Spécial films d’ici"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/les-ptites-vues-special-films-dici-3/"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/09/Les20P27tites20vues20-20SpC3A9cial20films20d27ici_-OZpFmr46mz1d-5N5eB__.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-20T19:30:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/les-ptites-vues-special-films-dici-3/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/7826846d-0f28-44b4-9bc9-8b61b2536b81"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/22e76d88-593d-4e82-9c46-50cba5c009ed"}],"http://schema.org/description":[{"@language":"fr","@value":"16 ans et + | 90 minutes Plongez dans l’ambiance feutrée d’une salle type cabaret pour une soirée de découvertes cinématographiques locales! Une sélection de films originaux vous fera voyager à travers des récits touchants, percutants ou surprenants, tous signés par des cinéastes et vidéastes de Trois-Rivières et de la Mauricie. Un véritable panorama de la créativité régionale, où chaque projection vous ouvre une fenêtre sur l’imaginaire, les enjeux et les sensibilités des artistes d’ici. "}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_les-ptites-vues-special-films-dici-3#Offer_0"}]},{"@id":"https://www.culture3r.com/evenements/les-ptites-vues-special-films-dici-3/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:25:55.218-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/les-ptites-vues-special-films-dici-3/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_les-ptites-vues-special-films-dici-3#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"10.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_richardson-zephir-punch-creole","http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/richardson-zephir-punch-creole/#WebPage"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-27T20:00:00-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/richardson-zephir-punch-creole/"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-473"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/02/Richardson20ZC3A9phir_-OJ07omYAT1ikhWlAZZJ_.jpg"}],"http://schema.org/name":[{"@language":"fr","@value":"Richardson Zéphir\nPunch Créole"}],"http://schema.org/description":[{"@language":"fr","@value":"Punch créole, le 2e one-man-show de l’humoriste Richardson Zéphir, n’est pas qu’un simple spectacle : c’est une célébration où la rigolade, la musique et l’improvisation se rencontrent dans une ambiance aussi chaleureuse que festive. Avec son charisme naturel et sa personnalité attachante, Richardson se déploie sur scène avec ses mimiques, son jeu physique éclatant et ses changements de voix qui donnent à ses anecdotes savoureuses une couleur unique et hilarante. Ce spectacle vivant et authentique, à l’image de Richardson, charmera autant les amateurs de stand-up que les fans d’improvisation et les téléspectateurs qui l’ont adoré dans LOL : Qui rira le dernier? ou Big Brother Célébrités. Laissez-vous emporter par l’énergie débordante de Richardson Zéphir et ses blagues livrées comme un bon punch créole, qui vous laisse le sourire aux lèvres et le cœur léger. Punch créole : des saveurs d’impro aux arômes de stand-up pur!"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_richardson-zephir-punch-creole#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_richardson-zephir-punch-creole#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_richardson-zephir-punch-creole#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_richardson-zephir-punch-creole#Offer_3"},{"@id":"http://kg.footlight.io/resource/culture3r-com_richardson-zephir-punch-creole#Offer_4"},{"@id":"http://kg.footlight.io/resource/culture3r-com_richardson-zephir-punch-creole#Offer_5"},{"@id":"http://kg.footlight.io/resource/culture3r-com_richardson-zephir-punch-creole#Offer_6"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-07T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culture3r-com_richardson-zephir-punch-creole#2026-02-27T200000-0500"},{"@id":"http://kg.footlight.io/resource/culture3r-com_richardson-zephir-punch-creole#2026-08-07T200000-0400"}]},{"@id":"https://www.culture3r.com/evenements/richardson-zephir-punch-creole/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:26:06.015-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/richardson-zephir-punch-creole/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_richardson-zephir-punch-creole#Offer_0","http://schema.org/price":[{"@value":"37.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_richardson-zephir-punch-creole#Offer_1","http://schema.org/price":[{"@value":"33.80"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_richardson-zephir-punch-creole#Offer_2","http://schema.org/price":[{"@value":"32.20"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_richardson-zephir-punch-creole#Offer_3","http://schema.org/price":[{"@value":"30.60"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_richardson-zephir-punch-creole#Offer_4","http://schema.org/price":[{"@value":"39.00"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culture3r-com_richardson-zephir-punch-creole#Offer_5","http://schema.org/price":[{"@value":"35.60"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culture3r-com_richardson-zephir-punch-creole#Offer_6","http://schema.org/price":[{"@value":"33.90"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culture3r-com_richardson-zephir-punch-creole#2026-02-27T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Richardson Zéphir\nPunch Créole"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-27T20:00:00-05:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_richardson-zephir-punch-creole#2026-08-07T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Richardson Zéphir\nPunch Créole"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-07T20:00:00-04:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_olivier-martineau-cest-pas-moi-cest-toi","http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-237"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-27T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/olivier-martineau-cest-pas-moi-cest-toi/#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/04/Olivier20Martineau_-OMwB2aXg8VSlszd5b4L_.jpg"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/olivier-martineau-cest-pas-moi-cest-toi/"}],"http://schema.org/name":[{"@language":"fr","@value":"Olivier Martineau\nC’est pas moi, c’est toi"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/description":[{"@language":"fr","@value":"Société, c’est fini. Oui, on a eu de beaux moments, mais là, c’est trop. T’as changé. Moi, je m’en vais. Je te laisse tes règles farfelues et tes promesses en plastique. J’en ai assez de cette mascarade. C’EST PAS MOI, C’EST TOI, c’est du stand-up pur et dur, un véritable feu roulant de gags où chaque punch frappe en plein cœur. Un regard sans filtre, mordant, mais surtout hilarant, sur une société où la rupture était inévitable. Après trois spectacles, celui-ci est le plus audacieux, le plus osé, le plus authentique. Bref, c’est la fin d’une histoire… et le début d’un show qui décoiffe. Mais moi, je m’en fous, j’ai pas de cheveux. "}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_olivier-martineau-cest-pas-moi-cest-toi#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_olivier-martineau-cest-pas-moi-cest-toi#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_olivier-martineau-cest-pas-moi-cest-toi#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_olivier-martineau-cest-pas-moi-cest-toi#Offer_3"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-20T20:00:00-05:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culture3r-com_olivier-martineau-cest-pas-moi-cest-toi#2026-03-27T200000-0400"},{"@id":"http://kg.footlight.io/resource/culture3r-com_olivier-martineau-cest-pas-moi-cest-toi#2026-11-20T200000-0500"}]},{"@id":"https://www.culture3r.com/evenements/olivier-martineau-cest-pas-moi-cest-toi/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:26:18.020-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/olivier-martineau-cest-pas-moi-cest-toi/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_olivier-martineau-cest-pas-moi-cest-toi#Offer_0","http://schema.org/price":[{"@value":"45.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_olivier-martineau-cest-pas-moi-cest-toi#Offer_1","http://schema.org/price":[{"@value":"41.20"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_olivier-martineau-cest-pas-moi-cest-toi#Offer_2","http://schema.org/price":[{"@value":"39.30"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_olivier-martineau-cest-pas-moi-cest-toi#Offer_3","http://schema.org/price":[{"@value":"37.40"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_olivier-martineau-cest-pas-moi-cest-toi#2026-03-27T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Olivier Martineau\nC’est pas moi, c’est toi"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-27T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_olivier-martineau-cest-pas-moi-cest-toi#2026-11-20T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Olivier Martineau\nC’est pas moi, c’est toi"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-20T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_megan-brouillard-chiendent","http://schema.org/name":[{"@language":"fr","@value":"Megan Brouillard Chiendent"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/a2348b7e-8edb-4784-8c39-9372a9552a28"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2023/03/show_-NRDuDiBCrX5wpmnlI0n_.jpg"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/megan-brouillard-chiendent/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/megan-brouillard-chiendent/"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-16T20:00:00-04:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/description":[{"@language":"fr","@value":"Le chiendent, c’est subjectif. La mauvaise herbe, c’est quelque chose qui pousse là où toi, tu ne voulais pas. Ce qui est du chiendent pour l’un ne l’est peut-être pas pour l’autre et ce qui est du chiendent quelque part, ne l’est pas ailleurs… Mon spectacle tourne autour de cette perspective, des différentes perceptions de chacun et chacune et autour de mes propres perceptions. Le premier one-woman-show de Mégan Brouillard est un spectacle mordant et authentique, où Mégan présente elle et les siens, sans filtre et toujours de façon hilarante! "}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_megan-brouillard-chiendent#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_megan-brouillard-chiendent#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_megan-brouillard-chiendent#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_megan-brouillard-chiendent#Offer_3"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-15T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culture3r-com_megan-brouillard-chiendent#2026-04-16T200000-0400"},{"@id":"http://kg.footlight.io/resource/culture3r-com_megan-brouillard-chiendent#2026-10-15T200000-0400"}]},{"@id":"http://kg.footlight.io/resource/a2348b7e-8edb-4784-8c39-9372a9552a28","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Megan Brouillard"}],"http://schema.org/hasOccupation":[{"@value":"Humoriste"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}]},{"@id":"https://www.culture3r.com/evenements/megan-brouillard-chiendent/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:27:10.241-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/megan-brouillard-chiendent/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_megan-brouillard-chiendent#Offer_0","http://schema.org/price":[{"@value":"42.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_megan-brouillard-chiendent#Offer_1","http://schema.org/price":[{"@value":"38.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_megan-brouillard-chiendent#Offer_2","http://schema.org/price":[{"@value":"36.75"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_megan-brouillard-chiendent#Offer_3","http://schema.org/price":[{"@value":"35.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_megan-brouillard-chiendent#2026-04-16T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Megan Brouillard Chiendent"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-16T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_megan-brouillard-chiendent#2026-10-15T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Megan Brouillard Chiendent"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-15T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_noir-silence-on-jase","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Noir Silence\n« On jase… »"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/noir-silence-on-jase/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/noir-silence-on-jase/"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/02/Noir20Silence_-OK710feZ6f7-gK08Qbr_.jpg"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/description":[{"@language":"fr","@value":"En novembre 1996, Noir Silence marque l’histoire en remportant trois Félix, propulsé par le succès monumental « On jase de toi », aujourd’hui classé parmi les grands classiques de la SOCAN. Ce titre emblématique a lancé une carrière ponctuée de succès radios inoubliables : « Malade », « Made in USA », « Le vent a tourné », « Méo Penché », et « T’es tellement belle », pour n’en nommer que quelques-uns. Toujours aussi unis et portés par leur formation originale, les cinq membres de Noir Silence reviennent sur scène avec toute l’énergie et la passion qui ont fait leur renommée. Ce spectacle, à la fois rétrospectif et vibrant, célèbre une carrière riche et marquante, tout en rassemblant des générations autour de leur rock authentique et intemporel. "}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-26T20:00:00-04:00"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_noir-silence-on-jase#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_noir-silence-on-jase#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_noir-silence-on-jase#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_noir-silence-on-jase#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/noir-silence-on-jase/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:27:18.979-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/noir-silence-on-jase/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_noir-silence-on-jase#Offer_0","http://schema.org/price":[{"@value":"49.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_noir-silence-on-jase#Offer_1","http://schema.org/price":[{"@value":"44.80"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_noir-silence-on-jase#Offer_2","http://schema.org/price":[{"@value":"42.70"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_noir-silence-on-jase#Offer_3","http://schema.org/price":[{"@value":"40.60"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_william-cloutier-l-amour","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"William Cloutier\nL’ Amour"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/09/William20Cloutier_-OZK35fy9cFILov0TIyR_.jpg"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K2-2050"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2027-01-30T20:00:00-05:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/description":[{"@language":"fr","@value":"Entre la folle aventure Mixmania et sa performance mémorable dans Starmania, 10 ans plus tard, ce qui est sûr, c’est que le gagnant de Star Académie 2021 a su s’imposer dans le paysage de la musique québécoise. En formule trio – accompagné d’Antoine Tousignant à la guitare et de Marie-Anne Tessier au drum – il interprète ses chansons avec une énergie brute et une proximité désarmante. Des plus grands succès de son album On ira (plus de 5 millions d’écoutes) aux titres plus récents de son opus L’amour (parution en mars 2025), William livre un spectacle vibrant, porté par une voix exceptionnelle. Une formule épurée, mais chargée d’émotion, où chaque chanson devient un moment de partage sincère. "}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/william-cloutier-l-amour/"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/william-cloutier-l-amour/#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_william-cloutier-l-amour#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_william-cloutier-l-amour#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_william-cloutier-l-amour#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_william-cloutier-l-amour#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/william-cloutier-l-amour/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:27:27.889-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/william-cloutier-l-amour/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_william-cloutier-l-amour#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"45.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_william-cloutier-l-amour#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"41.20"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_william-cloutier-l-amour#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"39.30"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_william-cloutier-l-amour#Offer_3","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"37.40"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_julyan-2","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/02/Julyan_-OK6zeAJPm9uPDOVpe2F_.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-27T20:00:00-05:00"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K13-114"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/julyan-2/"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/julyan-2/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/name":[{"@language":"fr","@value":"Julyan"}],"http://schema.org/description":[{"@language":"fr","@value":"Plongez dans l’univers envoûtant de Julyan, artiste charismatique et maestro du refrain accrocheur. L’auteur-compositeur vous présente son premier long-jeu, Future Lovers, son projet le plus personnel et authentique à ce jour. Sur scène, retrouvez quatre musicien·nes et choristes talentueux·ses, qui vous immergeront dans un captivant voyage musical, où la proéminence d’harmonies vocales s’agence parfaitement aux mélodies intemporelles de Julyan. Cette formation exceptionnelle propose un spectacle autant intimiste que grandiose. Laissez-vous emporter par ce son indie-pop aux accents folk, qui saura faire vibrer vos corps et stimuler vos esprits. "}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_julyan-2#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_julyan-2#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_julyan-2#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_julyan-2#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/julyan-2/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:27:36.634-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/julyan-2/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_julyan-2#Offer_0","http://schema.org/price":[{"@value":"28.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_julyan-2#Offer_1","http://schema.org/price":[{"@value":"25.70"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_julyan-2#Offer_2","http://schema.org/price":[{"@value":"24.55"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_julyan-2#Offer_3","http://schema.org/price":[{"@value":"23.40"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_ouellet-rodage","http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/ouellet-rodage/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/ouellet-rodage/"}],"http://schema.org/name":[{"@language":"fr","@value":"Ouellet\nRodage"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-12T20:00:00-04:00"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/12/Ouellet_-Og8WPeIMZqpIgysEhiW_.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"Vous l’avez peut-être vu au ComédiHa! Fest, à Juste pour rire ou en première partie de Jean-Michel Anctil… Mais cette fois, Ouellet débarque avec SON show, 100 % inédit et pleinement assumé. Gaspésien fier et barbu attachant, il incarne cette génération d’humoristes qui allient observations sociales aiguisées et efficacité comique redoutable. Révélation du ComédiHa! Fest 2023, il ne recule devant aucun sujet et n’épargne personne — surtout pas ceux qu’il aime! Ouellet peaufine en ce moment même son prochain one-man-show. Un rodage cru, drôle, mordant et surtout authentique, livré avec toute la fougue d’un gars de chez nous. C’est franc, c’est drôle, et ça frappe juste. Venez voir Ouellet avant que tout le monde en parle. Membres Accès Plus: Veuillez prendre note qu’exceptionnellement seul le rabais de 10 % et 15 % est disponible pour ce spectacle."}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_ouellet-rodage#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_ouellet-rodage#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_ouellet-rodage#Offer_2"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-13T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culture3r-com_ouellet-rodage#2026-03-12T200000-0400"},{"@id":"http://kg.footlight.io/resource/culture3r-com_ouellet-rodage#2026-03-13T200000-0400"}]},{"@id":"https://www.culture3r.com/evenements/ouellet-rodage/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:27:45.240-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/ouellet-rodage/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_ouellet-rodage#Offer_0","http://schema.org/price":[{"@value":"35.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_ouellet-rodage#Offer_1","http://schema.org/price":[{"@value":"32.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_ouellet-rodage#Offer_2","http://schema.org/price":[{"@value":"30.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_ouellet-rodage#2026-03-12T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Ouellet\nRodage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-12T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_ouellet-rodage#2026-03-13T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Ouellet\nRodage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-13T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_le-crime-de-lorient-express-la-comedie-humaine","@type":["http://schema.org/Event"],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-19T19:00:00-04:00"}],"http://schema.org/name":[{"@language":"fr","@value":"Le Crime de l’Orient-Express\nLa Comédie humaine"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/le-crime-de-lorient-express-la-comedie-humaine/#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"Comédie policière d’AGATHA CHRISTIE Plongez dans l’univers fascinant de la Reine du crime, Agatha Christie, avec l’une des enquêtes les plus emblématiques du légendaire Hercule Poirot ! \n Je vous propose de redécouvrir un classique de l’autrice Agatha Christie revisité par La Comédie humaine, compagnie dont la réputation n’est plus à faire. Cette comédie policière au suspense habilement mené vous tiendra en haleine jusqu’à la toute fin. Ouvrez l’œil : chaque détail compte dans cette chasse aux indices captivante! - Lisa Dugré, programmatrice aux arts de la scène. Entre suspense haletant et touches d’humour, Le Crime de l’Orient-Express vous embarque dans un voyage à grande vitesse où chaque passager cache un secret. Préparez-vous à une représentation captivante, où chaque regard, chaque détail, vous rapproche un peu plus de la vérité. Vivez l’intensité d’un huis clos palpitant, où le mystère s’épaissit à chaque instant. Entre rebondissements et révélations inattendues, ce chef-d’œuvre incontournable d’Agatha Christie vous tiendra en haleine jusqu’au dénouement final. Votre expérience de spectacle sera enrichie d’une médiation pour ouvrir le dialogue sur l’œuvre proposée : une présentation de la pièce en amont ainsi qu'une discussion de 15 minutes après la représentation. Ces moments d'échanges vous inviteront à tisser des liens, partager vos impressions et nourrir une réflexion collective. La culture devient alors un vecteur de rencontres et de changement. Auteur : Agatha Christie Adaptation : Ken Ludwig Traduction et mise en scène : Martin Lavigne Produit par : La Comédie Humaine Conception éclairages, vidéo scénographie : Les studios XF-40 (David Lavallée-Gagné) : Conception musique originale bande sonore : Medhat Hanbali Assistante à la mise en scène, directrice technique, régisseuse et manipulation du son : Mélanie Bouffard Crédit photo : Hugo B. Lefort Graphisme : Jérémie St-Cyr \n © 2016 Agatha Christie Limited \u0026amp; Ken Ludwig, adapté de Murder on The Orient-Express © 1934 Agatha Christie Limited. Tous droits réservés. ®MURDER ON THE ORIENT EXPRESS, AGATHA CHRISTIE et POIROT sont des marques déposées d'Agatha Christie Limited au Royaume-Uni et ailleurs. Tous droits réservés. \n Distribution: sujette à changement sans préavis"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/04/Le20Crime20de20l27Orient-Express_-ON_I5v72QH-aAvf_aqg_.jpg"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/le-crime-de-lorient-express-la-comedie-humaine/"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_le-crime-de-lorient-express-la-comedie-humaine#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_le-crime-de-lorient-express-la-comedie-humaine#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_le-crime-de-lorient-express-la-comedie-humaine#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_le-crime-de-lorient-express-la-comedie-humaine#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/le-crime-de-lorient-express-la-comedie-humaine/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:26:39.713-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/le-crime-de-lorient-express-la-comedie-humaine/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_le-crime-de-lorient-express-la-comedie-humaine#Offer_0","http://schema.org/price":[{"@value":"50.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_le-crime-de-lorient-express-la-comedie-humaine#Offer_1","http://schema.org/price":[{"@value":"45.70"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_le-crime-de-lorient-express-la-comedie-humaine#Offer_2","http://schema.org/price":[{"@value":"43.55"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_le-crime-de-lorient-express-la-comedie-humaine#Offer_3","http://schema.org/price":[{"@value":"41.40"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_charles-brunet-tres-tres-bon-show","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/06/Charles20Brunet_-OSZcyc14PZ7dXL8QqMi_.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-12T20:00:00-04:00"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/d352e5b3-a9b6-4d36-bf0e-0af5614b3e13"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/charles-brunet-tres-tres-bon-show/"}],"http://schema.org/name":[{"@language":"fr","@value":"Charles Brunet\nTrès très bon show"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/charles-brunet-tres-tres-bon-show/#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"J’ai 24 ans. J’suis extra vieux. Vraiment full mature pis profond. J’comprends un peu rien à la vie mais j’commence à comprendre mon métier. Le nom le dit, c’est un très très bon show. Ça parle d’un peu de tout. Ça parle à tout le monde. Vieux, jeune. Tiktok, Msn. Gauche, Droite. Bonjour, hi. C’est surtout fucking drôle. C’est l’œuvre dont je suis le plus fier de ma jeune carrière. Merci aux gens qui me permettent de continuer à vivre mon rêve de p’tit cul. Je vous le devais et le voici, un très très bon show. Première partie :Érickson Alisme"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_charles-brunet-tres-tres-bon-show#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_charles-brunet-tres-tres-bon-show#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_charles-brunet-tres-tres-bon-show#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_charles-brunet-tres-tres-bon-show#Offer_3"}]},{"@id":"http://kg.footlight.io/resource/d352e5b3-a9b6-4d36-bf0e-0af5614b3e13","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Charles Brunet"}],"http://schema.org/hasOccupation":[{"@value":"Humoriste"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}]},{"@id":"https://www.culture3r.com/evenements/charles-brunet-tres-tres-bon-show/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:26:50.600-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/charles-brunet-tres-tres-bon-show/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_charles-brunet-tres-tres-bon-show#Offer_0","http://schema.org/price":[{"@value":"43.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_charles-brunet-tres-tres-bon-show#Offer_1","http://schema.org/price":[{"@value":"39.40"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_charles-brunet-tres-tres-bon-show#Offer_2","http://schema.org/price":[{"@value":"37.60"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_charles-brunet-tres-tres-bon-show#Offer_3","http://schema.org/price":[{"@value":"35.80"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jessica-chartrand-rodage-nouvelle-vieille","@type":["http://schema.org/Event"],"http://schema.org/description":[{"@language":"fr","@value":"Je suis très fière de vous présenter le rodage de mon tout premier spectacle : Nouvelle vieille. Dans ma vie, je me suis souvent retrouvée face à une nouvelle moi, un nouveau départ, une nouvelle vie. On dirait que je suis souvent nouvelle dans quelque chose de vieux. Toujours un peu la dernière à embarquer dans le nouveau projet parce que j’aime étirer la sauce de l’ancien. J’arrive souvent en retard dans ce qui est cool, mais pour être franche avec vous, je pense que c’est parfait de même. Je prends le temps de me tanner, de suranalyser ce que je n’aime pas, ce qui me gosse, et je pense que ça fait des bonnes jokes! Après un parcours scolaire complètement flou, un coming out évident pour tout le monde sauf moi, et une accumulation tumultueuse de jobs angoissantes, à 30 ans j’abandonne tout et je repars à zéro. Nouvelle vieille, c’est un peu moi qui commence – en retard et déjà vieille – la job de mes rêves : faire du stand-up. Dépêchez-vous de venir me voir, par contre. Parce que même si j’adore mon nouveau métier, personne n’est à l’abri qu’à 40, je lâche tout pour me lancer dans un élevage massif de pinsons. Membres Accès Plus: Veuillez prendre note qu’exceptionnellement seul le rabais de 10 % et 15 % est disponible pour ce spectacle."}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/jessica-chartrand-rodage-nouvelle-vieille/"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/06/Jessica20Chartrand_-OS-QDJwqojd6_dzpl_Q_.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-02T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/jessica-chartrand-rodage-nouvelle-vieille/#WebPage"}],"http://schema.org/name":[{"@language":"fr","@value":"Jessica Chartrand\nRodage – Nouvelle vieille"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_jessica-chartrand-rodage-nouvelle-vieille#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_jessica-chartrand-rodage-nouvelle-vieille#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_jessica-chartrand-rodage-nouvelle-vieille#Offer_2"}]},{"@id":"https://www.culture3r.com/evenements/jessica-chartrand-rodage-nouvelle-vieille/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:27:54.771-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/jessica-chartrand-rodage-nouvelle-vieille/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jessica-chartrand-rodage-nouvelle-vieille#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"27.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jessica-chartrand-rodage-nouvelle-vieille#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"24.80"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jessica-chartrand-rodage-nouvelle-vieille#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"23.70"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_dominic-paquet-jcomprends-la-game","http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/spec-qc-ca_artiste_dominic-paquet"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/dominic-paquet-jcomprends-la-game/#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/description":[{"@language":"fr","@value":"Après avoir vendu plus de 250 000 billets de son dernier spectacle, le populaire humoriste Dominic Paquet est de retour avec un tout nouveau one-man-show : J’comprends la game. Le 5e spectacle solo de Dominic se veut un gros clin d’œil à ces fameuses personnes qui, grâce à leur expérience de vie, sont certaines de comprendre des choses qui sont inaccessibles pour le commun des mortels… J’comprends la game est un laboratoire humoristique où la folie prend tout son sens alors que Dominic explore, examine et met en évidence toutes sortes de théories farfelues afin de donner des réponses à des questionnements que personne n’a jamais eus! Encore une fois, Dominic Paquet fait rire aux éclats, surprend et séduit par sa capacité à incarner des personnages uniques et à créer des expressions qui marqueront votre imaginaire! Le public veut des réponses, donc moi je suis là pour nourrir les esprits. Quand le spectacle se termine, je sens que les gens quittent la salle en se disant « maintenant moi aussi j’comprends la game! ». – Dominic Paquet Le spectacle est présenté par Alimentation Mon Quartier, qui vous invite à vivre une expérience unique, signée par les artisans et producteurs alimentaires d’ici."}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/dominic-paquet-jcomprends-la-game/"}],"http://schema.org/name":[{"@language":"fr","@value":"Dominic Paquet\nJ’comprends la game"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-12T20:00:00-04:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/05/Dominic20Paquet_-OQxZv5Wa4f0C27Lci4A_.jpg"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_dominic-paquet-jcomprends-la-game#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_dominic-paquet-jcomprends-la-game#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_dominic-paquet-jcomprends-la-game#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_dominic-paquet-jcomprends-la-game#Offer_3"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-17T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culture3r-com_dominic-paquet-jcomprends-la-game#2026-03-12T200000-0400"},{"@id":"http://kg.footlight.io/resource/culture3r-com_dominic-paquet-jcomprends-la-game#2026-03-13T200000-0400"},{"@id":"http://kg.footlight.io/resource/culture3r-com_dominic-paquet-jcomprends-la-game#2026-03-21T200000-0400"},{"@id":"http://kg.footlight.io/resource/culture3r-com_dominic-paquet-jcomprends-la-game#2026-03-21T150000-0400"},{"@id":"http://kg.footlight.io/resource/culture3r-com_dominic-paquet-jcomprends-la-game#2026-10-17T200000-0400"}]},{"@id":"http://kg.footlight.io/resource/spec-qc-ca_artiste_dominic-paquet","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@value":"Dominic Paquet"}],"http://schema.org/image":[{"@value":"https://spec.qc.ca/uploads/artistes/dominic-paquet-2.jpg"}],"http://schema.org/sameAs":[{"@value":"http://kg.artsdata.ca/resource/K12-258"}],"http://schema.org/description":[{"@language":"fr","@value":"Dominic Paquet obtient son diplôme de l'École Nationale de l'humour en 1998. Aux Galas des Olivier 2006 et 2007, il est nominé dans plusieurs catégories : Révélation de l'année, Spectacle d'humour de l'année, Auteur de l'année, Metteur en scène de l'année et Jeu et performance de l'année. En plus de faire de la scène, Dominic Paquet est également connu des milieux de la radio et de la télévision. Il a coanimé plusieurs émissions sur les ondes de CKOI et de Énergie, avec ses collègues Patrick Groulx et Dominic et Martin. À la télévision, il a participé à bon nombre d'émissions de variété qui l'ont fait connaître du grand public. C'est en 2010 qu'il sort sont premier one man show. Cinq ans plus tard, il est de retour avec un troisième spectacle intitulé «Rien qu's'une gosse», pour lequel il vend plus de 40 000 billets en trois mois."}]},{"@id":"https://www.culture3r.com/evenements/dominic-paquet-jcomprends-la-game/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:27:00.055-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/dominic-paquet-jcomprends-la-game/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_dominic-paquet-jcomprends-la-game#Offer_0","http://schema.org/price":[{"@value":"54.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_dominic-paquet-jcomprends-la-game#Offer_1","http://schema.org/price":[{"@value":"49.75"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_dominic-paquet-jcomprends-la-game#Offer_2","http://schema.org/price":[{"@value":"47.38"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_dominic-paquet-jcomprends-la-game#Offer_3","http://schema.org/price":[{"@value":"45.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_dominic-paquet-jcomprends-la-game#2026-03-12T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Dominic Paquet\nJ’comprends la game"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-12T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_dominic-paquet-jcomprends-la-game#2026-03-13T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Dominic Paquet\nJ’comprends la game"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-13T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_dominic-paquet-jcomprends-la-game#2026-03-21T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Dominic Paquet\nJ’comprends la game"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-21T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_dominic-paquet-jcomprends-la-game#2026-03-21T150000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Dominic Paquet\nJ’comprends la game"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-21T15:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_dominic-paquet-jcomprends-la-game#2026-10-17T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Dominic Paquet\nJ’comprends la game"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-17T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_david-corriveau-100-contrefacons","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K2-1664"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/description":[{"@language":"fr","@value":"Imitateur aux mille voix et aux mille talents ! Découvert sur le plateau de Salut Bonjour! en 2021 où il a impressionné Gino Chouinard et le Québec tout entier, David s’envole ensuite pour La France a un incroyable talent où il décroche le golden buzzer de Sugar Sammy et se rend en finale. Depuis, il cumule plus de 35 millions de vues sur les réseaux sociaux et il a le vent dans les voiles grâce à son premier spectacle 100 contrefaçons avec lequel il plonge dans un répertoire des chansons populaires des années 1970 à 2000. C’est dans une ambiance intime, chaleureuse et feel good qu’il nous raconte comment il a réussi à passer du poulailler familial de Saint-Boniface-de-Shawinigan à la Place des Arts de Montréal pour réaliser son rêve de chanter sur scène comme ses idoles, qu’il imite de façon spectaculaire ! Une aventure possible grâce à vous et qui ne fait que commencer… 100 contrefaçons, les prouesses d’un imitateur à l’énergie contagieuse qui prend un véritable plaisir à nous faire revivre des moments pas si lointains que l’on chérit encore. Vous fermez les yeux et vous croyez entendre Céline Dion, Édith Piaf, Claude Dubois, Laurence Jalbert, Marie Carmen, James Blunt ou Ginette Reno, mais en les ouvrant, incrédules, vous avez un seul homme devant vous. "}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/9475525c-10fa-4a53-b89b-8c7d9ba68726"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/david-corriveau-100-contrefacons/"}],"http://schema.org/name":[{"@language":"fr","@value":"David Corriveau\n100 contrefaçons"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2024/03/David20Corriveau_-NtGnrqGDQRP_SIFQcxo_.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/david-corriveau-100-contrefacons/#WebPage"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-25T19:00:00-05:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_david-corriveau-100-contrefacons#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_david-corriveau-100-contrefacons#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_david-corriveau-100-contrefacons#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_david-corriveau-100-contrefacons#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/david-corriveau-100-contrefacons/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:28:04.376-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/david-corriveau-100-contrefacons/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_david-corriveau-100-contrefacons#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"42.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_david-corriveau-100-contrefacons#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"38.50"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_david-corriveau-100-contrefacons#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"36.72"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_david-corriveau-100-contrefacons#Offer_3","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"35.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_back-to-the-80s-band-le-retour-des-annees-80","@type":["http://schema.org/Event"],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/back-to-the-80s-band-le-retour-des-annees-80/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/name":[{"@language":"fr","@value":"Back to the 80s Band\nLe Retour des années 80"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/back-to-the-80s-band-le-retour-des-annees-80/"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-03T20:00:00-04:00"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/05/Back20to20the2080s20Band_-OQJ6b7hAJn5lvppy8Ps_.jpg"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/description":[{"@language":"fr","@value":"UN RÉPERTOIRE DIGNE DE VOS ARTISTES ROCK PRÉFÉRÉS !! Back to the 80s Band présente 4 musiciens et 2 chanteurs d’expérience, qui se spécialisent ici au Rock des années 80’s. De Queen à Bon Jovi, en passant par Tina Turner, Heart et sans tous les nommer Back To The 80s Band revisite les tubes les plus emblématiques du rock de cette époque culte! La liste des chansons interprétées est très variée et saura vous satisfaire, que ce soit pour un évènement corporatif, sur une scène de festival ou encore pour une soirée plus intimiste. L’élément ultime qui peut sûrement les distinguer est leur duo vocal qui est exceptionnel. Puissante et chargée d’émotion, leur prestation nous ramène instantanément à une époque où chaque mélodie nous fige dans le temps. Soyez prêts à écouter les « cassettes » comme dans le temps et à frissonner au son de leurs voix qui fusionnent harmonieusement avec l’ensemble musical. En somme, Back To The 80s Band c’est une expérience immersive de haut niveau. Que vous ayez vécu votre jeunesse lors de ces années et/ou que vous souhaitez revivre cette époque légendaire, ils sont là pour vous offrir une soirée des plus mémorables et électrisantes. Accrochez-vous, car le Rock 80’s est de retour et plus que vivant. Membres Accès Plus: Veuillez prendre note qu’exceptionnellement les rabais progressifs ne sont pas applicables pour cette représentation. "}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_back-to-the-80s-band-le-retour-des-annees-80#Offer_0"}]},{"@id":"https://www.culture3r.com/evenements/back-to-the-80s-band-le-retour-des-annees-80/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:26:28.120-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/back-to-the-80s-band-le-retour-des-annees-80/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_back-to-the-80s-band-le-retour-des-annees-80#Offer_0","http://schema.org/price":[{"@value":"55.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_arnaud-soly-rodage","@type":["http://schema.org/Event"],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/spec-qc-ca_artiste_arnaud-soly"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/arnaud-soly-rodage/"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/name":[{"@language":"fr","@value":"Arnaud Soly\nBon Jus"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2024/10/Arnaud20Soly_-O9QoXGWi_IeojYuTyD3_.jpg"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/description":[{"@language":"fr","@value":"Arnaud Soly revient sur scène avec son deuxième one-man-show : BON JUS. Il s’agit de son meilleur à ce jour (selon lui!) Pourquoi ça s’appelle BON JUS? C’est bizarre, non? Ça serait trop compliqué à expliquer. Venez le découvrir en salle. Vous allez rigoler, c’est promis."}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-10T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/arnaud-soly-rodage/#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_arnaud-soly-rodage#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_arnaud-soly-rodage#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_arnaud-soly-rodage#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_arnaud-soly-rodage#Offer_3"}]},{"@id":"http://kg.footlight.io/resource/spec-qc-ca_artiste_arnaud-soly","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@value":"Arnaud Soly"}],"http://schema.org/image":[{"@value":"https://spec.qc.ca/uploads/artistes/arnaud-soly-2.jpg"}],"http://schema.org/sameAs":[{"@value":"http://kg.artsdata.ca/resource/K12-234"}],"http://schema.org/description":[{"@language":"fr","@value":"Improvisateur d’expérience, flutiste émérite, comédien et humoriste, Arnaud Soly est un véritable artiste multidisciplinaire. Connu par certains pour ses vidéos sur internet, il consacre la plus grande partie de son temps à l’humour scénique. En rodage pour son premier one-man show, on le retrouve sur plusieurs scènes aux 4 coins de la province où il peaufine son humour unique. Un artiste prometteur à découvrir!"}]},{"@id":"https://www.culture3r.com/evenements/arnaud-soly-rodage/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:28:23.447-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/arnaud-soly-rodage/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_arnaud-soly-rodage#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"47.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_arnaud-soly-rodage#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"43.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_arnaud-soly-rodage#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"41.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_arnaud-soly-rodage#Offer_3","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"39.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_po-forget","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K5-380"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/po-forget/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/po-forget/"}],"http://schema.org/name":[{"@language":"fr","@value":"P-O FORGET\nRodage"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-24T20:00:00-04:00"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/09/P-O20FORGET_-O_S82kAuqI1GIdF8VBF_.jpg"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/description":[{"@language":"fr","@value":"Après un premier spectacle acclamé pour l’originalité des angles et la précision des punchs, P-O Forget vous convie maintenant au rodage d’une œuvre encore plus éclatée, encore plus incisive et avec votre aide, encore plus hilarante. Venez découvrir ou retrouver cette star montante de l’humour Québécois dans sa version la plus authentique. "}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_po-forget#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_po-forget#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_po-forget#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_po-forget#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/po-forget/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:28:42.507-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/po-forget/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_po-forget#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"37.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_po-forget#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"33.80"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_po-forget#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"32.20"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_po-forget#Offer_3","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"30.60"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mathieu-cyr-brillamment-con","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/mathieu-cyr-brillamment-con/"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/1ace79d9-d268-439d-a198-f1feec79c6b3"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-09T20:00:00-04:00"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/09/Mathieu20Cyr_-OZiXZMwYcHTNwL-7LNr_.jpg"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/name":[{"@language":"fr","@value":"Mathieu Cyr\nBrillamment con"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/mathieu-cyr-brillamment-con/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/description":[{"@language":"fr","@value":"« Avec son humour décomplexé et explosif, Mathieu Cyr punch de manière hilarante sur nos travers humains, avec la délicatesse d’une main de ciment dans un gant de plomb. Hyperactif infatigable et déficient d’attention troublé, Mathieu Cyr aime avoir une opinion sur tout en n’étant spécialiste de rien. Comme il le dit si bien : « avoir la langue dans sa poche, ça sert juste à mouiller tes jeans…» Sur les réseaux sociaux, il est suivi par plus de 600 000 fans. Chez lui, il est suivi par trois enfants et deux chats. Il tient à ce qu’on écrive ça ici, pour qu’il se rappelle de ne pas oublier son ti-rouleau à poils avant de monter sur scène. Un show qui défoule et qui fait du bien. 90 minutes de gros fun!"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_mathieu-cyr-brillamment-con#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_mathieu-cyr-brillamment-con#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_mathieu-cyr-brillamment-con#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_mathieu-cyr-brillamment-con#Offer_3"}]},{"@id":"http://kg.footlight.io/resource/1ace79d9-d268-439d-a198-f1feec79c6b3","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Mathieu Cyr"}],"http://schema.org/hasOccupation":[{"@value":"Humoriste"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}],"http://schema.org/url":[{"@value":"https://mathieucyr.ca/"}]},{"@id":"https://www.culture3r.com/evenements/mathieu-cyr-brillamment-con/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:28:13.535-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/mathieu-cyr-brillamment-con/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mathieu-cyr-brillamment-con#Offer_0","http://schema.org/price":[{"@value":"35.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mathieu-cyr-brillamment-con#Offer_1","http://schema.org/price":[{"@value":"32.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mathieu-cyr-brillamment-con#Offer_2","http://schema.org/price":[{"@value":"30.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mathieu-cyr-brillamment-con#Offer_3","http://schema.org/price":[{"@value":"29.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_tai-tl-tai-tl-derange-le-quebec","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/05/Tai20TL_-OQKLtu-5T23DUNA6XE8_.jpg"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/tai-tl-tai-tl-derange-le-quebec/"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/name":[{"@language":"fr","@value":"Tai TL\nTai TL dérange le Québec"}],"http://schema.org/description":[{"@language":"fr","@value":"Le tout nouveau spectacle de l’humoriste TAI TL part en tournée partout au Québec. Dans ce one-man-show, TAI transposera sur scène sa fameuse émission Occupation Hood — qui réunissait, en pleine pandémie, plus de 100 000 personnes en direct, chaque dimanche sur Instagram Live — en plus d’y retrouver son inimitable personnage-phare de Jean-Robert, un type au complet beaucoup trop ample et aux lunettes sur le bout du nez, qui vivra de toutes nouvelles et improbables aventures ! "}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-10T20:00:00-04:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/tai-tl-tai-tl-derange-le-quebec/#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_tai-tl-tai-tl-derange-le-quebec#Offer_0"}]},{"@id":"https://www.culture3r.com/evenements/tai-tl-tai-tl-derange-le-quebec/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:28:32.305-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/tai-tl-tai-tl-derange-le-quebec/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_tai-tl-tai-tl-derange-le-quebec#Offer_0","http://schema.org/price":[{"@value":"42.00"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culture3r-com_pour-une-histoire-dun-soir","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/pour-une-histoire-dun-soir/"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/name":[{"@language":"fr","@value":"Pour une autre histoire d’un soir\nJoe Bocan, Marie Carmen et Marie Denise Pelletier"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/10/Pour20une20autre20histoire20d27un20soir_-Ob8LryGc36l_BFxDCSh_.jpg"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/pour-une-histoire-dun-soir/#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/description":[{"@language":"fr","@value":" Trois des plus belles voix du Québec, Joe Bocan, Marie Carmen et Marie Denise Pelletier, se retrouveront de nouveau ensemble sur scène, avec Pour une autre histoire d’un soir, une version revisitée du spectacle qui leur a valu le Félix du Spectacle de l’année Variétés/Réinterprétations en 2022 et plus de 50,000 spectateurs. Dans une mise en scène renouvelée de Michel Poirier, voici un spectacle qui n’aura rien perdu de son charme. Un hommage à l’auteur-compositeur-interprète Germain Gauthier, décédé en août dernier, s’inscrira parmi les nouveautés. Et rassurez-vous : la rumeur veut que L’aigle noir, Repartir à zéro et Tous les cris les S.O.S. soient de nouveau au programme ! Pour une autre histoire d’un soir est une occasion pour les fans de la première heure de revivre des moments inoubliables tout en découvrant quelques surprises. Pour les autres, l’occasion de comprendre pourquoi ce spectacle a récolté tant d’éloges de la part du public et des critiques. Pour une autre histoire d’un soir, la porte s’ouvre sur une nouvelle aventure où se reflètent le meilleur du passé et des découvertes de grande envergure. "}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-13T19:00:00-04:00"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_pour-une-histoire-dun-soir#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_pour-une-histoire-dun-soir#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_pour-une-histoire-dun-soir#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_pour-une-histoire-dun-soir#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/pour-une-histoire-dun-soir/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:29:36.716-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/pour-une-histoire-dun-soir/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_pour-une-histoire-dun-soir#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"59.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_pour-une-histoire-dun-soir#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"53.80"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_pour-une-histoire-dun-soir#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"51.20"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_pour-une-histoire-dun-soir#Offer_3","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"48.60"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_vins-de-france-cave-a-vin","@type":["http://schema.org/Event"],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Vins de France\nCave à vin"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/08/vins-de-france-cave-a-vin.jpg"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/vins-de-france-cave-a-vin/"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/7cfa3a3f-d283-4e28-9f09-2b0756ac2e27"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-27T19:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/vins-de-france-cave-a-vin/#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"Cette saison, les Caves à vin de Boréalis vous feront revivre la vieille Europe avec sa trilogie classique : Espagne, France et Italie! Nos cousins de la France ont toujours su se démarquer par la qualité de leurs vins. Venez en apprendre plus sur leur héritage vinicole, les différentes régions et les particularités qui leur sont propres. Profitez de l’occasion pour goûter quelques-unes des sélections de notre sommelier maison. Conférencier invité : Hugo Robillard Auger Bouchées: Le Complexe Laviolette INCLUS Cette activité comprend 3 services de vin et bouchées. IMPORTANT 18 ans et plus "}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_vins-de-france-cave-a-vin#Offer_0"}]},{"@id":"https://www.culture3r.com/evenements/vins-de-france-cave-a-vin/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:29:05.635-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/vins-de-france-cave-a-vin/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_vins-de-france-cave-a-vin#Offer_0","http://schema.org/price":[{"@value":"70.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_rencontre-dartiste-guylaine-guay","@type":["http://schema.org/Event"],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-28T13:00:00-04:00"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-453"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/1272f78a-b32d-481a-9143-df6737cf0580"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/rencontre-dartiste-guylaine-guay/#WebPage"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/rencontre-dartiste-guylaine-guay/"}],"http://schema.org/name":[{"@language":"fr","@value":"Rencontre d’artiste Guylaine Guay"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/09/Rencontre20d27artiste20Guylaine20Guay_-OZpJLyEC1U4s0-l9b-s_.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"Grands public | 60 minutes Plongez dans l’univers unique de Guylaine Guay, où s’entrelacent créativité, humour et humanité. À travers son exposition, Guylaine va partager son parcours, ses inspirations et sa démarche artistique marquée par l’inclusion, l’amour de la différence et la joie assumée. Une occasion privilégiée d’explorer sa vision du monde, colorée et sensible, et de découvrir les histoires derrière la création de sa toute première exposition. Un échange authentique et généreux, à l’image de ses œuvres. Note: Cette activité prend la forme d’une visite commentée à travers l’exposition, il n’y a donc pas de places assises. Voir l'exposition liée "}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_rencontre-dartiste-guylaine-guay#Offer_0"}]},{"@id":"https://www.culture3r.com/evenements/rencontre-dartiste-guylaine-guay/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:29:24.004-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/rencontre-dartiste-guylaine-guay/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_rencontre-dartiste-guylaine-guay#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culture3r-com_trip-de-pierre-yves-roy-desmarais","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/trip-de-pierre-yves-roy-desmarais/"}],"http://schema.org/name":[{"@language":"fr","@value":"TRIP DE PIERRE-YVES ROY-DESMARAIS"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-490"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/trip-de-pierre-yves-roy-desmarais/#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2cbd428a-4e42-4276-bfe1-de1d14d1ea98"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/10/photo-mev-as-5.png"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-05T20:30:00-04:00"}],"http://schema.org/description":[{"@language":"fr","@value":"Pierre-Yves Roy-Desmarais nous arrive avec son spectacle intégral Une année en un an, faire ce qu’il fait de mieux : nous faire rire jusqu’à avoir mal aux joues. Qui vient TRIPPER avec lui pour cette soirée du samedi 5 septembre 2026 à l’Amphithéâtre Cogeco? La toujours piquante Mégan Brouillard! Pour compléter ce trio explosif, Charles Pellerin débarque à TRIP avec son humour fin et décalé. Ce sera Une année en un an, 1 humoriste en 3 humoristes, 1 rivière en Trois-Rivières… Ça n’aura aucun sens et ce sera CRAMPANT! BILLETS BILLETS MOBILITÉ RÉDUITE "}]},{"@id":"https://www.culture3r.com/evenements/trip-de-pierre-yves-roy-desmarais/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:29:33.537-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/trip-de-pierre-yves-roy-desmarais/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jeremy-demay-naturel","http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/jeremy-demay-naturel/"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2026/01/JC3A9rC3A9my20Demay_-OiEdaLNWIs6nepNCaDt_.jpg"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/2f230580-69b1-472e-996a-d965696cb643"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/description":[{"@language":"fr","@value":"Jérémy Demay, le plus québécois des humoristes français, résidant au Québec depuis plus de 17 ans, est en tournée avec son spectacle intitulé Naturel. Avec ce 3 e one-man-show, et entre l’écriture de ses livres plusieurs fois bestseller (150 000 copies vendues), Jérémy s’amuse à aller dans des zones plus personnelles et étonnera assurément par un contenu délicieusement audacieux qu’on ne lui soupçonnait pas… Avec une franchise à la fois désarmante mais ô combien hilarante, Jérémy se dévoile comme jamais avec de nombreuses confessions sur lui, sa vie et sa famille en exploitant le côté comique de situations qu’il a vécues. Avec Naturel, on retrouve sur scène un Jérémy toujours aussi énergique, charismatique et vif d’esprit qui tisse dès le départ un lien NATUREL avec le public. Avec son talent unique, Jérémy Demay réussit à la fois à faire rire à gorge déployée et à toucher droit au cœur, tout ça pour le grand plaisir de tous! "}],"http://schema.org/name":[{"@language":"fr","@value":"Jérémy Demay\nNaturel"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/jeremy-demay-naturel/#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_jeremy-demay-naturel#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_jeremy-demay-naturel#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_jeremy-demay-naturel#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_jeremy-demay-naturel#Offer_3"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-10T20:00:00-05:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culture3r-com_jeremy-demay-naturel#2026-01-10T200000-0500"},{"@id":"http://kg.footlight.io/resource/culture3r-com_jeremy-demay-naturel#2026-01-10T150000-0500"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-10T15:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/2f230580-69b1-472e-996a-d965696cb643","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Jérémy Demay"}],"http://schema.org/hasOccupation":[{"@value":"Humoriste"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}],"http://schema.org/url":[{"@value":"https://jeremydemay.ca/"}],"http://schema.org/sameAs":[{"@value":"http://www.wikidata.org/entity/Q16802031"},{"@value":"https://www.facebook.com/JeremyDemayOfficiel/"}]},{"@id":"https://www.culture3r.com/evenements/jeremy-demay-naturel/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-08T23:30:00.847-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/jeremy-demay-naturel/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jeremy-demay-naturel#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"52.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jeremy-demay-naturel#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"47.50"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jeremy-demay-naturel#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"45.25"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jeremy-demay-naturel#Offer_3","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"43.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jeremy-demay-naturel#2026-01-10T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Jérémy Demay\nNaturel"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-10T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jeremy-demay-naturel#2026-01-10T150000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Jérémy Demay\nNaturel"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-10T15:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_borealim-improvisation-au-musee-2","@type":["http://schema.org/Event"],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"BoréaLIM\nImprovisation au musée"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/borealim-improvisation-au-musee-2/"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-17T20:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/borealim-improvisation-au-musee-2/#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a2fd3f07-b1f9-433b-97eb-e6c30386cd1c"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/7cfa3a3f-d283-4e28-9f09-2b0756ac2e27"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/08/borealim-improvisation-au-musee.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"Assistez à la création de saynètes inédites des plus divertissantes, alors que la Ligue d’Improvisation Mauricienne (LIM) sera de retour à Boréalis l’instant d’une soirée. Pour le plus grand plaisir de l’auditoire, les improvisateur·trice·s se laisseront inspirer par le bâtiment centenaire et ses nombreuses salles. Moments inusités, rires et folie seront au rendez-vous! La première partie aura lieu dans l’exposition Transformations et elle sera suivie d’un spectacle sur scène."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_borealim-improvisation-au-musee-2#Offer_0"}]},{"@id":"https://www.culture3r.com/evenements/borealim-improvisation-au-musee-2/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:28:56.388-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/borealim-improvisation-au-musee-2/"}]},{"@id":"http://kg.footlight.io/resource/a2fd3f07-b1f9-433b-97eb-e6c30386cd1c","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Activité de programmation"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_borealim-improvisation-au-musee-2#Offer_0","http://schema.org/price":[{"@value":"15.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_andre-philippe-gagnon-remasterise","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K13-15"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2024/12/AndrC3A9-Philippe20Gagnon_-ODI7E8Peafsu2NEsc_Y_.jpg"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/andre-philippe-gagnon-remasterise/"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/name":[{"@language":"fr","@value":"André-Philippe Gagnon\nRemasterisé"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/andre-philippe-gagnon-remasterise/#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/description":[{"@language":"fr","@value":"Dans ce nouveau spectacle, André-Philippe Gagnon continue de nous épater avec ses prouesses vocales, comme il le fait depuis 40 ans. En plus de 70 imitations virtuoses, il nous partage pour la première fois des anecdotes personnelles de son incroyable carrière, livrées avec humour et authenticité, pour nous faire découvrir l’homme derrière les milles voix. Il revisite les plus belles chansons des dernières décennies qui nous accompagnent dans nos vies. Un moment unique, haut en rire, en émotion et en musique ! "}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-15T15:00:00-04:00"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_andre-philippe-gagnon-remasterise#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_andre-philippe-gagnon-remasterise#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_andre-philippe-gagnon-remasterise#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_andre-philippe-gagnon-remasterise#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/andre-philippe-gagnon-remasterise/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:29:15.325-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/andre-philippe-gagnon-remasterise/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_andre-philippe-gagnon-remasterise#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"53.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_andre-philippe-gagnon-remasterise#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"48.40"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_andre-philippe-gagnon-remasterise#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"46.10"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_andre-philippe-gagnon-remasterise#Offer_3","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"43.80"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_quand-la-nature-prend-forme","@type":["http://schema.org/Event"],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/quand-la-nature-prend-forme/"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-31T09:30:00-04:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/name":[{"@language":"fr","@value":"Quand la nature prend forme"}],"http://schema.org/description":[{"@language":"fr","@value":"3 ans et + | 60 minutes À l’occasion de l’exposition La forêt des entre-mondes, plongez dans l’univers fantastique de l’artiste Myriam Lafae. En complément de cette découverte, prenez part à un atelier de création familial de sculptures textiles. Chacun pourra habiller une structure en broche, inspirée de la nature, à l’aide de tissus et d’objets recyclés. Une belle façon de mêler imagination, matière et engagement. Venez transformer la culture en nature! Activité dans le cadre de festival Petits bonheurs Les enfants doivent être accompagnés d’un adulte pour cette activité. Voir l'exposition liée "}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/quand-la-nature-prend-forme/#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/1272f78a-b32d-481a-9143-df6737cf0580"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-453"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/09/Quand20la20nature20prend20forme_-OZu5DO90mUf6-MlSv5k_.jpg"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_quand-la-nature-prend-forme#Offer_0"}]},{"@id":"https://www.culture3r.com/evenements/quand-la-nature-prend-forme/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:29:56.886-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/quand-la-nature-prend-forme/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_quand-la-nature-prend-forme#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culture3r-com_vin-ditalie-cave-a-vin","@type":["http://schema.org/Event"],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-10T19:00:00-04:00"}],"http://schema.org/name":[{"@language":"fr","@value":"Vins d’Italie\nCave à vin"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/10/vin-ditalie-oymhf-uasiabbnyhpvk.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/vin-ditalie-cave-a-vin/#WebPage"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/vin-ditalie-cave-a-vin/"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/7cfa3a3f-d283-4e28-9f09-2b0756ac2e27"}],"http://schema.org/description":[{"@language":"fr","@value":"Cette saison, les Caves à vin de Boréalis vous feront revivre la vieille Europe avec sa trilogie classique : Espagne, France et Italie! L’Italie fait toujours rêver par son histoire, ses paysages, sa culture et sa gastronomie. Pourquoi ne pas venir y goûter lors de cette soirée où les vins italiens seront à l’honneur? Venez découvrir ce terroir millénaire et les trésors qui s’y cachent. Une finale à notre trio des Caves à vin à ne pas manquer! Conférencier invité : Hugo Robillard Auger Bouchées: Le Complexe Laviolette INCLUS Cette activité comprend 3 services de vin et bouchées. IMPORTANT 18 ans et plus "}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_vin-ditalie-cave-a-vin#Offer_0"}]},{"@id":"https://www.culture3r.com/evenements/vin-ditalie-cave-a-vin/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:29:46.088-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/vin-ditalie-cave-a-vin/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_vin-ditalie-cave-a-vin#Offer_0","http://schema.org/price":[{"@value":"70.00"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culture3r-com_anthony-kavanagh-happy","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/0d62be56-c452-4418-8478-c4637d2d88d2"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/02/Anthony20Kavanagh_-OJ-3wKxXzbmVM9DS6dI_.jpg"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/anthony-kavanagh-happy/"}],"http://schema.org/name":[{"@language":"fr","@value":"Anthony Kavanagh\nHappy"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-11T20:00:00-04:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/anthony-kavanagh-happy/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/description":[{"@language":"fr","@value":"Êtes-vous heureux ? Le monde n’a jamais eu autant besoin de rire, pas vrai ? Ça tombe bien, la providence vient justement de nous envoyer un homme qui sait réchauffer les coeurs et balayer les soucis. Il est humoriste, showman, multitalentueux, et son énergie est débordante ! Après avoir fait rire des milliers de gens à travers la francophonie dernièrement, Anthony Kavanagh se fait une joie de revenir chez lui, au Québec, pour passer du bon temps avec nous. 5 ans que nous ne l’avons pas vu… S’il a des tas d’histoires burlesques à raconter, c’est surtout sa recette du bonheur que l’humoriste partagera avec son public. Après avoir étudié les points communs qu’ont les gens heureux, Kavanagh a décidé de les illustrer de façon cartoonesque, avec sa folie, son style inimitable et son piment bien à lui, pour une dégustation tout en saveur ! Au menu, beaucoup de rires, de surprises et des clés pour améliorer votre quotidien. Et lorsque vous sortirez du spectacle, à la question « Êtes-vous heureux? », soyez assurés que la réponse se lira instantanément sur vos visages… Alors, prêts? Anthony a hâte de vous retrouver à partir du…. "}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_anthony-kavanagh-happy#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_anthony-kavanagh-happy#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_anthony-kavanagh-happy#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_anthony-kavanagh-happy#Offer_3"}]},{"@id":"http://kg.footlight.io/resource/0d62be56-c452-4418-8478-c4637d2d88d2","@type":["http://schema.org/Person"],"http://schema.org/sameAs":[{"@value":"http://www.wikidata.org/entity/Q2852885"},{"@value":"https://www.facebook.com/Anthony.Kavanagh.Officiel/"}],"http://schema.org/url":[{"@value":"https://www.anthonykavanagh.com/"}],"http://schema.org/name":[{"@language":"fr","@value":"Anthony Kavanagh"}],"http://schema.org/hasOccupation":[{"@language":"fr","@value":"Humoriste"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}]},{"@id":"https://www.culture3r.com/evenements/anthony-kavanagh-happy/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:30:06.169-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/anthony-kavanagh-happy/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_anthony-kavanagh-happy#Offer_0","http://schema.org/price":[{"@value":"47.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_anthony-kavanagh-happy#Offer_1","http://schema.org/price":[{"@value":"43.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_anthony-kavanagh-happy#Offer_2","http://schema.org/price":[{"@value":"41.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_anthony-kavanagh-happy#Offer_3","http://schema.org/price":[{"@value":"39.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_hommage-a-ccr-et-rolling-stones-ccr-connection","@type":["http://schema.org/Event"],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-24T20:00:00-04:00"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/hommage-a-ccr-et-rolling-stones-ccr-connection/"}],"http://schema.org/name":[{"@language":"fr","@value":"Hommage à CCR et Rolling Stones\nCCR Connection"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/07/Hommage20C3A020CCR20et20Rolling20Stones_-OUKU148uw3QUulcTDx6_.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/hommage-a-ccr-et-rolling-stones-ccr-connection/#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"CCR connection et The Ya-Ya’s out Une soirée légendaire sous le signe du rock ! Préparez-vous à vibrer au rythme des plus grands classiques du rock avec une soirée hommage inoubliable ! Deux groupes exceptionnels se partagent la scène : l’un célèbre l’énergie brute de Creedence Clearwater Revival (CCR), tandis que l’autre rend hommage à l’attitude iconique et aux riffs envoûtants des Rolling Stones. Une double dose de nostalgie et de puissance musicale qui ravira les fans de toutes les générations ! Membres Accès Plus: Veuillez prendre note qu’exceptionnellement les rabais progressifs ne sont pas applicables pour cette représentation. "}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_hommage-a-ccr-et-rolling-stones-ccr-connection#Offer_0"}]},{"@id":"https://www.culture3r.com/evenements/hommage-a-ccr-et-rolling-stones-ccr-connection/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:30:17.518-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/hommage-a-ccr-et-rolling-stones-ccr-connection/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_hommage-a-ccr-et-rolling-stones-ccr-connection#Offer_0","http://schema.org/price":[{"@value":"39.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_patrice-lecuyer-apres-seulement-32-ans-dabsence-sur-scene","http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-07T20:00:00-05:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/03/Patrice20l27Ecuyer_-OMN5xwV5d1qB8AJwdwg_.jpg"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/patrice-lecuyer-apres-seulement-32-ans-dabsence-sur-scene/"}],"http://schema.org/name":[{"@language":"fr","@value":"Patrice l’Ecuyer\nAprès seulement 32 ans d’absence sur scène"}],"http://schema.org/description":[{"@language":"fr","@value":"Patrice L’Ecuyer présente son tout premier one-man-show intitulé Après seulement 32 ans d’absence sur scène. L’humour a toujours occupé une grande place dans sa carrière, que ce soit dans les Bye Bye (comédien dans quatorze éditions!), L’Ecuyer, Prière de ne pas envoyer de fleurs, les galas Juste pour Rire et ComediHa! ou encore le spectacle Merci beaucoup. Mais aujourd’hui, l’animateur chéri depuis trois décennies s’attaque seul à la scène pour la toute première fois! Au fil de sa carrière, Patrice a toujours été discret sur sa vie, même s’il a vécu des situations hors du commun tant avec ses proches qu’avec de grandes personnalités… mais sur scène, il va tout dévoiler! Flash-back sur sa vie, sa carrière, ses rencontres : à travers un impressionnant flot d’anecdotes remplies d’autodérision, Patrice déploie son immense talent comique. Ancienne star de la LNI (et l’un des joueurs les plus punis!), il a maintenant toute la liberté qu’il faut pour faire rire, surprendre et se mettre à nu sans se sentir coupable. Son talent de conteur, son charisme et ses propos surprenants feront de son spectacle un incontournable de la scène humoristique québécoise. "}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/patrice-lecuyer-apres-seulement-32-ans-dabsence-sur-scene/#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_patrice-lecuyer-apres-seulement-32-ans-dabsence-sur-scene#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_patrice-lecuyer-apres-seulement-32-ans-dabsence-sur-scene#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_patrice-lecuyer-apres-seulement-32-ans-dabsence-sur-scene#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_patrice-lecuyer-apres-seulement-32-ans-dabsence-sur-scene#Offer_3"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-08T16:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culture3r-com_patrice-lecuyer-apres-seulement-32-ans-dabsence-sur-scene#2026-03-07T200000-0500"},{"@id":"http://kg.footlight.io/resource/culture3r-com_patrice-lecuyer-apres-seulement-32-ans-dabsence-sur-scene#2026-03-08T160000-0400"}]},{"@id":"https://www.culture3r.com/evenements/patrice-lecuyer-apres-seulement-32-ans-dabsence-sur-scene/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:30:51.988-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/patrice-lecuyer-apres-seulement-32-ans-dabsence-sur-scene/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_patrice-lecuyer-apres-seulement-32-ans-dabsence-sur-scene#Offer_0","http://schema.org/price":[{"@value":"54.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_patrice-lecuyer-apres-seulement-32-ans-dabsence-sur-scene#Offer_1","http://schema.org/price":[{"@value":"49.30"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_patrice-lecuyer-apres-seulement-32-ans-dabsence-sur-scene#Offer_2","http://schema.org/price":[{"@value":"46.95"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_patrice-lecuyer-apres-seulement-32-ans-dabsence-sur-scene#Offer_3","http://schema.org/price":[{"@value":"44.60"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_patrice-lecuyer-apres-seulement-32-ans-dabsence-sur-scene#2026-03-07T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Patrice l’Ecuyer\nAprès seulement 32 ans d’absence sur scène"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-07T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_patrice-lecuyer-apres-seulement-32-ans-dabsence-sur-scene#2026-03-08T160000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Patrice l’Ecuyer\nAprès seulement 32 ans d’absence sur scène"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-08T16:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_tout-le-monde-shait_podcast-podcast","@type":["http://schema.org/Event"],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/04/Tout20le20monde20s27haC3AFt_-OO44657islnJY7NF3_D_.jpg"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/tout-le-monde-shait_podcast-podcast/"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/tout-le-monde-shait_podcast-podcast/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/name":[{"@language":"fr","@value":"Tout le monde s’haït\nPodcast"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/description":[{"@language":"fr","@value":"Marylène Gendron et Sam Cyr, collègues humoristes et surtout meilleurs amis, sont deux bêtes extrêmement complexées. Avec leurs invités, ils se livrent au sujet de toutes ces imperfections qui les obsèdent, ils en rient, ils en pleurent (peut-être pas quand même!). En gros, ils veulent savoir si tout le monde s’haït autant qu’eux."}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-25T20:00:00-04:00"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_tout-le-monde-shait_podcast-podcast#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_tout-le-monde-shait_podcast-podcast#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_tout-le-monde-shait_podcast-podcast#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_tout-le-monde-shait_podcast-podcast#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/tout-le-monde-shait_podcast-podcast/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:31:03.225-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/tout-le-monde-shait_podcast-podcast/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_tout-le-monde-shait_podcast-podcast#Offer_0","http://schema.org/price":[{"@value":"30.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_tout-le-monde-shait_podcast-podcast#Offer_1","http://schema.org/price":[{"@value":"27.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_tout-le-monde-shait_podcast-podcast#Offer_2","http://schema.org/price":[{"@value":"26.25"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_tout-le-monde-shait_podcast-podcast#Offer_3","http://schema.org/price":[{"@value":"25.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_steeve-diamond-presente-bocelli-friends-steeve-diamond-sylvie-fortin-et-nicolas-guimont","http://schema.org/name":[{"@language":"fr","@value":"Steeve Diamond présente Bocelli \u0026 Friends"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K2-2811"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/steeve-diamond-presente-bocelli-friends-steeve-diamond-sylvie-fortin-et-nicolas-guimont/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-08T14:00:00-04:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/9475525c-10fa-4a53-b89b-8c7d9ba68726"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/steeve-diamond-presente-bocelli-friends-steeve-diamond-sylvie-fortin-et-nicolas-guimont/"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/10/Steeve20Diamond20prC3A9sente20Bocelli202620Friends202203A20De20CC3A9line20C3A020Bocelli_-Oa_2e_aEtGfWSiLwaar_.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"Après un succès incroyable avec Bocelli \u0026amp; Friends, Steeve Diamond revient en force avec un tout nouveau spectacle : Bocelli \u0026amp; Friends 2 – De Céline à Bocelli! Entre deux tournées européennes avec les 4 SANS VOIX, l’imitateur-chanteur est de retour au Québec avec cette suite grandiose, encore plus émotive, surprenante et éclatante. Sur scène, Steeve Diamond et Sylvie Fortin font revivre à eux seuls une galerie impressionnante de voix légendaires, québécoises et internationales. Imaginez Andrea Bocelli chantant « Wonderful World » avec Louis Armstrong, puis « L’hymne à l’amour » en trio avec Gerry Boulet et Édith Piaf, et enfin « The Prayer » en duo avec Céline Dion. Avec la complicité musicale du pianiste Pierre Verrette, ainsi que la violoncelliste Gabrielle Préfontaine, ils livrent une performance aussi époustouflante qu’émouvante. Membres Accès Plus: Veuillez prendre note qu’exceptionnellement seul le rabais de 10 % est disponible pour le spectacle."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_steeve-diamond-presente-bocelli-friends-steeve-diamond-sylvie-fortin-et-nicolas-guimont#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_steeve-diamond-presente-bocelli-friends-steeve-diamond-sylvie-fortin-et-nicolas-guimont#Offer_1"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-13T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culture3r-com_steeve-diamond-presente-bocelli-friends-steeve-diamond-sylvie-fortin-et-nicolas-guimont#2026-03-08T140000-0400"},{"@id":"http://kg.footlight.io/resource/culture3r-com_steeve-diamond-presente-bocelli-friends-steeve-diamond-sylvie-fortin-et-nicolas-guimont#2026-03-13T200000-0400"}]},{"@id":"https://www.culture3r.com/evenements/steeve-diamond-presente-bocelli-friends-steeve-diamond-sylvie-fortin-et-nicolas-guimont/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:31:13.922-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/steeve-diamond-presente-bocelli-friends-steeve-diamond-sylvie-fortin-et-nicolas-guimont/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_steeve-diamond-presente-bocelli-friends-steeve-diamond-sylvie-fortin-et-nicolas-guimont#Offer_0","http://schema.org/price":[{"@value":"55.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_steeve-diamond-presente-bocelli-friends-steeve-diamond-sylvie-fortin-et-nicolas-guimont#Offer_1","http://schema.org/price":[{"@value":"50.20"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_steeve-diamond-presente-bocelli-friends-steeve-diamond-sylvie-fortin-et-nicolas-guimont#2026-03-08T140000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Steeve Diamond présente Bocelli \u0026 Friends"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-08T14:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_steeve-diamond-presente-bocelli-friends-steeve-diamond-sylvie-fortin-et-nicolas-guimont#2026-03-13T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Steeve Diamond présente Bocelli \u0026 Friends"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-13T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_la-foret-des-entre-mondes-2","@type":["http://schema.org/Event"],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/la-foret-des-entre-mondes-2/#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/4ccaf1c3-26ff-41d6-84e5-b421483eeae3"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-03-14"}],"http://schema.org/description":[{"@language":"fr","@value":"Dans La forêt des entre-mondes, Myriam Lafae propose une réflexion poétique sur les relations entre nature et culture. Par le biais de la figure symbolique de l’Arbre, l’artiste interroge les interdépendances entre les êtres vivants et leur environnement. Dans une démarche éco-consciente, elle conçoit un parcours immersif composé d’installations sculpturales mêlant arts textiles et paysage sonore. Cette expérience multisensorielle invite le visiteur à suspendre le temps, à ralentir, et à s’ouvrir à un univers où mémoire, matière et imaginaire s’entrelacent. Artiste : Myriam Lafae Une immersion artistique qui nous engage à ralentir et à repenser en profondeur notre place dans un monde en plein changement Visites guidées Découvrez l’exposition dans une toute nouvelle perspective en réservant une de nos visites guidées. Accompagné·e d’un·e médiateur·trice, approfondissez vos connaissances de l’artiste, de sa démarche et de ses techniques. Une expérience personnalisée et participative qui vous permettra d’explorer les œuvres avec émotion et profondeur. Réserver une visite guidée Voir l'activité liée: Quand la nature prend forme "}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/la-foret-des-entre-mondes-2/"}],"http://schema.org/name":[{"@language":"fr","@value":"La forêt des entre-mondes"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/08/myriam-lafae-2024-scaled.jpg"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-06-14"}]},{"@id":"https://www.culture3r.com/evenements/la-foret-des-entre-mondes-2/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:30:37.471-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/la-foret-des-entre-mondes-2/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_les-shows-acoustiques-shania-twain-les-shows-acoustiques-shania-twain","@type":["http://schema.org/Event"],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-16T20:00:00-04:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/les-shows-acoustiques-shania-twain-les-shows-acoustiques-shania-twain/"}],"http://schema.org/name":[{"@language":"fr","@value":"Les Shows Acoustiques – Shania Twain"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/10/Les20Shows20Acoustiques20-20Shania20Twain_-Obd5AVyEehanNXoGo1X_-scaled.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"SHANIA TWAIN – VERSION ACOUSTIQUE Anick Gagnon (La Voix, Cirque du Soleil) revisite le répertoire de Shania Twain dans un spectacle acoustique intimiste. Entourée de trois musiciens accomplis ayant partagé la scène avec de nombreux artistes de renom, elle propose une relecture originale de succès comme “Man! I Feel Like a Woman!”, “You’re Still the One” et “From This Moment On”. Les arrangements à la guitare, au violon et à la contrebasse ainsi que les harmonies vocales sont au cœur de cette version épurée. L’atmosphère chaleureuse et conviviale crée une proximité avec le public, qui se laisse porter par cette expérience musicale authentique. * Première partie: Michel Turcotte* Membres Accès Plus: Veuillez prendre note qu’exceptionnellement les rabais progressifs ne sont pas applicables pour cette représentation. "}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/les-shows-acoustiques-shania-twain-les-shows-acoustiques-shania-twain/#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_les-shows-acoustiques-shania-twain-les-shows-acoustiques-shania-twain#Offer_0"}]},{"@id":"https://www.culture3r.com/evenements/les-shows-acoustiques-shania-twain-les-shows-acoustiques-shania-twain/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:30:41.399-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/les-shows-acoustiques-shania-twain-les-shows-acoustiques-shania-twain/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_les-shows-acoustiques-shania-twain-les-shows-acoustiques-shania-twain#Offer_0","http://schema.org/price":[{"@value":"35.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_emile-proulx-cloutier-ma-main-au-feu","@type":["http://schema.org/Event"],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/emile-proulx-cloutier-ma-main-au-feu/#WebPage"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/emile-proulx-cloutier-ma-main-au-feu/"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/description":[{"@language":"fr","@value":"Après trois albums aux textes éloquents, d’une richesse musicale saluée par le public et la critique, ÉMILE PROULX-CLOUTIER nous éblouit une fois de plus avec sa voix percutante, assumée et émouvante, confirmant qu’il a su définir le son d’un univers musical bien à lui. Dans ce spectacle, l’auteur-compositeur-interprète nous livre les nouvelles chansons de son album Ma main au feu, ainsi que plusieurs autres pièces poétiques et vives de son répertoire. "}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-15T20:00:00-05:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/02/C389mile20Proulx20Cloutier_-OK7-Ln2UtI8Jv_FIvBs_.jpg"}],"http://schema.org/name":[{"@language":"fr","@value":"Émile Proulx Cloutier\nMa main au feu"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_emile-proulx-cloutier-ma-main-au-feu#Offer_0"}]},{"@id":"https://www.culture3r.com/evenements/emile-proulx-cloutier-ma-main-au-feu/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-13T23:31:30.765-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/emile-proulx-cloutier-ma-main-au-feu/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_emile-proulx-cloutier-ma-main-au-feu#Offer_0","http://schema.org/price":[{"@value":"47.00"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culture3r-com_qw4rtz-a-cappella-heros","http://schema.org/name":[{"@language":"fr","@value":"Qw4rtz\nÇa Goûte Noël"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K10-294"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/description":[{"@language":"fr","@value":"Cet hiver pour le temps des Fêtes, QW4RTZ vous a mijoté un spectacle entremêlant chansons, recettes, humour et anecdotes savoureuses. Arômes de cannelle, effluves de girofle et muscade, chacun de vos quatre chefs/cuistots de l’a cappella y présente, en chanson, la recette de son plat signature du temps des Fêtes. Vos oreilles se délecteront des meilleurs succès de Noël, de l’électro-trad à Marie-Noël en passant par Last Christmas et Le sentier de neige, clairement… Ça Goûte Noël ! Première partie du 20 décembre 2025 : Ben Nesrallah "}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/04/Qw4rtz_-OMme9jZbOvm1i3uO4k8_.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/qw4rtz-a-cappella-heros/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-12-16T20:00:00-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/qw4rtz-a-cappella-heros/"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_qw4rtz-a-cappella-heros#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_qw4rtz-a-cappella-heros#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_qw4rtz-a-cappella-heros#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_qw4rtz-a-cappella-heros#Offer_3"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-12-17T20:00:00-05:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culture3r-com_qw4rtz-a-cappella-heros#2026-12-16T200000-0500"},{"@id":"http://kg.footlight.io/resource/culture3r-com_qw4rtz-a-cappella-heros#2026-12-17T200000-0500"}]},{"@id":"https://www.culture3r.com/evenements/qw4rtz-a-cappella-heros/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:30:28.118-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/qw4rtz-a-cappella-heros/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_qw4rtz-a-cappella-heros#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"45.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_qw4rtz-a-cappella-heros#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"41.20"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_qw4rtz-a-cappella-heros#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"39.30"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_qw4rtz-a-cappella-heros#Offer_3","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"37.40"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_qw4rtz-a-cappella-heros#2026-12-16T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Qw4rtz\nÇa Goûte Noël"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-12-16T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_qw4rtz-a-cappella-heros#2026-12-17T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Qw4rtz\nÇa Goûte Noël"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-12-17T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_stephane-fallu","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/stephane-fallu/"}],"http://schema.org/description":[{"@language":"fr","@value":"Après avoir été vu sur grand écran dans vos émissions préférées au cours des dernières années, Stéphane Fallu prépare enfin son retour sur scène. Nouveau spectacle, nouvelles blagues, mais toujours le même Fallu ! Du pur stand-up et du pur Fallu : simple, direct et décousu. Stéphane se fait plaisir en portant un regard amusé sur une société où tout le monde se prend beaucoup trop au sérieux. Stéphane Fallu a des projets plein la tête! Il a tourné récemment la saison 8 de Refuge animal et sera également le tout nouvel animateur de la série Sur le pouce diffusée sur Zeste. Stéphane continue de donner de son temps aux jeunes adultes avec la maison Stéphane Fallu. Membres Accès Plus: Veuillez prendre note qu’exceptionnellement le rabais de 20 % n’est pas applicable pour cette représentation."}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/b3d8b21b-4138-4f4d-9fdd-6ec3ef086d5f"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-26T20:00:00-04:00"}],"http://schema.org/name":[{"@language":"fr","@value":"Stéphane Fallu\nAssumé"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/03/StC3A9phane20Fallu_-OLKA68SBCdk1IvNRTJP_.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/stephane-fallu/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_stephane-fallu#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_stephane-fallu#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_stephane-fallu#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_stephane-fallu#Offer_3"}]},{"@id":"http://kg.footlight.io/resource/b3d8b21b-4138-4f4d-9fdd-6ec3ef086d5f","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Stéphane Fallu"}],"http://schema.org/hasOccupation":[{"@language":"fr","@value":"Humoriste"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}],"http://schema.org/sameAs":[{"@value":"http://www.wikidata.org/entity/Q3501749"}]},{"@id":"https://www.culture3r.com/evenements/stephane-fallu/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:32:23.435-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/stephane-fallu/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_stephane-fallu#Offer_0","http://schema.org/price":[{"@value":"40.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_stephane-fallu#Offer_1","http://schema.org/price":[{"@value":"36.70"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_stephane-fallu#Offer_2","http://schema.org/price":[{"@value":"35.05"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_stephane-fallu#Offer_3","http://schema.org/price":[{"@value":"33.40"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_equipage-recherche-cirque-collini","@type":["http://schema.org/Event"],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/equipage-recherche-cirque-collini/#WebPage"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-03T15:00:00-05:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/8f57c4c9-da91-4809-83ae-b5e8db471f39"}],"http://schema.org/description":[{"@language":"fr","@value":"4 ans et + | 50 minutes | Cirque, théâtre et musique Thèmes: aventure/équipe/confiance en soi À noter qu’un petit mot d’introduction est prévu avant le début du spectacle et qu’une discussion animée, non obligatoire, est offerte à la fin de la représentation. Accessibilité Public de la neurodiversité ou avec hypersensibilité, consultez la trousse pour une sortie au théâtre via ce lien. Durant la relâche, j’aime offrir une proposition qui permet de faire une pause dans le quotidien effréné des enfants et des parents. Je souhaitais un spectacle qui permette de réunir toute la famille autour d’un bon moment. Grâce à d’impressionnantes acrobaties, Équipage recherché invite les spectateurs à voir les défis comme des opportunités pour grandir. – Lisa Dugré, programmatrice aux arts de la scène. Le Capitaine Madjack Rackham est en quête d’un ultime membre d’équipage afin de partir à la recherche d’un trésor. Le public est convié à assister à cette grandiose assemblée, lors de laquelle un aspirant marin devra surmonter diverses épreuves pour mériter sa place au sein de l’équipage. Un voyage où les coutumes et les défis de la vie en mer sont représentés à travers cette histoire de liberté, avec des prestations circassiennes à couper le souffle. Cette fiction historique inspirante met en scène des personnages variés avec une trame musicale originale qui marquera votre mémoire grâce aux acrobaties aériennes, équilibres, main à main et combats de scène. Direction artistique et écriture : François Bédard Aide à la mise en scène et conseillère dramaturgie : Julie Renault Artistes : Vanessa Collini | Anne Cormac, François Bédard | Capitaine Mad Jack Rackham, David Fleury | Jean Fleury, Camille Tremblay | Grâce O’mally, Guillaume Laroche | Pirate Guy , Clara | Grâce O’Malley remplaçante et Jérémie Poirier | Pirate Guy remplaçant Musique originale : Guillaume Laroche et David Fleury Coordinateur de cascade : Stéphane Julien Éclairages : Patrick Thivierge, Rémi Plamondon et Lyne Rioux Dessins : Julie Mercier Remerciements | Mathieu Larochelle et l’équipe technique du Théâtre des Eskers Membres Accès Plus: Veuillez prendre note qu’exceptionnellement les rabais progressifs ne sont pas applicable pour cette représentation. Trousse d'accompagnement "}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/name":[{"@language":"fr","@value":"Équipage recherché\nCirque Collini"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/03/C389quipage20recherchC3A9_-OLnw2N4yah5_FMV_JeE_.jpg"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/equipage-recherche-cirque-collini/"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_equipage-recherche-cirque-collini#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_equipage-recherche-cirque-collini#Offer_1"}]},{"@id":"https://www.culture3r.com/evenements/equipage-recherche-cirque-collini/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:32:32.095-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/equipage-recherche-cirque-collini/"}]},{"@id":"http://kg.footlight.io/resource/8f57c4c9-da91-4809-83ae-b5e8db471f39","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Cirque"}],"http://www.w3.org/2004/02/skos/core#closeMatch":[{"@id":"http://kg.artsdata.ca/resource/CircusPerformance"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_equipage-recherche-cirque-collini#Offer_0","http://schema.org/price":[{"@value":"14.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_equipage-recherche-cirque-collini#Offer_1","http://schema.org/price":[{"@value":"10.00"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culture3r-com_breen-leboeuf-mes-blues-passent-pu-dans-porte","@type":["http://schema.org/Event"],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/name":[{"@language":"fr","@value":"Breen Leboeuf\nMes blues passent pu dans porte"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2024/10/Breen20Leboeuf_-O8N_tdIbDcd6xMBgVfj_.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/breen-leboeuf-mes-blues-passent-pu-dans-porte/#WebPage"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-27T19:00:00-04:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/description":[{"@language":"fr","@value":"En plus d’être l’interprète d’un des plus grands succès de notre histoire musicale, Breen LeBoeuf est pour plusieurs considéré comme l’une des plus belles voix du Québec. Cette légende du Rock provoque l’enthousiasme d’un public charmé, voir impressionné, par sa performance vocale, l’unique signature de chacune de ses interprétations, ses anecdotes inédites et cette solide énergie qui continue de l’habiter après plus de 55 ans de carrière. MES BLUES PASSENT PU DANS’ PORTE nous replonge dans cette époque marquée par le groupe culte Offenbach dont Breen a été l’un des principaux artisans. Pour l’occasion, il a le plaisir et l’honneur de s’entourer de quelques-uns de nos meilleurs musiciens/chanteurs: Réjean Lachance à la guitare, Pierre Beauregard au clavier et sax et Marc Brodeur aux drums. C’est donc en leur compagnie que Breen revisite pour vous les plus grands classiques d’Offenbach et célèbre avec vous ce groupe qui nous a laissé un des plus riches répertoires musicaux d’ici."}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/breen-leboeuf-mes-blues-passent-pu-dans-porte/"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_breen-leboeuf-mes-blues-passent-pu-dans-porte#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_breen-leboeuf-mes-blues-passent-pu-dans-porte#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_breen-leboeuf-mes-blues-passent-pu-dans-porte#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_breen-leboeuf-mes-blues-passent-pu-dans-porte#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/breen-leboeuf-mes-blues-passent-pu-dans-porte/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:32:40.979-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/breen-leboeuf-mes-blues-passent-pu-dans-porte/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_breen-leboeuf-mes-blues-passent-pu-dans-porte#Offer_0","http://schema.org/price":[{"@value":"50.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_breen-leboeuf-mes-blues-passent-pu-dans-porte#Offer_1","http://schema.org/price":[{"@value":"45.70"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_breen-leboeuf-mes-blues-passent-pu-dans-porte#Offer_2","http://schema.org/price":[{"@value":"43.55"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_breen-leboeuf-mes-blues-passent-pu-dans-porte#Offer_3","http://schema.org/price":[{"@value":"41.40"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_josephine-et-les-grandes-personnes-le-carrousel","@type":["http://schema.org/Event"],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/josephine-et-les-grandes-personnes-le-carrousel/#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"7 ans et + | 55 minutes | Théâtre Thèmes: Émotions/rapport aux adultes/famille À noter qu’un petit mot d’introduction est prévu avant le début du spectacle et qu’une discussion animée, non obligatoire, est offerte à la fin de la représentation. Accessibilité Public de la neurodiversité ou avec hypersensibilité, consultez la trousse pour une sortie au théâtre via ce lien. J’aime l’humanité qui se dégage de l’écriture de l’autrice Marie-Hélène Larose-Truchon. Pour les enfants, cette pièce a le pouvoir de réparer certaines blessures, en mettant en lumière les comportements parfois déroutants des adultes. Pour les grands, elle ouvre une porte vers leur cœur d’enfant, les invite à se reconnaître dans leurs contradictions… et peut-être à se regarder avec un peu plus de compassion. – Lisa Dugré, programmatrice aux arts de la scène. Joséphine est une petite fille coach de vie pour enfants. Elle offre des trucs et astuces pour apprendre à vivre avec les grandes personnes. Au fil des dix leçons de coaching, on découvre les aléas de la vie familiale de Joséphine : une vie de petites et grandes peines de parents et d’enfants. Heureusement, une curieuse vieille dame assiste à chacune des leçons… elles se lient d’amitié et s’aident l’une et l’autre. Il y a des enfants qui portent le poids du monde un peu plus qu’ils ne le devraient. Il y a des enfants qui jouent aux grandes personnes. Il y en a d’autres qui sont, carrément, des grandes personnes. La pièce de Marie-Hélène Larose-Truchon est une ode à l’enfance, à l’imaginaire et à la force des enfants. Elle fera rire les jeunes spectateurs, car ils y reconnaîtront leurs parents. Elle fera peut-être sourire les adultes… qui se reconnaîtront dans leurs contradictions. Texte : Marie-Hélène Larose-Truchon Mise en scène : Marie-Eve Huot Distribution : Emilie Dionne et Mireille Métellus Musique : Diane Labrosse Costumes et soutien à la scénographie: Linda Brunelle Lumière et projections : Louis-Xavier Gagnon-Lebrun assisté de Nicola Dubois Illustrations : Patrice Charbonneau-Brunelle Coiffures et maquillages : Suzanne Trépanier Conseillère aux mouvements : Marie-Gabrielle Ménard Assistance à la mise en scène et coordination de production : Lucie Baril-Castonguay Direction technique : Nicolas Fortin Régie à la création : Nicolas Fortin ou Ariane Roy Le Carrousel remercie : Pony Studios pour la sérigraphie sur le chandail de Joséphine et Géraldine Courchesne pour la perruque du personnage de Françoise Joséphine et les grandes personnes est une création du Carrousel, compagnie de théâtre, en coproduction avec le Théâtre du Bic (Québec) et les Tréteaux de France, Centre dramatique national (France). Photos en répétitions : David Ospina La pièce Joséphine et les grandes personnes est publiée et représentée par L’ARCHE, éditeur \u0026amp; agence théâtrale. Membres Accès Plus: Veuillez prendre note qu’exceptionnellement les rabais progressifs ne sont pas applicable pour cette représentation. TROUSSE D'ACCOMPAGNEMENT "}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/josephine-et-les-grandes-personnes-le-carrousel/"}],"http://schema.org/name":[{"@language":"fr","@value":"Joséphine et les grandes personnes\nLe Carrousel"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/03/JosC3A9phine20et20les20grandes20personnes_-OLnzFhVQl2nLrVf0AUX_.jpg"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-28T15:00:00-04:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_josephine-et-les-grandes-personnes-le-carrousel#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_josephine-et-les-grandes-personnes-le-carrousel#Offer_1"}]},{"@id":"https://www.culture3r.com/evenements/josephine-et-les-grandes-personnes-le-carrousel/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:32:51.550-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/josephine-et-les-grandes-personnes-le-carrousel/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_josephine-et-les-grandes-personnes-le-carrousel#Offer_0","http://schema.org/price":[{"@value":"14.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_josephine-et-les-grandes-personnes-le-carrousel#Offer_1","http://schema.org/price":[{"@value":"10.00"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culture3r-com_perou-vallee-des-incas-les-grands-explorateurs","http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/perou-vallee-des-incas-les-grands-explorateurs/"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/perou-vallee-des-incas-les-grands-explorateurs/#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/22e76d88-593d-4e82-9c46-50cba5c009ed"}],"http://schema.org/name":[{"@language":"fr","@value":"Pérou, vallée des Incas\nLes Grands Explorateurs"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/04/PC3A9rou2C20vallC3A9e20des20Incas_-OMr0Icrx1VI6yhqnYGO_.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"Pérou, vallée des Incas – Rencontre avec André Maurice André Maurice nous emmène dans la Cordillère des Andes et ses parties basses, pour nous livrer ses richesses, tant ethniques que géographiques et historiques. C’est dans la communauté de Patakancha que nous commencerons notre voyage à travers l’espace et le temps : site de Moray, Montagne des 7 couleurs et Salines de Maras. Vous découvrirez, Cusco, la capitale de l’empire inca, qui brille par ses vestiges et ses fêtes. Puis nous partirons pour le Machu Picchu, merveille de l’architecture inca par excellence. Nous partirons ensuite dans la forêt tropicale, où l’on cultive le cacao, le thé et le café. La deuxième partie s’amorce par Trujillo, où la marinera, danse de multiples influences, s’imposera à nous. Yungay, point de départ pour explorer la Cordillère Blanche, tremble encore aujourd’hui de l’avalanche de boue du Huascarán qui l’a enfouie sous terre en 1970. Lima, capitale du Pérou, est un important centre culturel, la fête nationale y est célébrée en juillet. Et si nous descendons vers le sud, l’Oasis de Huacachina nous fascinera, ainsi que les fameuses Lignes de Nazca. Arequipa, la deuxième du pays, est un passage obligé pour accéder au Canyon de Colca, où les vigognes font partie du paysage et où le Mirador des Condors nous offrira un point de vue extraordinaire. Biographie Diplômé du Conservatoire d’Art Dramatique de Montréal en 1988, André est aussi un comédien professionnel, qui a enseigné le théâtre et a tourné dans plusieurs séries télévisées durant sa carrière. C’est à l’âge de 18 ans qu’il part explorer le monde, pour un grand tour d’Europe qui le mènera par la suite en Asie, en Afrique, aux Antilles, en Amérique du Sud et aux États-Unis. Passionné de cinéma, il signe sa première réalisation en 2006, Le Québec de Montréal à Kuujjuaq. Dès 2012, il réalise son second film, Québec Direction Nord, qui s’attarde sur les régions nordiques moins explorées. Il réalise son troisième projet, terminé en 2015 : Viva Argentina. En 2020, il revient vers le pôle Nord et sillonne : Le Canada, Terre de Grands Espaces, porté par l’appel de la nature et de l’immensité de son pays. Ses réalisations ont par la suite été diffusé par l’intermédiaire des Grands Explorateurs. Membres Accès Plus: Veuillez prendre note que les rabais progressifs ne sont pas applicables pour cette représentation."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_perou-vallee-des-incas-les-grands-explorateurs#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-24T16:30:00-05:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culture3r-com_perou-vallee-des-incas-les-grands-explorateurs#2026-01-24T163000-0500"},{"@id":"http://kg.footlight.io/resource/culture3r-com_perou-vallee-des-incas-les-grands-explorateurs#2026-01-24T133000-0500"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-24T13:30:00-05:00"}]},{"@id":"https://www.culture3r.com/evenements/perou-vallee-des-incas-les-grands-explorateurs/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-22T23:32:28.353-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/perou-vallee-des-incas-les-grands-explorateurs/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_perou-vallee-des-incas-les-grands-explorateurs#Offer_0","http://schema.org/price":[{"@value":"33.92"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culture3r-com_perou-vallee-des-incas-les-grands-explorateurs#2026-01-24T163000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Pérou, vallée des Incas\nLes Grands Explorateurs"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-24T16:30:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_perou-vallee-des-incas-les-grands-explorateurs#2026-01-24T133000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Pérou, vallée des Incas\nLes Grands Explorateurs"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-24T13:30:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mikoman-2","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/12/2025-av-expo-caroline-monnet-photo-web-cr-etienne-boisvert-1-1-scaled.jpg"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/4ccaf1c3-26ff-41d6-84e5-b421483eeae3"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2025-12-06"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/mikoman-2/"}],"http://schema.org/name":[{"@language":"fr","@value":"MIKOMÀN"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/mikoman-2/#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"Caroline Monnet, artiste anishinaabe et française, déploie un travail innovant où se mêlent arts visuels, vidéo, installation et sculpture. Son œuvre interroge les intersections entre culture traditionnelle et contemporaine, tout en explorant les réalités sociales et politiques des communautés autochtones aujourd’hui. À travers un vocabulaire visuel puissant, Monnet s’approprie des symboles, des motifs et des récits issus de son héritage anishinaabe, qu’elle revisite et réinvente à l’aide des outils et des pratiques contemporaines. Son travail met en lumière des questions essentielles sur l’identité, la mémoire et la représentation des peuples autochtones dans un monde globalisé, souvent dominé par des récits eurocentrés. L’artiste interroge les frontières et les clivages culturels, et son œuvre se veut un pont entre les générations, entre l’histoire et l’avenir. Dans ses installations et vidéos, Monnet provoque un dialogue dynamique et fluide entre le passé et le présent, entre l’individuel et le collectif. Elle invite le spectateur à redéfinir les notions d’appartenance et de territoire à travers des récits visuels qui confrontent, réconcilient et transforment les perceptions. Artiste : Caroline Monnet Caroline Monnet est représentée par la galerie Blouin Division. Un espace où l’art tisse le lien entre identité et mémoire. Visites guidées Découvrez l’exposition dans une toute nouvelle perspective en réservant une de nos visites guidées. Accompagné·e d’un·e médiateur·trice, approfondissez vos connaissances de l’artiste, de sa démarche et de ses techniques. Une expérience personnalisée et participative qui vous permettra d’explorer les œuvres avec émotion et profondeur. RÉSERVER UNE VISITE GUIDÉE Voir l'activité liée: Formes et racines "}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-03-08"}]},{"@id":"https://www.culture3r.com/evenements/mikoman-2/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:31:36.964-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/mikoman-2/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jean-francois-mercier-en-rodage","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-232"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Jean-François Mercier\nAvis d’ébullition"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/jean-francois-mercier-en-rodage/#WebPage"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/06/Jean-FranC3A7ois20Mercier_-OTDM2t0HjX8gPoczelk_.jpg"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-30T20:00:00-04:00"}],"http://schema.org/description":[{"@language":"fr","@value":"Avec son regard critique et pertinent, son ton souvent corrosif et parfois émouvant, il ramène son humour singulier sur les planches avec un seul objectif : brasser une fois de plus la cage de l’humour en confrontant nos convictions pour nous faire rire de nos travers, mais aussi des siens. Auteur de grand talent, animateur et comédien, c’est sur scène, nourri par les réactions du public, qu’il trouve sa voix d’humoriste pour nous faire rire et réfléchir. "}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/jean-francois-mercier-en-rodage/"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_jean-francois-mercier-en-rodage#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_jean-francois-mercier-en-rodage#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_jean-francois-mercier-en-rodage#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_jean-francois-mercier-en-rodage#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/jean-francois-mercier-en-rodage/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:31:40.450-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/jean-francois-mercier-en-rodage/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jean-francois-mercier-en-rodage#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"47.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jean-francois-mercier-en-rodage#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"43.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jean-francois-mercier-en-rodage#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"41.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jean-francois-mercier-en-rodage#Offer_3","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"39.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_nos-saisons-pps-danse","@type":["http://schema.org/Event"],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/nos-saisons-pps-danse/#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-08T15:00:00-05:00"}],"http://schema.org/name":[{"@language":"fr","@value":"Nos saisons\nPPS Danse"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/nos-saisons-pps-danse/"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/f9335d62-dd9f-4751-8130-a4a000bb979b"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/03/Nos20saisons_-OLnsmKzOL-v5DpTo0yI_.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"7 ans et + | 50 minutes | Danse Thèmes: Nature/conscience environnementale/curiosité/respect de l’autre À noter qu’un petit mot d’introduction est prévu avant le début du spectacle et qu’une discussion animée, non obligatoire, est offerte à la fin de la représentation. Accessibilité Public de la neurodiversité ou avec hypersensibilité, consultez la trousse pour une sortie au théâtre via ce lien. J’ai sélectionné pour vous Nos saisons parce qu’à travers la danse, le spectacle sensibilise à l’impact de nos gestes sur l’environnement. Il invite petits et grands à réfléchir, à se positionner et à s’affirmer face à des sujets de société importants. Pssst…J’ai moi-même appris des choses grâce à ce spectacle.– Lisa Dugré, programmatrice aux arts de la scène. Quelle saison es-tu aujourd’hui? Est-ce qu’il fait soleil dans ton coeur ou il neige dans ta tête? Ou les deux? Et ta journée? Est-ce un jour Tortue, un jour Roche ou un jour Tempête ? Dans ce parcours chorégraphique et écologiste, quatre interprètes voyagent en mouvements et en paroles entre les mondes extérieurs et intérieurs. On s’amuse à barbouiller, voire à abattre, la frontière qu’on a trop solidement érigée entre l’humain et la nature. Bien équipés de sac à dos, d’imperméables, de collations et de carnets de notes, les danseurs.euses empruntent le chemin ludique de l’enfance et de son émerveillement, pour revisiter l’expérience d’habiter notre planète. On fait la fête aux sensations, douces ou inconfortables, on y accueille toutes les transformations : les mutations mystérieuses du corps, le passage du jour à la nuit, la danse des saisons. Ce spectacle, à la fois joyeux et environnementaliste, est un appel à prendre soin de la planète comme de nous-mêmes. Nous sommes vivants, donc nous sommes rochers, mers, animaux, vent et plantes. Idéation : David Rancourt Direction et chorégraphie : David Rancourt et Jessica Serli Créé et interprété par : Nicolas Boivin, Marilyne Cyr, Cyndie Gravel, Hoor Malas (substitut), Ciro Melgaço Direction musicale et composition : Alexis Dumais et Vanessa Marcoux Musique additionnelle : Antoine Berthiaume Direction des répétitions : Marie-Ève Carrière Conception des éclairages : Paul Chambers Costumes, scénographie et accessoires : Karine Galarneau Direction technique : Ian Vadnais Régie en tournée : Stéphanie Savaria Coordination et administration chez PPS Danse : Mark Eden Towle et Marie-Claude Boudreault Membres Accès Plus: Veuillez prendre note qu’exceptionnellement les rabais progressifs ne sont pas applicable pour cette représentation. Voir la vidéo Trousse d'accompagnement "}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_nos-saisons-pps-danse#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_nos-saisons-pps-danse#Offer_1"}]},{"@id":"https://www.culture3r.com/evenements/nos-saisons-pps-danse/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:31:50.425-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/nos-saisons-pps-danse/"}]},{"@id":"http://kg.footlight.io/resource/f9335d62-dd9f-4751-8130-a4a000bb979b","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Famille"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_nos-saisons-pps-danse#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"14.50"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_nos-saisons-pps-danse#Offer_1","http://schema.org/price":[{"@value":"10.00"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culture3r-com_wooden-shapes","@type":["http://schema.org/Event"],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/wooden-shapes/#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/wooden-shapes/"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/11/Wooden20Shapes_-OerOXPj8oXakw-nYF3k_.jpg"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/name":[{"@language":"fr","@value":"Wooden Shapes\nDu classique au rock 2"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-23T20:00:00-04:00"}],"http://schema.org/description":[{"@language":"fr","@value":"Le spectacle qui a conquis le public est de retour! Après une tournée remarquée au Québec et à l’international, Wooden Shapes débarque avec son tout nouveau spectacle. Plongez dans les plus grands classiques du rock, Metallica, Harmonium, Guns N’ Roses, David Bowie et bien d’autres, réinventés et joués uniquement sur des instruments à cordes. Avec des arrangements encore plus grandioses, ce spectacle ne cherche pas à imiter, il réinvente. Une célébration audacieuse du rock, qui frappe droit au cœur."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_wooden-shapes#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_wooden-shapes#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_wooden-shapes#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_wooden-shapes#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/wooden-shapes/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:31:59.699-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/wooden-shapes/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_wooden-shapes#Offer_0","http://schema.org/price":[{"@value":"46.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_wooden-shapes#Offer_1","http://schema.org/price":[{"@value":"42.10"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_wooden-shapes#Offer_2","http://schema.org/price":[{"@value":"40.15"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_wooden-shapes#Offer_3","http://schema.org/price":[{"@value":"38.20"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_maroc-en-vanlife-les-grands-explorateurs","http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-02T13:30:00-04:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/maroc-en-vanlife-les-grands-explorateurs/"}],"http://schema.org/description":[{"@language":"fr","@value":"Maroc en vanlife – Rencontre avec Marc Temmerman Voyage libre et inspiré, c’est à bord de son van aménagé que Marc nous fait découvrir « son » Maroc depuis Tanger jusqu’à Dakhla, dans l’extrême sud du pays. Guidé par ses envies, son intuition, une bonne dose d’improvisation et sa passion pour le kitesurf, ce voyage culturel, humain et aventureux révèle le sens de l’accueil des Marocains et dévoile avec originalité, sensibilité et sincérité un Maroc surprenant, déroutant. Parti depuis sa Belgique natale avec son van, Marc rejoint Tanger depuis Marseille. Son « Maroc version vanlife » commence dans le nord du pays avec les villes de Tétouan, Chefchaouen, Fès ou encore la forêt d’Azrou. Marc s’égare ensuite dans les altitudes des Monts Atlas avant de rejoindre les décors inattendus de Ouarzazate. Recherchant l’ordinaire, mais tombant bien souvent sur l’extraordinaire, il déroule les kilomètres jusqu’à l’océan Atlantique en passant par Taliouine et Tafraoute et rejoint le petit village d’Imsouane avant une étape à Essaouira. Commence ensuite sa longue route dans le désert du Sahara pour atteindre Dakhla, la Mecque du kitesurf, sa destination finale. Avec pour intention d’interroger un pays « lien » entre l’occident et l’Afrique, ce voyage à la fois dynamique et rêveur, se veut proche de toutes les sensations qu’un Maroc moderne et traditionnel peut offrir. Biographie Marc Temmerman, né en Belgique et journaliste de formation, a orchestré sa vie autour du voyage. Après avoir parcouru l’Europe en auto-stop, il se lance dans une carrière de réalisateur de films documentaires. Pour les télévisions belge et française, il balade sa caméra aux quatre coins du globe dans des conditions exceptionnelles. Des expériences uniques qui lui ont ouvert les portes des ciné-conférences. Un regard positif assumé sur le monde tout en racontant des histoires : des films sincères, teintés de poésie et profondément respectueux des pays hôtes. Après Cuba et le Sénégal, Marc a parcouru le Maroc avec son van aménagé, poursuivant encore et toujours ses rêves d’aventures. Membres Accès Plus: Veuillez prendre note que les rabais progressifs ne sont pas applicables pour cette représentation."}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/maroc-en-vanlife-les-grands-explorateurs/#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/22e76d88-593d-4e82-9c46-50cba5c009ed"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/04/Maroc20en20vanlife_-ON05rU7bqgIkg3JUM5V_.jpg"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/name":[{"@language":"fr","@value":"Maroc en vanlife\nLes Grands Explorateurs"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_maroc-en-vanlife-les-grands-explorateurs#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-02T16:30:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culture3r-com_maroc-en-vanlife-les-grands-explorateurs#2026-05-02T133000-0400"},{"@id":"http://kg.footlight.io/resource/culture3r-com_maroc-en-vanlife-les-grands-explorateurs#2026-05-02T163000-0400"}]},{"@id":"https://www.culture3r.com/evenements/maroc-en-vanlife-les-grands-explorateurs/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:31:27.448-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/maroc-en-vanlife-les-grands-explorateurs/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_maroc-en-vanlife-les-grands-explorateurs#Offer_0","http://schema.org/price":[{"@value":"33.92"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_maroc-en-vanlife-les-grands-explorateurs#2026-05-02T133000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Maroc en vanlife\nLes Grands Explorateurs"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-02T13:30:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_maroc-en-vanlife-les-grands-explorateurs#2026-05-02T163000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Maroc en vanlife\nLes Grands Explorateurs"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-02T16:30:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_marina-orsini-reconstruire-les-saisons","@type":["http://schema.org/Event"],"http://schema.org/description":[{"@language":"fr","@value":"Marina Orsini, comédienne et animatrice parmi les plus appréciées du Québec, proposera son tout premier spectacle original, Reconstruire les saisons, dès novembre 2025. Auteure et interprète d’une grande sensibilité, elle partagera avec émotion son parcours artistique, accompagnée par le quatuor à cordes Mommies on the Run, sous la direction musicale de Catherine Major. Le spectacle, du même nom que son album sorti le 14 février dernier, mettra en lumière des thèmes poignants tels que l’amour, le désir, la déchirure, le deuil, les femmes guerrières de sa famille et, bien sûr, la nature, son refuge. Marina Orsini dévoile enfin ses écrits intimes et sincères, dans une œuvre profondément ancrée dans la tradition de la chanson francophone. Billets en prévente exclusivement aux membres Accès plus dès maintenant ! Pas encore membre? Abonnez-vous ici: https://bit.ly/devenir-membre-acces-plus Vente grand public le 21 mars 2025 "}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-01T16:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/marina-orsini-reconstruire-les-saisons/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Marina Orsini\nReconstruire les saisons"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/marina-orsini-reconstruire-les-saisons/"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/03/Marina20Orsini_-OLJs2RLqLsg__X2fcyy_-1.jpg"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_marina-orsini-reconstruire-les-saisons#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_marina-orsini-reconstruire-les-saisons#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_marina-orsini-reconstruire-les-saisons#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_marina-orsini-reconstruire-les-saisons#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/marina-orsini-reconstruire-les-saisons/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:32:11.862-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/marina-orsini-reconstruire-les-saisons/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_marina-orsini-reconstruire-les-saisons#Offer_0","http://schema.org/price":[{"@value":"52.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_marina-orsini-reconstruire-les-saisons#Offer_1","http://schema.org/price":[{"@value":"47.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_marina-orsini-reconstruire-les-saisons#Offer_2","http://schema.org/price":[{"@value":"45.25"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_marina-orsini-reconstruire-les-saisons#Offer_3","http://schema.org/price":[{"@value":"43.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_ouganda-aux-sources-du-nil-les-grands-explorateurs","http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/22e76d88-593d-4e82-9c46-50cba5c009ed"}],"http://schema.org/description":[{"@language":"fr","@value":"Ouganda, aux sources du Nil – Rencontre avec Caroline Riegel Au milieu du XIXème siècle, les explorateurs Richard Burton et John Speke s’affrontent pour découvrir l’énigme géographique des sources du Nil. À cette époque, le Nil est navigable jusqu’à Gondokoro, au nord de l’Ouganda. Au-delà, la région ne peut être visitée qu’à pied, en traversant de vastes régions où les trafiquants d’esclaves et autres bandits règnent en maîtres. De l’Ouganda aujourd’hui, nous connaissons si peu, marqués par le règne sanglant d’Amin Dada. Or ce pays a tout pour mériter le beau titre que lui avait accordé Winston Churchill : la Perle d’Afrique. Une terre de luxuriance végétale et animale, baignée de rires et de lumière. Mandatée sur un chantier de construction de barrage sur le Nil, Caroline s’immerge et découvre la magie de ce haut-plateau où il fait si bon vivre. Ce film est un voyage au cœur des sources du Nil Blanc, au gré des chuchotements des personnages qui les ont explorées, entre les rives du lac Victoria et les pentes des Montagnes de la Lune, à la découverte de la Perle d’Afrique, sublime. Biographie Ingénieure en constructions hydrauliques, réalisatrice, écrivaine, photographe, humanitaire et conférencière, Caroline Riegel est une grande voyageuse, dont la soif de comprendre le monde motive les pas et les rencontres. En guise de voyage initiatique, elle a traversé l’Asie au fil de l’eau, du lac Baïkal au Golfe du Bengale, à dos de chameau, de cheval, d’âne, sur une bicyclette indienne… De ce voyage elle a tiré un récit en deux tomes, Soifs d’Orient et Méandres d’Asie. Elle a aussi gardé un attachement particulier au Zanskar, vallée perchée à 3 500 m dans l’Himalayen indien, où elle retourne chaque année depuis quinze années. De son incroyable histoire d’amitié avec les nonnes d’un petit village de cette vallée reculée, elle a réalisé le film « Semeuses de Joie », largement salué par la critique. Sa vie oscille entre de grands chantiers à travers le monde et le Zanskar devenu terre d’adoption. Membres Accès Plus: Veuillez prendre note que les rabais progressifs ne sont pas applicables pour cette représentation."}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/04/Ouganda2C20aux20sources20du20Nil_-ON-y1vUTc7CjuZAKCQa_.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-14T13:30:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/ouganda-aux-sources-du-nil-les-grands-explorateurs/#WebPage"}],"http://schema.org/name":[{"@language":"fr","@value":"Ouganda, aux sources du Nil\nLes Grands Explorateurs"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/ouganda-aux-sources-du-nil-les-grands-explorateurs/"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_ouganda-aux-sources-du-nil-les-grands-explorateurs#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-14T16:30:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culture3r-com_ouganda-aux-sources-du-nil-les-grands-explorateurs#2026-03-14T133000-0400"},{"@id":"http://kg.footlight.io/resource/culture3r-com_ouganda-aux-sources-du-nil-les-grands-explorateurs#2026-03-14T163000-0400"}]},{"@id":"https://www.culture3r.com/evenements/ouganda-aux-sources-du-nil-les-grands-explorateurs/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:20:17.949-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/ouganda-aux-sources-du-nil-les-grands-explorateurs/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_ouganda-aux-sources-du-nil-les-grands-explorateurs#Offer_0","http://schema.org/price":[{"@value":"33.92"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_ouganda-aux-sources-du-nil-les-grands-explorateurs#2026-03-14T133000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Ouganda, aux sources du Nil\nLes Grands Explorateurs"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-14T13:30:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_ouganda-aux-sources-du-nil-les-grands-explorateurs#2026-03-14T163000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Ouganda, aux sources du Nil\nLes Grands Explorateurs"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-14T16:30:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_ostr-opera-bonbon-laventure-gourmande-dhansel-et-gretel-serie-paysages-musicaux","@type":["http://schema.org/Event"],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/description":[{"@language":"fr","@value":"Cette initiation à l’opéra et à la voix lyrique forme un spectacle délicieux qui se savoure comme un bonbon! Il était une fois – sol si do fa ! – deux chanteuses très rieuses qui voulaient raconter en musique l’histoire d’Hansel et Gretel. Leur copain très malin essaie de leur donner un coup de main, mais avec sa gourmandise et sa distraction, il a toujours les pieds dans les plats… surtout dans le plat de bonbons ! Comment faire pour retenir son attention ? Peut-être en lui confiant un rôle important dans l’histoire… Ce concert tout en drôlerie et en fantaisie revisite un conte bien connu des amateurs de gourmandises. Quoi de mieux pour découvrir un des arts les plus complets qui soit : l’opéra ! Membres Accès Plus: Veuillez prendre note que les rabais progressifs ne s’appliquent pas pour cette représentation."}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-25T11:00:00-05:00"}],"http://schema.org/name":[{"@language":"fr","@value":"OSTR – Opéra-bonbon : l’aventure gourmande d’Hansel et Gretel\nSérie Paysages musicaux"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/ostr-opera-bonbon-laventure-gourmande-dhansel-et-gretel-serie-paysages-musicaux/#WebPage"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/05/OSTR20-20OpC3A9ra-bonbon203A20l27aventure20gourmande20d27Hansel20et20Gretel_-OQFOCOv2N2Z2cHVV1HY_.jpg"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/ostr-opera-bonbon-laventure-gourmande-dhansel-et-gretel-serie-paysages-musicaux/"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_ostr-opera-bonbon-laventure-gourmande-dhansel-et-gretel-serie-paysages-musicaux#Offer_0"}]},{"@id":"https://www.culture3r.com/evenements/ostr-opera-bonbon-laventure-gourmande-dhansel-et-gretel-serie-paysages-musicaux/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-23T23:30:53.309-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/ostr-opera-bonbon-laventure-gourmande-dhansel-et-gretel-serie-paysages-musicaux/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_ostr-opera-bonbon-laventure-gourmande-dhansel-et-gretel-serie-paysages-musicaux#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"44.84"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_guy-nantel-si-je-comprends-bien-vous-etes-en-train-de-me-dire","http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/spec-qc-ca_artiste_guy-nantel"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/guy-nantel-si-je-comprends-bien-vous-etes-en-train-de-me-dire/"}],"http://schema.org/name":[{"@language":"fr","@value":"Guy Nantel\nRodage – Bienveillant"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/11/Guy20Nantel_-OdiEoan3DbkMiacQ4YM_.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-28T20:00:00-04:00"}],"http://schema.org/description":[{"@language":"fr","@value":"Alors que l’actualité regorge de sujets explosifs, d’opinions tranchées et de contradictions bien de chez nous, l’humoriste Guy Nantel revient avec un tout nouveau spectacle au titre pour le moins intrigant : Bienveillant. Oui, vous avez bien lu. BIENVEILLANT. Le mot qu’on associe le moins spontanément à un humoriste reconnu pour son franc-parler, son regard critique et son humour sociopolitique sans compromis. Disons que le titre donne le ton! Depuis toujours, Nantel est cette voix différente qui ose dire tout haut ce que tout le monde pense tout bas. Avec sa lucidité mordante et son ironie bien placée, il met le doigt sur nos travers collectifs, nos incohérences et nos grands débats d’époque, tout ça au grand plaisir du public. Avec ce 7e spectacle en carrière, Guy Nantel continue de faire ce qu’il fait le mieux : nous dérouter, nous désarmer et surtout nous faire rire à gorge déployée! Mais la question demeure : comment s’y prend-il pour le faire avec bienveillance? On ne demande qu’à voir… mais surtout à entendre. Avouez que ça promet!"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/guy-nantel-si-je-comprends-bien-vous-etes-en-train-de-me-dire/#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_guy-nantel-si-je-comprends-bien-vous-etes-en-train-de-me-dire#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_guy-nantel-si-je-comprends-bien-vous-etes-en-train-de-me-dire#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_guy-nantel-si-je-comprends-bien-vous-etes-en-train-de-me-dire#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_guy-nantel-si-je-comprends-bien-vous-etes-en-train-de-me-dire#Offer_3"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-29T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culture3r-com_guy-nantel-si-je-comprends-bien-vous-etes-en-train-de-me-dire#2026-08-28T200000-0400"},{"@id":"http://kg.footlight.io/resource/culture3r-com_guy-nantel-si-je-comprends-bien-vous-etes-en-train-de-me-dire#2026-08-29T200000-0400"}]},{"@id":"http://kg.footlight.io/resource/spec-qc-ca_artiste_guy-nantel","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@value":"Guy Nantel"}],"http://schema.org/image":[{"@value":"https://spec.qc.ca/uploads/artistes/guy-nantel-2.jpg"}],"http://schema.org/sameAs":[{"@value":"http://kg.artsdata.ca/resource/K12-194"}],"http://schema.org/description":[{"@language":"fr","@value":"Guy Nantel s'impose comme un humoriste unique et original. Avec ses prises de position, Nantel fait réfléchir, dénonce et met en évidence les contradictions de notre société en s'attaquant aux travers humains. Seul dans son créneau, son côté caustique et sarcastique donne un style singulier à cet humoriste populaire. Avec son nouveau spectacle, Corrompu, Guy Nantel analyse notre société et ses enjeux avec esprit et cynisme, fidèle à ses habitudes. Irrévérencieux, sans filtre et sans tabou, Nantel n’a pas son pareil! Scandales politiques, financiers, conspiration, collusion, corruption; il dénonce, propose et s'interroge. Dans ce quatrième one-man-show, il nous présente toute la vérité, rien que la vérité sur la société québécoise, mais également sur lui-même en se posant de multiples questions : Ferait-il un bon politicien? Succomberait-il à la corruption? Salué comme l’humoriste politique et social de sa génération, il se révèle de façon inattendue avec Corrompu. Fort du succès de son précédent spectacle, pour lequel il a vendu plus de 100 000 billets, Guy Nantel nous livre encore une fois un contenu à la hauteur des attentes, un contenu intelligent, engagé et hilarant !"}]},{"@id":"https://www.culture3r.com/evenements/guy-nantel-si-je-comprends-bien-vous-etes-en-train-de-me-dire/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:21:10.895-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/guy-nantel-si-je-comprends-bien-vous-etes-en-train-de-me-dire/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_guy-nantel-si-je-comprends-bien-vous-etes-en-train-de-me-dire#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"45.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_guy-nantel-si-je-comprends-bien-vous-etes-en-train-de-me-dire#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"41.20"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_guy-nantel-si-je-comprends-bien-vous-etes-en-train-de-me-dire#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"39.30"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_guy-nantel-si-je-comprends-bien-vous-etes-en-train-de-me-dire#Offer_3","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"37.40"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_guy-nantel-si-je-comprends-bien-vous-etes-en-train-de-me-dire#2026-08-28T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Guy Nantel\nRodage – Bienveillant"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-28T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_guy-nantel-si-je-comprends-bien-vous-etes-en-train-de-me-dire#2026-08-29T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Guy Nantel\nRodage – Bienveillant"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-29T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_sciences-po-101-traite-dinsoumission-a-lusage-du-vrai-monde-catherine-dorion","@type":["http://schema.org/Event"],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-28T19:00:00-04:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Sciences Po 101 – Traité d’insoumission à l’usage du vrai monde\nCatherine Dorion"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/sciences-po-101-traite-dinsoumission-a-lusage-du-vrai-monde-catherine-dorion/"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/sciences-po-101-traite-dinsoumission-a-lusage-du-vrai-monde-catherine-dorion/#WebPage"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/04/Sciences20PO201012C20TraitC3A920d27insoumission20C3A020l27usage20du20vrai20monde_-ON_Hgdcx81bPyyIQ1IA_.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"Théâtre documentaire J’avais envie de vous proposer du théâtre documentaire avec cette pièce qui vient raviver l’espoir pour notre société tout en apportant des pistes de réflexion. Cette œuvre met en lumière la soumission consciente de notre société tout en explorant des avenues pour retisser les liens essentiels à la vie collective. Un antidote au cynisme qui prouve que nous ne sommes pas seuls. – Lisa Dugré, programmatrice aux arts de la scène. Sciences Po 101, Traité d’insoumission à l’usage du vrai monde est un spectacle drôle, émouvant et intelligent qui s’inspire du vécu en politique de Catherine Dorion et de son ex-attaché politique Vincent Massé-Gagné, ainsi que de témoignages anonymes de politiciens, de journalistes et aussi de gens « ordinaires ». Peut-on encore espérer réparer le monde, ou bien tout est-il déjà foutu ? Sur scène, Catherine et son acolyte Vincent fouillent les manières dont le peuple pourrait transformer son sentiment d’impuissance en une puissante boussole de sens qui éloigne la peur et le raccorde à lui-même. Et ils se demandent s’il est possible, au travers des catastrophes dont regorgent les bulletins de nouvelles, des agendas remplis à ras bord et de la surcharge informationnelle ambiante, de renouer avec le désir de se fabriquer un monde meilleur. Durant la représentation, la nature interactive de la pièce permet au public de voter et de s’exprimer de diverses manières. Votre expérience de spectacle sera enrichie d’une médiation pour ouvrir le dialogue sur l’œuvre proposée : une présentation de la pièce en amont ainsi qu’une discussion de 15 minutes après la représentation. Ces moments d’échanges vous inviteront à tisser des liens, partager vos impressions et nourrir une réflexion collective. La culture devient alors un vecteur de rencontres et de changement. Auteurs et distribution: Catherine Dorion et Vincent Massé-Gagné Metteur en scène: Alexandre Fecteau. Distribution: sujette à changement sans préavis"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_sciences-po-101-traite-dinsoumission-a-lusage-du-vrai-monde-catherine-dorion#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_sciences-po-101-traite-dinsoumission-a-lusage-du-vrai-monde-catherine-dorion#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_sciences-po-101-traite-dinsoumission-a-lusage-du-vrai-monde-catherine-dorion#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_sciences-po-101-traite-dinsoumission-a-lusage-du-vrai-monde-catherine-dorion#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/sciences-po-101-traite-dinsoumission-a-lusage-du-vrai-monde-catherine-dorion/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:33:02.660-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/sciences-po-101-traite-dinsoumission-a-lusage-du-vrai-monde-catherine-dorion/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_sciences-po-101-traite-dinsoumission-a-lusage-du-vrai-monde-catherine-dorion#Offer_0","http://schema.org/price":[{"@value":"49.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_sciences-po-101-traite-dinsoumission-a-lusage-du-vrai-monde-catherine-dorion#Offer_1","http://schema.org/price":[{"@value":"44.80"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_sciences-po-101-traite-dinsoumission-a-lusage-du-vrai-monde-catherine-dorion#Offer_2","http://schema.org/price":[{"@value":"42.70"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_sciences-po-101-traite-dinsoumission-a-lusage-du-vrai-monde-catherine-dorion#Offer_3","http://schema.org/price":[{"@value":"40.60"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_kain-la-cour-des-grands-les-25-ans-de-kain","@type":["http://schema.org/Event"],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-13T20:00:00-05:00"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K5-432"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Kaïn\nLa cour des grands : les 25 ans de Kaïn"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/10/KaC3AFn_-OcpP7BmU0Nmf6cr678W_.jpg"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/kain-la-cour-des-grands-les-25-ans-de-kain/"}],"http://schema.org/description":[{"@language":"fr","@value":"L’infatigable groupe Kaïn se paie la traite pour ses 25 ans d’existence et s’offre une place dans la cour des grands, le temps d’un album. Un hommage à ses idoles Québécois de Desjardins à Mandeville, en passant par Plume, Pag, Piché, Séguin et d’autres influences importantes du band. Plus festif et vivant que jamais, le spectacle promet la fête et la grande soif! Kaïn, un incontournable party Québécois. "}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/kain-la-cour-des-grands-les-25-ans-de-kain/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_kain-la-cour-des-grands-les-25-ans-de-kain#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_kain-la-cour-des-grands-les-25-ans-de-kain#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_kain-la-cour-des-grands-les-25-ans-de-kain#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_kain-la-cour-des-grands-les-25-ans-de-kain#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/kain-la-cour-des-grands-les-25-ans-de-kain/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:33:14.726-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/kain-la-cour-des-grands-les-25-ans-de-kain/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_kain-la-cour-des-grands-les-25-ans-de-kain#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"47.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_kain-la-cour-des-grands-les-25-ans-de-kain#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"43.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_kain-la-cour-des-grands-les-25-ans-de-kain#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"41.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_kain-la-cour-des-grands-les-25-ans-de-kain#Offer_3","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"39.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_michel-barrette-drole-depuis-1957","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-284"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Michel Barrette\nDrôle depuis 1957"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/michel-barrette-drole-depuis-1957/"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-12-13T16:00:00-05:00"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/10/Michel20Barrette_-OcpRM1blgSYqr4WB4a8_.jpg"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/description":[{"@language":"fr","@value":"Dans son tout nouveau spectacle Drôle depuis 1957, Michel Barrette revient en force avec un regard hilarant et tendre sur sa génération. Il y célèbre « la revanche des baby-boomers », ces jeunes d’hier devenus les grands sages d’aujourd’hui… mais toujours aussi irrévérencieux. À travers anecdotes savoureuses, souvenirs touchants et observations mordantes, il nous raconte son époque avec une énergie contagieuse. Entre rires et réflexions, Michel prouve qu’on peut vieillir sans jamais cesser d’être drôle. Ce spectacle est une véritable lettre d’amour au passé… et un clin d’œil bien senti au présent. "}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/michel-barrette-drole-depuis-1957/#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_michel-barrette-drole-depuis-1957#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_michel-barrette-drole-depuis-1957#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_michel-barrette-drole-depuis-1957#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_michel-barrette-drole-depuis-1957#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/michel-barrette-drole-depuis-1957/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:33:25.492-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/michel-barrette-drole-depuis-1957/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_michel-barrette-drole-depuis-1957#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"49.00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_michel-barrette-drole-depuis-1957#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"44.80"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_michel-barrette-drole-depuis-1957#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"42.70"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_michel-barrette-drole-depuis-1957#Offer_3","http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"40.60"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_recits-en-resonance-2","@type":["http://schema.org/Event"],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-06-07"}],"http://schema.org/name":[{"@language":"fr","@value":"Récits en Résonance"}],"http://schema.org/description":[{"@language":"fr","@value":"Dans Récits en Résonance, Olivier Ricard propose une installation vidéo immersive à multi-écrans qui place le spectateur au cœur d’une réflexion sur les dynamiques de l’information et de la désinformation à l’ère des réseaux sociaux. L’œuvre met en scène l’actrice Élizabeth Darveau et explore la notion de « fiction individuelle », où des narrations personnelles se superposent et se multiplient à travers une répétition constante. Inspirée par le phénomène des chambres d’écho sur les plateformes numériques, Ricard interroge comment la répétition d’idées sans remise en question peut accentuer la polarisation des discours. En plongeant le spectateur dans un environnement audiovisuel où les récits se répercutent et se déforment, l’œuvre invite à une réflexion sur les implications sociales et psychologiques de ces espaces numériques, et sur l’impact de la répétition sur la construction de nos perceptions du monde. Artiste : Olivier Ricard Une réflexion sur les dynamiques de l’information et de la désinformation à l’ère des réseaux sociaux. "}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/recits-en-resonance-2/#WebPage"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/11/2026-av-epj-expo-olivier-ricard-photo.jpg"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/recits-en-resonance-2/"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/7826846d-0f28-44b4-9bc9-8b61b2536b81"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2026-03-21"}]},{"@id":"https://www.culture3r.com/evenements/recits-en-resonance-2/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:33:34.646-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/recits-en-resonance-2/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_dom-babin-rodage-2","http://schema.org/description":[{"@language":"fr","@value":"En passant, c’est du rodage. Pas juste du spectacle, j’rode aussi ma personne. J’veux pas être le dude qui finit sa personnalité en même temps que l’secondaire, qui arrive à 30 ans pis fait des stories sur Snapchat. Ça fait que j’essaye de pas m’prendre pour acquis. Mais chose sûre, c’est qu’une grosse partie de qui tu es dépend d’où tu viens. J’descends d’une grande famille de débrouillards de la Gaspésie qui a fini par make it. Des pauvres devenus riches en gardant leurs valeurs de pauvres. J’trouve qu’il y a quelque chose de poétique quand je vois des millionnaires qui mange du stew au baloney à Noël. Mes parents ont tellement make it qu’ils ont déménagé à Rimouski. Ouais, dans l’temps c’était ça make it. J’ai grandi d’une place qui s’passe rien fait que tu sais toute. Y’a aucun crime mais y’a paquet de témoins. Tout le monde voit les mêmes choses mais l’entend différemment. Ça fait que quand j’raconte mes histoires, c’est pour que t’entendes les tiennes. J’descends faire le tour. Viens rire de moi, viens rire de toi, viens rire de nous. "}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/03/Dom20Babin_-OMN1Q4OW1YUxXRTeoVs_.jpg"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/name":[{"@language":"fr","@value":"Dom Babin\nRodage"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/dom-babin-rodage-2/#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-14T20:00:00-04:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/dom-babin-rodage-2/"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_dom-babin-rodage-2#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_dom-babin-rodage-2#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_dom-babin-rodage-2#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_dom-babin-rodage-2#Offer_3"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-15T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culture3r-com_dom-babin-rodage-2#2026-08-14T200000-0400"},{"@id":"http://kg.footlight.io/resource/culture3r-com_dom-babin-rodage-2#2026-08-15T200000-0400"}]},{"@id":"https://www.culture3r.com/evenements/dom-babin-rodage-2/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:33:37.717-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/dom-babin-rodage-2/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_dom-babin-rodage-2#Offer_0","http://schema.org/price":[{"@value":"38.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_dom-babin-rodage-2#Offer_1","http://schema.org/price":[{"@value":"34.70"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_dom-babin-rodage-2#Offer_2","http://schema.org/price":[{"@value":"33.05"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_dom-babin-rodage-2#Offer_3","http://schema.org/price":[{"@value":"31.40"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_dom-babin-rodage-2#2026-08-14T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Dom Babin\nRodage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-14T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_dom-babin-rodage-2#2026-08-15T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Dom Babin\nRodage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-15T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_orchestre-pop-entre-vous-et-moi-avec-sylvie-fortin","http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/orchestre-pop-entre-vous-et-moi-avec-sylvie-fortin/#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/name":[{"@language":"fr","@value":"Orchestre Pop\n« Entre vous et moi » avec Sylvie Fortin"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/06/Orchestre20Pop_-OSVNOjZtsXM3dKM61Gv_.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-30T20:00:00-04:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/description":[{"@language":"fr","@value":"Sylvie Fortin est native de St-Tite en Mauricie. On dit d’elle qu’elle s’imprègne du talent de son grand-père, le ténor Honoré Fortin, en partageant son amour de la musique dans des soirées diversifiées depuis plus de 30 ans. Dans les années 90 elle fait partie d’une Revue Musicale « D’Elvis à Céline » durant 5 ans. En 2000, avec la troupe « Les Crooners » elle a participé à une série de spectacles au sein de la formation « Viva Casino ». Elle a peaufiné son art avec le ténor trifluvien Francesco Verrecchia avec qui elle chante toujours à l’occasion. En Mars 2024 elle a fait 2 représentations de son propre spectacle à la salle Anaïs Rousseau accompagné de l’orchestre Pop de Trois Rivières. Depuis 2021, elle parcourt le Québec , en tournée avec le chanteur et humoriste Steeve Diamond pour le spectacle « Bocelli \u0026amp; Friends ». Mezzo, soprano ou contralto? Sylvie Fortin possède une voix tantôt suave et touchante, tantôt ronde et puissante, d’une élégance et d’une force vocale magnifique. Elle vous arrachera une larme et vous redonnera le sourire. L’ORCHESTRE POP « sous la direction de Pierre VERRETTE » Membres Accès Plus: Veuillez prendre note que les rabais progressifs ne sont pas applicables pour ces représentations"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/orchestre-pop-entre-vous-et-moi-avec-sylvie-fortin/"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_orchestre-pop-entre-vous-et-moi-avec-sylvie-fortin#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-31T14:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culture3r-com_orchestre-pop-entre-vous-et-moi-avec-sylvie-fortin#2026-05-30T200000-0400"},{"@id":"http://kg.footlight.io/resource/culture3r-com_orchestre-pop-entre-vous-et-moi-avec-sylvie-fortin#2026-05-31T140000-0400"}]},{"@id":"https://www.culture3r.com/evenements/orchestre-pop-entre-vous-et-moi-avec-sylvie-fortin/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:33:48.637-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/orchestre-pop-entre-vous-et-moi-avec-sylvie-fortin/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_orchestre-pop-entre-vous-et-moi-avec-sylvie-fortin#Offer_0","http://schema.org/price":[{"@value":"44.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_orchestre-pop-entre-vous-et-moi-avec-sylvie-fortin#2026-05-30T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Orchestre Pop\n« Entre vous et moi » avec Sylvie Fortin"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-30T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_orchestre-pop-entre-vous-et-moi-avec-sylvie-fortin#2026-05-31T140000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Orchestre Pop\n« Entre vous et moi » avec Sylvie Fortin"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-31T14:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_vincent-vallieres-au-travers-de-la-route","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-1001"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/vincent-vallieres-au-travers-de-la-route/"}],"http://schema.org/description":[{"@language":"fr","@value":"Vincent Vallières revient sur scène entouré de ses musiciens pour un tout nouveau spectacle, où son imposant répertoire s’unit aux pièces de son nouvel album à paraître à l’automne 2025. Un spectacle à l’image de cet incontournable de la chanson québécoise: rassembleur, vivant et authentique. "}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/04/Vincent20ValliC3A8res_-OOcAjP9qbEcBR2bjbQ-_.jpg"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/name":[{"@language":"fr","@value":"Vincent Vallières\nAu travers de la route"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-11T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/vincent-vallieres-au-travers-de-la-route/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_vincent-vallieres-au-travers-de-la-route#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_vincent-vallieres-au-travers-de-la-route#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_vincent-vallieres-au-travers-de-la-route#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_vincent-vallieres-au-travers-de-la-route#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/vincent-vallieres-au-travers-de-la-route/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:33:58.217-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/vincent-vallieres-au-travers-de-la-route/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_vincent-vallieres-au-travers-de-la-route#Offer_0","http://schema.org/price":[{"@value":"49.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_vincent-vallieres-au-travers-de-la-route#Offer_1","http://schema.org/price":[{"@value":"44.80"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_vincent-vallieres-au-travers-de-la-route#Offer_2","http://schema.org/price":[{"@value":"42.70"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_vincent-vallieres-au-travers-de-la-route#Offer_3","http://schema.org/price":[{"@value":"40.60"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-hebert-grandiose-rodage","http://schema.org/description":[{"@language":"fr","@value":"17 et 18 FÉVRIER 2026 en RODAGE 18 AVRIL 2026 15 h et 20 h spectacle « GRANDIOSE » Tous les matins, on se lève avec le même objectif : que notre vie soit grandiose! On en rêve, on s’épuise, on se dénature, on se casse la gueule et parfois, pendant un court instant, on y arrive. C’est à ce moment qu’on réalise que tout ça en valait la peine. Tout ce chemin, tous ces efforts pour vivre ce moment parfait qui veut tout dire, avant que tout se termine pour de bon. Quatre ans après la fin de la tournée Le goût du risque, Pierre Hébert reprend enfin la route avec son nouveau spectacle : Grandiose. On y retrouve l’humoriste souvent moqueur, parfois touchant, qui porte un regard plein d’humour et d’émerveillement sur le monde qui l’entoure. 12 ANS ET PLUS"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/pierre-hebert-grandiose-rodage/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/04/Pierre20HC3A9bert_-OMlewRe1YnxXCGfi1XP_.jpg"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/pierre-hebert-grandiose-rodage/"}],"http://schema.org/name":[{"@language":"fr","@value":"Pierre Hébert\nGrandiose"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-17T20:00:00-05:00"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-hebert-grandiose-rodage#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-hebert-grandiose-rodage#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-hebert-grandiose-rodage#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-hebert-grandiose-rodage#Offer_3"},{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-hebert-grandiose-rodage#Offer_4"},{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-hebert-grandiose-rodage#Offer_5"},{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-hebert-grandiose-rodage#Offer_6"},{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-hebert-grandiose-rodage#Offer_7"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-18T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-hebert-grandiose-rodage#2026-02-17T200000-0500"},{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-hebert-grandiose-rodage#2026-02-18T200000-0500"},{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-hebert-grandiose-rodage#2026-04-18T200000-0400"},{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-hebert-grandiose-rodage#2026-04-18T150000-0400"}]},{"@id":"https://www.culture3r.com/evenements/pierre-hebert-grandiose-rodage/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:34:17.020-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/pierre-hebert-grandiose-rodage/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-hebert-grandiose-rodage#Offer_0","http://schema.org/price":[{"@value":"44.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-hebert-grandiose-rodage#Offer_1","http://schema.org/price":[{"@value":"40.30"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-hebert-grandiose-rodage#Offer_2","http://schema.org/price":[{"@value":"38.45"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-hebert-grandiose-rodage#Offer_3","http://schema.org/price":[{"@value":"36.60"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-hebert-grandiose-rodage#Offer_4","http://schema.org/price":[{"@value":"53.00"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-hebert-grandiose-rodage#Offer_5","http://schema.org/price":[{"@value":"48.40"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-hebert-grandiose-rodage#Offer_6","http://schema.org/price":[{"@value":"46.10"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-hebert-grandiose-rodage#Offer_7","http://schema.org/price":[{"@value":"43.80"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-hebert-grandiose-rodage#2026-02-17T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Pierre Hébert\nGrandiose"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-17T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-hebert-grandiose-rodage#2026-02-18T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Pierre Hébert\nGrandiose"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-18T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-hebert-grandiose-rodage#2026-04-18T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Pierre Hébert\nGrandiose"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-18T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_pierre-hebert-grandiose-rodage#2026-04-18T150000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Pierre Hébert\nGrandiose"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-18T15:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_lim-ligue-dimprovisation-mauricienne-2","http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"LIM – Ligue d’improvisation mauricienne\nLA LIM"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2023/01/logo-lim-2025-scaled.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/lim-ligue-dimprovisation-mauricienne-2/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/lim-ligue-dimprovisation-mauricienne-2/"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-03T19:30:00-05:00"}],"http://schema.org/description":[{"@language":"fr","@value":"Les soirées de la LIM mettent en scène à tous les mardis deux équipes qui s’unissent pour le grand plaisir du public! Sous la direction d’un maître de jeu, des sketchs, histoires, chansons, drames et blagues s’enchaînent sans relâche pendant 2 heures. Tous les joueurs sont à la fois comédiens, auteurs, metteurs en scène, chanteurs et plus encore! Membres Accès Plus: Veuillez prendre note qu’exceptionnellement les rabais progressifs ne sont pas applicables pour cette représentation. "}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_lim-ligue-dimprovisation-mauricienne-2#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-28T19:30:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culture3r-com_lim-ligue-dimprovisation-mauricienne-2#2026-02-03T193000-0500"},{"@id":"http://kg.footlight.io/resource/culture3r-com_lim-ligue-dimprovisation-mauricienne-2#2026-02-10T193000-0500"},{"@id":"http://kg.footlight.io/resource/culture3r-com_lim-ligue-dimprovisation-mauricienne-2#2026-02-24T193000-0500"},{"@id":"http://kg.footlight.io/resource/culture3r-com_lim-ligue-dimprovisation-mauricienne-2#2026-03-03T193000-0500"},{"@id":"http://kg.footlight.io/resource/culture3r-com_lim-ligue-dimprovisation-mauricienne-2#2026-03-10T193000-0400"},{"@id":"http://kg.footlight.io/resource/culture3r-com_lim-ligue-dimprovisation-mauricienne-2#2026-03-17T193000-0400"},{"@id":"http://kg.footlight.io/resource/culture3r-com_lim-ligue-dimprovisation-mauricienne-2#2026-03-24T193000-0400"},{"@id":"http://kg.footlight.io/resource/culture3r-com_lim-ligue-dimprovisation-mauricienne-2#2026-04-14T193000-0400"},{"@id":"http://kg.footlight.io/resource/culture3r-com_lim-ligue-dimprovisation-mauricienne-2#2026-04-21T193000-0400"},{"@id":"http://kg.footlight.io/resource/culture3r-com_lim-ligue-dimprovisation-mauricienne-2#2026-04-28T193000-0400"}]},{"@id":"https://www.culture3r.com/evenements/lim-ligue-dimprovisation-mauricienne-2/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:34:26.939-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/lim-ligue-dimprovisation-mauricienne-2/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_lim-ligue-dimprovisation-mauricienne-2#Offer_0","http://schema.org/price":[{"@value":"15.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_lim-ligue-dimprovisation-mauricienne-2#2026-02-03T193000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"LIM – Ligue d’improvisation mauricienne\nLA LIM"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-03T19:30:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_lim-ligue-dimprovisation-mauricienne-2#2026-02-10T193000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"LIM – Ligue d’improvisation mauricienne\nLA LIM"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-10T19:30:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_lim-ligue-dimprovisation-mauricienne-2#2026-02-24T193000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"LIM – Ligue d’improvisation mauricienne\nLA LIM"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-24T19:30:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_lim-ligue-dimprovisation-mauricienne-2#2026-03-03T193000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"LIM – Ligue d’improvisation mauricienne\nLA LIM"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-03T19:30:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_lim-ligue-dimprovisation-mauricienne-2#2026-03-10T193000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"LIM – Ligue d’improvisation mauricienne\nLA LIM"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-10T19:30:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_lim-ligue-dimprovisation-mauricienne-2#2026-03-17T193000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"LIM – Ligue d’improvisation mauricienne\nLA LIM"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-17T19:30:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_lim-ligue-dimprovisation-mauricienne-2#2026-03-24T193000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"LIM – Ligue d’improvisation mauricienne\nLA LIM"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-24T19:30:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_lim-ligue-dimprovisation-mauricienne-2#2026-04-14T193000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"LIM – Ligue d’improvisation mauricienne\nLA LIM"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-14T19:30:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_lim-ligue-dimprovisation-mauricienne-2#2026-04-21T193000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"LIM – Ligue d’improvisation mauricienne\nLA LIM"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-21T19:30:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_lim-ligue-dimprovisation-mauricienne-2#2026-04-28T193000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"LIM – Ligue d’improvisation mauricienne\nLA LIM"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-28T19:30:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mario-tessier","@type":["http://schema.org/Event"],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-249"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"\n Champion "}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/01/Mario20Tessier_-OHcbAAtbE-gggA-BCyg_.png"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/mario-tessier/"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/mario-tessier/#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"L’humoriste Mario Tessier présente son 3e one-man-show intitulé Champion, alors qu’il est de retour avec l’énergie explosive qu’on lui connait. Champion, c’est un concentré d’humour irrésistible qui mélange autodérision et situations improbables. Dans ce spectacle, Mario affronte ses propres contradictions avec une dose de folie : suivre un entraînement de boxe professionnel juste après une séance de botox, devenir propriétaire d’une jument alors qu’il n’est jamais monté à cheval ou encore se prendre pour un génie alors que… bon, vous voyez le tableau! Avec Champion, Mario met en lumière ce « grand champion » qui sommeille en chacun de nous, celui qui nous pousse à viser plus haut… ou à nous ridiculiser de la plus drôle des façons. Si vous l’aimez à la radio sur Énergie, attendez de le voir sur scène! Il vous fera encore plus rire grâce à son énergie contagieuse, son efficacité comique légendaire et cette tête de vainqueur qu’on ne peut qu’aimer! "}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-29T20:00:00-04:00"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culture3r-com_mario-tessier#Offer_0"},{"@id":"http://kg.footlight.io/resource/culture3r-com_mario-tessier#Offer_1"},{"@id":"http://kg.footlight.io/resource/culture3r-com_mario-tessier#Offer_2"},{"@id":"http://kg.footlight.io/resource/culture3r-com_mario-tessier#Offer_3"}]},{"@id":"https://www.culture3r.com/evenements/mario-tessier/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:34:07.607-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/mario-tessier/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mario-tessier#Offer_0","http://schema.org/price":[{"@value":"45.00"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix régulier*"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mario-tessier#Offer_1","http://schema.org/price":[{"@value":"41.20"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 10%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mario-tessier#Offer_2","http://schema.org/price":[{"@value":"39.30"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 15%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_mario-tessier#Offer_3","http://schema.org/price":[{"@value":"37.40"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Prix accès plus* 20%"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_gong-show-2","@type":["http://schema.org/Event"],"http://schema.org/description":[{"@language":"fr","@value":"Tout le monde peut être drôle, mais est-ce que n’importe qui peut l’être sur scène? C’est l’opportunité que Le Gong Show offre! Charles Deschamps, Antoni Remillard et un juge invité donnent la chance à n’importe qui d’essayer le stand-up. L’attrappe ? Ils n’ont que 3 minutes pour réussir à faire rire les juges, sinon ils se font arrêter par le gong! Évidemment, le but n’est pas d’être méchant envers les participants… mais clairement pas d’être gentil. Le Gong Show, c’est un spectacle qui fait place à la découverte pour le meilleur ou pour le pire! ● N.B : Le spectacle fait l’objet d’une captation. BILLETS "}],"http://schema.org/name":[{"@language":"fr","@value":"GONG SHOW"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/gong-show-2/"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-04T23:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/gong-show-2/#WebPage"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/12/gong26-post.png"}]},{"@id":"https://www.culture3r.com/evenements/gong-show-2/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:34:36.287-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/gong-show-2/"}]},{"@id":"http://kg.footlight.io/resource/culture3r-com_jerry-tremblay","@type":["http://schema.org/Event"],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.culture3r.com/evenements/jerry-tremblay/#WebPage"}],"http://schema.org/name":[{"@language":"fr","@value":"JERRY TREMBLAY"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/jerry-tremblay/"}],"http://schema.org/image":[{"@value":"https://www.culture3r.com/app/uploads/2025/12/jerry26-c3r-fiche-web-scaled.png"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74"}],"http://schema.org/description":[{"@language":"fr","@value":"Cultivateur de malaise expérimenté mais involontaire, Jerry rayonne par son manque d’habileté sociale et son désir profond d’être aimé. Le spectacle, un cocktail explosif de cirque et de malaises croustillants, offre une plongée à la fois hilarante et touchante dans les réalités parfois absurdes de la quête amoureuse des cœurs désespérés. Pour le meilleur et pour le pire, Jerry Tremblay vous garantit une soirée inoubliable remplie de surprises. BILLETS "}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-06T18:45:00-04:00"}]},{"@id":"https://www.culture3r.com/evenements/jerry-tremblay/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T23:34:39.145-05:00"}],"http://schema.org/url":[{"@value":"https://www.culture3r.com/evenements/jerry-tremblay/"}]},{"@id":"http://kg.footlight.io/resource/5cd258ca-a189-422f-978e-25696bac1d4e#PostalAddress","@type":["http://schema.org/PostalAddress"],"http://schema.org/streetAddress":[{"@language":"fr","@value":"3175 Boulevard Laviolette"}],"http://schema.org/postalCode":[{"@value":"G8Z 3W2"}],"http://schema.org/addressLocality":[{"@language":"fr","@value":"Trois-Rivières"}],"http://schema.org/addressRegion":[{"@language":"fr","@value":"QC"}],"http://schema.org/addressCountry":[{"@language":"fr","@value":"CA"}]},{"@id":"http://kg.footlight.io/resource/2465b8a9-1e12-438a-8d86-0c5a2ce42d74#PostalAddress","@type":["http://schema.org/PostalAddress"],"http://schema.org/addressLocality":[{"@language":"fr","@value":"Trois-Rivières"}],"http://schema.org/postalCode":[{"@value":"G9A 5H3"}],"http://schema.org/streetAddress":[{"@language":"fr","@value":"1425 Pl. de l'Hôtel de Ville"}],"http://schema.org/addressCountry":[{"@language":"fr","@value":"CA"}],"http://schema.org/addressRegion":[{"@language":"fr","@value":"QC"}]},{"@id":"http://kg.footlight.io/resource/efff2c7b-43e1-46e9-99c1-d50f6ab0e45a","@type":["http://schema.org/Place"],"http://schema.org/name":[{"@language":"fr","@value":"Maison de la culture de Trois-Rivières"}],"http://schema.org/address":[{"@id":"http://kg.footlight.io/resource/efff2c7b-43e1-46e9-99c1-d50f6ab0e45a#PostalAddress"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"art_gallery, tourist_attraction, museum, point_of_interest, establishment at 1425 Pl. de l'Hôtel de Ville 3e Étage, Trois-Rivières, QC G9A 5H3, Canada"}],"http://schema.org/longitude":[{"@value":"-72.5423793"}],"http://schema.org/latitude":[{"@value":"46.34285000000001"}],"http://schema.org/sameAs":[{"@value":"https://maps.google.com/?cid=2346479477994560617"}]},{"@id":"http://kg.footlight.io/resource/7cfa3a3f-d283-4e28-9f09-2b0756ac2e27#PostalAddress","@type":["http://schema.org/PostalAddress"],"http://schema.org/streetAddress":[{"@language":"fr","@value":"200 Av. des Draveurs"}],"http://schema.org/postalCode":[{"@value":"G9A 2J2"}],"http://schema.org/addressLocality":[{"@language":"fr","@value":"Trois-Rivières"}],"http://schema.org/addressRegion":[{"@language":"fr","@value":"QC"}],"http://schema.org/addressCountry":[{"@language":"fr","@value":"CA"}]},{"@id":"http://kg.footlight.io/resource/53301c32-b35a-447a-9c83-c53a865eefec#PostalAddress","@type":["http://schema.org/PostalAddress"],"http://schema.org/streetAddress":[{"@language":"fr","@value":"1425 Pl. de l'Hôtel de Ville"}],"http://schema.org/postalCode":[{"@value":"G9A 5H3"}],"http://schema.org/addressLocality":[{"@language":"fr","@value":"Trois-Rivières"}],"http://schema.org/addressRegion":[{"@language":"fr","@value":"QC"}],"http://schema.org/addressCountry":[{"@language":"fr","@value":"CA"}]},{"@id":"http://kg.footlight.io/resource/7826846d-0f28-44b4-9bc9-8b61b2536b81#PostalAddress","@type":["http://schema.org/PostalAddress"],"http://schema.org/streetAddress":[{"@language":"fr","@value":"150 Rue Fusey"}],"http://schema.org/postalCode":[{"@value":"G8T 2V8"}],"http://schema.org/addressLocality":[{"@language":"fr","@value":"Trois-Rivières"}],"http://schema.org/addressRegion":[{"@language":"fr","@value":"QC"}],"http://schema.org/addressCountry":[{"@language":"fr","@value":"CA"}]},{"@id":"http://kg.footlight.io/resource/c8cac7e7-aced-4da1-85f1-660a037d68cf#PostalAddress","@type":["http://schema.org/PostalAddress"],"http://schema.org/postalCode":[{"@value":"G9A"}],"http://schema.org/addressLocality":[{"@language":"fr","@value":"Trois-Rivières"}],"http://schema.org/addressRegion":[{"@language":"fr","@value":"QC"}],"http://schema.org/addressCountry":[{"@language":"fr","@value":"CA"}]},{"@id":"http://kg.footlight.io/resource/2cbd428a-4e42-4276-bfe1-de1d14d1ea98#PostalAddress","@type":["http://schema.org/PostalAddress"],"http://schema.org/addressCountry":[{"@language":"fr","@value":"Canada"}],"http://schema.org/streetAddress":[{"@language":"fr","@value":"100 Av. des Draveurs"}],"http://schema.org/addressLocality":[{"@language":"fr","@value":"Trois-Rivières"}],"http://schema.org/addressRegion":[{"@language":"fr","@value":"QC"}],"http://schema.org/postalCode":[{"@value":"G9A 5H3"}]},{"@id":"http://kg.footlight.io/resource/4ccaf1c3-26ff-41d6-84e5-b421483eeae3#PostalAddress","@type":["http://schema.org/PostalAddress"],"http://schema.org/streetAddress":[{"@language":"fr","@value":"1425 Pl. de l'Hôtel de Ville"}],"http://schema.org/postalCode":[{"@value":"G9A 5H3"}],"http://schema.org/addressLocality":[{"@language":"fr","@value":"Trois-Rivières"}],"http://schema.org/addressRegion":[{"@language":"fr","@value":"QC"}],"http://schema.org/addressCountry":[{"@language":"fr","@value":"CA"}]}]
\ No newline at end of file
diff --git a/data/migration_baseline/culturemauricie_lepointdevente_com.jsonld b/data/migration_baseline/culturemauricie_lepointdevente_com.jsonld
new file mode 100644
index 00000000..de16c0eb
--- /dev/null
+++ b/data/migration_baseline/culturemauricie_lepointdevente_com.jsonld
@@ -0,0 +1 @@
+[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_1rq251115001","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"The 80’s Rock Show au Trou du Diable"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/1RQ/25/11/15/001/1rq251115001-1152x648-fr-6823aff7.png"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/1rq251115001?lang=fr#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/896cb34d-9e47-4dcb-91e4-4b1eecc665d5"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/1rq251115001?lang=fr"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/description":[{"@language":"fr","@value":" The 80’s Rock Show à Sherbrooke – La fièvre des années 80 débarque au Centre Culturel Le Parvis!Préparez-vous à une soirée explosive de rock et de pop des années 80, alors queThe 80’s Rock Show prend d’assaut la salle Le Parvis!Un spectacle haut en couleur qui rend hommage aux plus grands hits d’une époque légendaire : Bon Jovi, Madonna, Journey, Queen, Def Leppard, Pat Benatar, Europe, Bryan Adams… et bien d’autres!Sur scène, une équipe de musiciens passionnés et de chanteurs charismatiques pour vous faire revivre l’âge d’or de la musique Rock et Pop. Tout y est : l’énergie, les costumes, les solos épiques et les harmonies qui donnent des frissons!Un party 80’s 100% live à ne pas manquer! "}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-15T20:00:00-05:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_1rq251115001#Offer_0"}]},{"@id":"https://lepointdevente.com/billets/1rq251115001?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-14T01:10:40.214-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/1rq251115001?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Musique"}],"http://www.w3.org/2004/02/skos/core#closeMatch":[{"@id":"http://kg.artsdata.ca/resource/MusicPerformance"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_1rq251115001#Offer_0","http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_comingbackst-narcisse","@type":["http://schema.org/Event"],"http://schema.org/description":[{"@language":"fr","@value":" Coming Back vous présente avec précision la presqu'intégralité de l'album \"Dark Side of the moon\", plusieurs pièces de l'album \"The Wall\" ainsi que plusieurs autres grands succès du mythique groupe Pink Floyd. Informations relatives au stationnement Stationnement disponible sur place "}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/comingbackst-narcisse?lang=fr"}],"http://schema.org/name":[{"@language":"fr","@value":"Hommage à Pink Floyd"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-29T20:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/comingbackst-narcisse?lang=fr#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/sameAs":[{"@value":"[\"https://www.facebook.com/ComingBackPinkFloyd\"]"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/TW1/24/02/24/001/comingbackhommage-1152x648-fr-65b1c2eb.png"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/674400f5-305a-49ed-b986-69efc5e7f725"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/UkQlddbY1f8?enablejsapi=1"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_comingbackst-narcisse#Offer_0"}]},{"@id":"https://lepointdevente.com/billets/comingbackst-narcisse?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-28T01:14:09.773-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/comingbackst-narcisse?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/674400f5-305a-49ed-b986-69efc5e7f725","@type":["http://schema.org/Place"],"http://schema.org/name":[{"@language":"fr","@value":"Centre communautaire - Henri St-Arnaud"}],"http://schema.org/address":[{"@id":"http://kg.footlight.io/resource/674400f5-305a-49ed-b986-69efc5e7f725#PostalAddress"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_comingbackst-narcisse#Offer_0","http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"35.0"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/674400f5-305a-49ed-b986-69efc5e7f725#PostalAddress","@type":["http://schema.org/PostalAddress"],"http://schema.org/addressCountry":[{"@language":"fr","@value":"Canada"}],"http://schema.org/streetAddress":[{"@language":"fr","@value":"1 Pl. du Centre"}],"http://schema.org/postalCode":[{"@value":"G0X 2Y0"}],"http://schema.org/addressLocality":[{"@language":"fr","@value":"Saint-Narcisse"}],"http://schema.org/addressRegion":[{"@language":"fr","@value":"QC"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_2560otstunt","@type":["http://schema.org/Event"],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-12-05T20:00:00-05:00"}],"http://schema.org/sameAs":[{"@value":"[\"http://www.facebook.com/outrunthesunrock\", \"https://www.facebook.com/TwentyFiveSixty.Band/\", \"https://www.facebook.com/outrunthesunrock/\"]"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/78173530-d109-47fa-82ad-1122216cee9d"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/2560otstunt?lang=fr"}],"http://schema.org/name":[{"@language":"fr","@value":"Twenty Five Sixty, Outrun The Sun \u0026 The Zoomiez @ Rock Café Le Stage"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/PHL/25/12/05/001/2560otstunt-1152x648-fr-691e536f.png"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/2560otstunt?lang=fr#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_2560otstunt#Offer_0"}]},{"@id":"http://kg.footlight.io/resource/78173530-d109-47fa-82ad-1122216cee9d","@type":["http://schema.org/Place"],"http://schema.org/name":[{"@language":"fr","@value":"Rock Café Le Stage"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"cafe, food, point_of_interest, establishment at 75 Rue Fusey, Trois-Rivières, QC G8T 2T9, Canada"}],"http://schema.org/address":[{"@id":"http://kg.footlight.io/resource/78173530-d109-47fa-82ad-1122216cee9d#PostalAddress"}],"http://schema.org/longitude":[{"@value":"-72.5276717"}],"http://schema.org/latitude":[{"@value":"46.36547789999999"}],"http://schema.org/sameAs":[{"@value":"https://maps.google.com/?cid=2999213381732431777"}]},{"@id":"https://lepointdevente.com/billets/2560otstunt?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-12-04T01:12:47.250-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/2560otstunt?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_2560otstunt#Offer_0","http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"http://schema.org/price":[{"@value":"16.26"}]},{"@id":"http://kg.footlight.io/resource/78173530-d109-47fa-82ad-1122216cee9d#PostalAddress","@type":["http://schema.org/PostalAddress"],"http://schema.org/streetAddress":[{"@language":"fr","@value":"75 Rue Fusey"}],"http://schema.org/postalCode":[{"@value":"G8T 2T9"}],"http://schema.org/addressLocality":[{"@language":"fr","@value":"Trois-Rivières"}],"http://schema.org/addressRegion":[{"@language":"fr","@value":"QC"}],"http://schema.org/addressCountry":[{"@language":"fr","@value":"CA"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_k4q251206001","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K13-28"}],"http://schema.org/name":[{"@language":"fr","@value":"Brigitte Boisjoli - Bring back '68 - Elvis"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-12-06T20:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/k4q251206001?lang=fr#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/K4Q/25/12/06/001/k4q251206001-1152x648-fr-67067713.png"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/k4q251206001?lang=fr"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/823bc9ee-c6da-4423-bab4-624043dd4f5d"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/description":[{"@language":"fr","@value":" Entourée de 4 musiciens, Brigitte Boisjoli fait revivre la fièvre Elvis Presley!« Elvis a marqué les générations par son talent, sa beauté et sa voix qui semblait sortie tout droit d’un chœur gospel. Un rock’n’roll qui a dérangé, qui s’est déhanché.Ce besoin urgent de chanter qui je suis, tout comme Elvis. Aujourd’hui, je le sens, je suis prête pour le rock’n’roll d’une vie, celle d’Elvis, mon Elvis! »Brigitte Boisjoli________________________________________________________________________________CHOISISSEZ BIEN VOS SIÈGES, LE BON TARIF ET LA BONNE SECTION! INSTALLEZ-VOUS BIEN, ÇA VA BRASSER!Besoin d'un p'tit coup de main? Mardi ou jeudi matin de 9H00 à 12H00 au (819) 531-1968. Demandez à Daniel.________________________________________________________________________________ "}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_k4q251206001#Offer_0"},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_k4q251206001#Offer_1"},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_k4q251206001#Offer_2"}]},{"@id":"https://lepointdevente.com/billets/k4q251206001?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-12-04T01:10:39.923-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/k4q251206001?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/823bc9ee-c6da-4423-bab4-624043dd4f5d","@type":["http://schema.org/Place"],"http://schema.org/name":[{"@language":"fr","@value":"Salle Gilles-Grondin, CÉGEP de Shawinigan"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"route at Rue Gilles-Grondin, Shawinigan, QC, Canada"}],"http://schema.org/address":[{"@id":"http://kg.footlight.io/resource/823bc9ee-c6da-4423-bab4-624043dd4f5d#PostalAddress"}],"http://schema.org/longitude":[{"@value":"-72.7320292"}],"http://schema.org/latitude":[{"@value":"46.5285704"}],"http://schema.org/sameAs":[{"@value":"https://maps.google.com/?q=Rue+Gilles-Grondin,+Shawinigan,+QC,+Canada\u0026ftid=0x4cc64c768610b595:0x4441781fa76a8172"}],"http://schema.org/alternateName":[{"@language":"fr","@value":"CÉGEP de Shawinigan"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_k4q251206001#Offer_0","http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"47.0"}],"http://schema.org/name":[{"@language":"fr","@value":"Régulier parterre - section rouge"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_k4q251206001#Offer_1","http://schema.org/price":[{"@value":"45.0"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Ainés - Parterre rouge"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_k4q251206001#Offer_2","http://schema.org/price":[{"@value":"28.0"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Balcon"}]},{"@id":"http://kg.footlight.io/resource/823bc9ee-c6da-4423-bab4-624043dd4f5d#PostalAddress","@type":["http://schema.org/PostalAddress"],"http://schema.org/postalCode":[{"@value":"G9N 6V8"}],"http://schema.org/streetAddress":[{"@language":"fr","@value":"2263 Boul Du Collège"}],"http://schema.org/addressLocality":[{"@language":"fr","@value":"Shawinigan"}],"http://schema.org/addressRegion":[{"@language":"fr","@value":"QC"}],"http://schema.org/addressCountry":[{"@language":"fr","@value":"CA"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_el2251206001","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/el2251206001?lang=fr"}],"http://schema.org/name":[{"@language":"fr","@value":"GET UP"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/d81014ce-3ff6-485f-a6ef-b88b4c6be329"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/1BOjbACoGYw?enablejsapi=1"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/3942d194-b200-4cd6-91cd-de49240ad644"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/el2251206001?lang=fr#WebPage"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/EL2/25/12/06/001/el2251206001-1152x648-fr-689e33ae.png"}],"http://schema.org/description":[{"@language":"fr","@value":" GET UP KARL BLANCHARDGet up ! C’est plus de deux heures de spectacle…Les années disco, les années 80 en passant par le country , le rock jusqu’aux plus récents hits !Vivez l’énergie de Get up ! * Les places sont assignées selon l'ordre d'achat de votre billet. Les tables et banquettes sont réservées pour les clients qui achètent le forfait souper-spectacle et les détenteurs de billets pour le spectacle seulement sont dirigés vers la section \"mezzanine\"SOUPER-SPECTACLE : OUVERTURE DES PORTES 17H30 | ARRIVÉE MAXIMUM 18H30 | repas 3 services suivi du spectacle * LES PRIX AFFICHÉS INCLUENT LES FRAIS DE SERVICE, LES TAXES ET LE POURBOIRE Menu : https://bit.ly/47JWlPRSPECTACLE SEULEMENT : 20h30 | arrivée à 20hPour toutes questions concernant votre achat en ligne: 1-855-550-7469 ou le 819-694-0555* Important : Pour les gens à mobilité réduite veuillez nous aviser de votre visite sur jpaul@lememphis.com* Billets non remboursables, échangeables sous certaines conditions* 18 ANS ET PLUS "}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-12-06T20:30:00-05:00"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_el2251206001#Offer_0"},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_el2251206001#Offer_1"}]},{"@id":"http://kg.footlight.io/resource/d81014ce-3ff6-485f-a6ef-b88b4c6be329","@type":["http://schema.org/Organization"],"http://schema.org/name":[{"@language":"fr","@value":"Le Memphis Cabaret"}],"http://schema.org/url":[{"@value":"https://www.facebook.com/MemphisCabaret/"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Organization"}]},{"@id":"http://kg.footlight.io/resource/3942d194-b200-4cd6-91cd-de49240ad644","@type":["http://schema.org/Place"],"http://schema.org/address":[{"@id":"http://kg.footlight.io/resource/3942d194-b200-4cd6-91cd-de49240ad644#PostalAddress"}],"http://schema.org/latitude":[{"@value":"46.3418388"}],"http://schema.org/longitude":[{"@value":"-72.5424953"}],"http://schema.org/name":[{"@language":"fr","@value":"Le Memphis Cabaret"}],"http://schema.org/sameAs":[{"@value":"https://maps.google.com/?cid=2221244181872585842"}],"http://schema.org/alternateName":[{"@language":"fr","@value":"Memphis Cabaret"}]},{"@id":"https://lepointdevente.com/billets/el2251206001?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-12-04T01:10:46.476-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/el2251206001?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_el2251206001#Offer_0","http://schema.org/price":[{"@value":"119.45"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Souper-Spectacle | entrée 17H30"}],"http://schema.org/availability":[{"@value":"http://schema.org/InStock"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_el2251206001#Offer_1","http://schema.org/price":[{"@value":"60.3"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Spectacle | entrée 20h"}]},{"@id":"http://kg.footlight.io/resource/3942d194-b200-4cd6-91cd-de49240ad644#PostalAddress","@type":["http://schema.org/PostalAddress"],"http://schema.org/addressCountry":[{"@language":"fr","@value":"CA"}],"http://schema.org/addressLocality":[{"@language":"fr","@value":"Trois-Rivières"}],"http://schema.org/addressRegion":[{"@language":"fr","@value":"QC"}],"http://schema.org/postalCode":[{"@value":"G9A 2G9"}],"http://schema.org/streetAddress":[{"@language":"fr","@value":"323 Rue des Forges"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_8uj251207001","@type":["http://schema.org/Event"],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/49299024-e10d-436f-b3b8-0802ce9e80de"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/8UJ/25/10/05/001/8uj251005001-1152x648-fr-68ca06fa.png"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/8uj251207001?lang=fr#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/name":[{"@language":"fr","@value":"Les dimanches Fou-Rire du Q\u0026F 7 décembre"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/8uj251207001?lang=fr"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-12-07T19:00:00-05:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_8uj251207001#Offer_0"}]},{"@id":"http://kg.footlight.io/resource/49299024-e10d-436f-b3b8-0802ce9e80de","@type":["http://schema.org/Place"],"http://schema.org/name":[{"@language":"fr","@value":"Quilles et Frites"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"bowling_alley, point_of_interest, establishment at 131 Rue du Moulin, Saint-Adelphe, QC G0X 2G0, Canada"}],"http://schema.org/address":[{"@id":"http://kg.footlight.io/resource/49299024-e10d-436f-b3b8-0802ce9e80de#PostalAddress"}],"http://schema.org/longitude":[{"@value":"-72.43026730000001"}],"http://schema.org/latitude":[{"@value":"46.7311096"}],"http://schema.org/sameAs":[{"@value":"https://maps.google.com/?cid=9605231375352640527"}]},{"@id":"https://lepointdevente.com/billets/8uj251207001?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-12-06T01:10:24.170-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/8uj251207001?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Humour"}],"http://www.w3.org/2004/02/skos/core#closeMatch":[{"@id":"http://kg.artsdata.ca/resource/ComedyPerformance"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_8uj251207001#Offer_0","http://schema.org/price":[{"@value":"25.0"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"http://schema.org/availability":[{"@value":"http://schema.org/InStock"}]},{"@id":"http://kg.footlight.io/resource/49299024-e10d-436f-b3b8-0802ce9e80de#PostalAddress","@type":["http://schema.org/PostalAddress"],"http://schema.org/streetAddress":[{"@language":"fr","@value":"131 Rue du Moulin"}],"http://schema.org/postalCode":[{"@value":"G0X 2G0"}],"http://schema.org/addressLocality":[{"@language":"fr","@value":"Saint-Adelphe"}],"http://schema.org/addressRegion":[{"@language":"fr","@value":"QC"}],"http://schema.org/addressCountry":[{"@language":"fr","@value":"CA"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_llockwell10122025","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Louis-Maxime Lockwell"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-12-10T19:30:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/llockwell10122025?lang=fr#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/5BQ/25/12/10/001/5bq251210001-1152x648-fr-68ef977c.png"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-698"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/llockwell10122025?lang=fr"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c04eff2a-2e39-44ba-8091-7f884250912d"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_llockwell10122025#Offer_0"}]},{"@id":"https://lepointdevente.com/billets/llockwell10122025?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-12-08T01:10:42.391-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/llockwell10122025?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/c04eff2a-2e39-44ba-8091-7f884250912d","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Arts littéraires"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_llockwell10122025#Offer_0","http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"http://schema.org/price":[{"@value":"11.14"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_tdd250206001","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Groovy Aardvark et The Matchup"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/TDD/25/02/06/001/tdd250206001-1152x648-fr-690dfe12.png"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/tdd250206001?lang=fr#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-12-12T20:00:00-05:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/tdd250206001?lang=fr"}],"http://schema.org/description":[{"@language":"fr","@value":" Groovy Aardvark, figure légendaire du rock alternatif québécois, enflamme les scènes depuis la fin des années 80 avec son énergie brute et ses mélodies percutantes. Le groupe a marqué des générations avec des classiques comme Y’a Tu Kelkun?, Dérangeant et Le p’tit bonheur. Reconnu pour ses concerts mémorables, Groovy Aardvark offre une expérience scénique intense et authentique. "}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/896cb34d-9e47-4dcb-91e4-4b1eecc665d5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_tdd250206001#Offer_0"}]},{"@id":"https://lepointdevente.com/billets/tdd250206001?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-12-10T01:10:42.168-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/tdd250206001?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_tdd250206001#Offer_0","http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"42.4"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_partytime","http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/event-groups/EL2/partytime-1152x648-fr-685421dd.png"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/name":[{"@language":"fr","@value":"PARTY TIME - Lepointdevente.com"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/partytime?lang=fr#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-12-12T20:30:00-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/partytime?lang=fr"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-706"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_partytime#Offer_0"},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_partytime#Offer_1"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-12-13T20:30:00-05:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_partytime#2025-12-12T203000-0500"},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_partytime#2025-12-13T203000-0500"}]},{"@id":"https://lepointdevente.com/billets/partytime?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-12-12T01:10:53.982-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/partytime?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_partytime#Offer_0","http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"125.36"}],"http://schema.org/name":[{"@language":"fr","@value":"Souper-Spectacle | entrée 17H30"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_partytime#Offer_1","http://schema.org/price":[{"@value":"66.21"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Spectacle | entrée 20h"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_partytime#2025-12-12T203000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"PARTY TIME - Lepointdevente.com"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-706"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-12-12T20:30:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_partytime#2025-12-13T203000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"PARTY TIME - Lepointdevente.com"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-706"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-12-13T20:30:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_9vh251216001","@type":["http://schema.org/Event"],"http://schema.org/description":[{"@language":"fr","@value":" ★★★ ST-TITE 16 DÉC 2025 à 19h30 - seulement 32,95$ ★★★(réduction 5$ le billets si vous achetez 4 billets ou plus en meme temps. Ca vaut la peine de trouver quelques amies)Et oui! L'événement « Sois ta meilleure amie ! » ENFIN À ST-TITE à la Salle La Ganterie, 445 du Moulin, St-TiteC'est une salle intime, parfaite pour vous accueillir, vous rencontrer et passer une super soirée avec vous. Il y a même un bar :)LA SOIRÉE LA PLUS DROLE ET POSITIVE. UN ÉVÉNEMENT À NE PAS MANQUER! ON A HATE DE VOUS RENCONTRER.Et malgré le nom c’est autant pour les hommes que les femmes! Louis-Philippe est là avec moi et les sujets s’adressent à tous.Une soirée qui a pour but de vous faire rire un bon coup et vous aider à jeter un regard plus positif sur votre vie ! Un moment drôle, lumineux, rempli d’énergie.----------------Faites vite ! On a hâte de vous voir. On vous accueille à la porte, on vous jase, on passe vraiment une soirée avec vous !A QUOI S’ATTENDRE?On parle de bonheur à travers l'amour de soi et l'auto-compassion. De voir la vie avec un œil plus positif. D’auto-dérision. De gratitude, qui fait toute la différence dans la vie. Vous voulez savoir c’est quoi le \" Ouin Pis?\" Les qualifauts! Le I know I know! C’est ce que vous allez apprendre.Mais au-delà de tout ça, c’est une soirée très drôle. On rit beaucoup.Mon but c’est que vous partiez avec un grand sourire dans le visage. C’est une soirée qui fait du bien.Venez seule, en couple ou avec votre belle gang! De toute façon on va rire en masse!Sois ta (et ton) meilleure amie! Ose tout ce qui t’es possible!19h30 à 21h3032.95$ plus taxes et frais de billeterie.27,95$ plus taxes et frais de billeterie Spécial 4 personnes ou plus.Vous êtes un groupe de 8 personnes ou plus? Écrivez-nous au joseeeventbrite@gmail.com pour voir ce qu'on peut faire pour vous.On a très hâte de vous voir, parlez-en à ceux que vous aimez!VOS BILLETS SERONT ENVOYÉS AUTOMATIQUEMENT DANS VOTRE BOITE COURRIEL. ASSUREZ-VOUS DE BIEN ÉCRIRE VOTRE COURRIEL EN COMMANDANT ET SI VOUS NE LES VOYEZ PAS APPARAITRE, REGARDEZ DANS VOS INDÉSIRABLES.VOUS POURREZ LES IMPRIMER, NOUS LES MONTRER SUR VOTRE TELEPHONE OU SIMPLEMENT NOUS DONNER VOTRE NOM À L'ENTRÉE. C'EST NOUS QUI VOUS ACCUEILLONS ET C'EST TRÈS FACILE. Y'A JAMAIS DE PROBLÈME :)Vous avez des questions après avoir acheté vos billets? Écrivez-nous au joseeeventbrite@gmail.com "}],"http://schema.org/name":[{"@language":"fr","@value":"ST-TITE SOIS TA MEILLEURE AMIE JOSEE BOUDREAULT"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/9VH/25/12/16/001/9vh251216001-1152x648-fr-68b5dbc2.png"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-12-16T19:30:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/9vh251216001?lang=fr#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/fab5149e-e51b-455e-ab32-2e594130131e"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/9vh251216001?lang=fr"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_9vh251216001#Offer_0"},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_9vh251216001#Offer_1"}]},{"@id":"https://lepointdevente.com/billets/9vh251216001?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-12-14T01:10:45.644-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/9vh251216001?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/fab5149e-e51b-455e-ab32-2e594130131e","@type":["http://schema.org/Place"],"http://schema.org/name":[{"@language":"fr","@value":"Salle La ganterie"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"street_address, subpremise at 445 Rue du Moulin, Saint-Tite, QC G0X 3H0, Canada"}],"http://schema.org/address":[{"@id":"http://kg.footlight.io/resource/fab5149e-e51b-455e-ab32-2e594130131e#PostalAddress"}],"http://schema.org/longitude":[{"@value":"-72.56497449999999"}],"http://schema.org/latitude":[{"@value":"46.7292151"}],"http://schema.org/sameAs":[{"@value":"https://maps.google.com/?cid=7415738794050940155"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_9vh251216001#Offer_0","http://schema.org/price":[{"@value":"39.67"}],"@type":["http://schema.org/Offer"],"http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_9vh251216001#Offer_1","http://schema.org/price":[{"@value":"33.79"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"RABAIS 4 PERSONNES OU PLUS"}]},{"@id":"http://kg.footlight.io/resource/fab5149e-e51b-455e-ab32-2e594130131e#PostalAddress","@type":["http://schema.org/PostalAddress"],"http://schema.org/streetAddress":[{"@language":"fr","@value":"445 Rue du Moulin"}],"http://schema.org/postalCode":[{"@value":"G0X 3H0"}],"http://schema.org/addressLocality":[{"@language":"fr","@value":"Saint-Tite"}],"http://schema.org/addressRegion":[{"@language":"fr","@value":"QC"}],"http://schema.org/addressCountry":[{"@language":"fr","@value":"CA"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_el2251219001","@type":["http://schema.org/Event"],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/d81014ce-3ff6-485f-a6ef-b88b4c6be329"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/name":[{"@language":"fr","@value":"ÉRIC MASSON"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/el2251219001?lang=fr#WebPage"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-12-19T20:30:00-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/el2251219001?lang=fr"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/EL2/25/12/19/001/el2251219001-1152x648-fr-68a5d53e.png"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/description":[{"@language":"fr","@value":" ÉRIC MASSON ET SON BANDLa réputation d’Éric Masson n’est plus à faire, dans une ambiance festive sans pareille et une complicité unique avec ses fidèles musiciens, Masson viendra faire sauter la baraque pour un soir seulement. Tout un party des fêtes vous attend!!!* Les places sont assignées selon l'ordre d'achat de votre billet. Les tables et banquettes sont réservées pour les clients qui achètent le forfait souper-spectacle et les détenteurs de billets pour le spectacle seulement sont dirigés vers la section \"mezzanine\"SOUPER-SPECTACLE : OUVERTURE DES PORTES 17H30 | ARRIVÉE MAXIMUM 18H30 | repas 3 services suivi du spectacle * LES PRIX AFFICHÉS INCLUENT LES FRAIS DE SERVICE, LES TAXES ET LE POURBOIRE Menu : https://bit.ly/47JWlPRSPECTACLE SEULEMENT : 20h30 | arrivée à 20hPour toutes questions concernant votre achat en ligne: 1-855-550-7469 ou le 819-694-0555* Important : Pour les gens à mobilité réduite veuillez nous aviser de votre visite sur jpaul@lememphis.com* Billets non remboursables, échangeables sous certaines conditions* 18 ANS ET PLUS "}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/3942d194-b200-4cd6-91cd-de49240ad644"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_el2251219001#Offer_0"}]},{"@id":"https://lepointdevente.com/billets/el2251219001?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-12-18T01:10:41.835-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/el2251219001?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_el2251219001#Offer_0","http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_el2251220001","@type":["http://schema.org/Event"],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/d81014ce-3ff6-485f-a6ef-b88b4c6be329"}],"http://schema.org/name":[{"@language":"fr","@value":"MARIE-ELAINE THIBERT - Magie de Noël"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/EL2/25/12/20/001/el2251220001-1152x648-fr-67fd2861.png"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-12-20T20:30:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/el2251220001?lang=fr#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/3942d194-b200-4cd6-91cd-de49240ad644"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/el2251220001?lang=fr"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/description":[{"@language":"fr","@value":" MARIE-ELAINE THIBERT - MAGIE DE NOËLMarie-Élaine Thibert et ses musiciens vous invitent à un spectacle de Noël haut en couleur. Ce moment magique propose plus d’une vingtaine de chansons issues du répertoire des fêtes, ponctuées d’anecdotes tantôt drôles, tantôt touchantes.Plongez dans l’univers où rythmes entraînants, mélodies douces et moments poignants se mêlent harmonieusement.Un voyage au pays des plus beaux souvenirs d’enfance.C’est un rendez-vous!* Les places sont assignées selon l'ordre d'achat de votre billet. Les tables et banquettes sont réservées pour les clients qui achètent le forfait souper-spectacle et les détenteurs de billets pour le spectacle seulement sont dirigés vers la section \"mezzanine\"SOUPER-SPECTACLE : OUVERTURE DES PORTES 17H30 | ARRIVÉE MAXIMUM 18H30 | repas 3 services suivi du spectacle * LES PRIX AFFICHÉS INCLUENT LES FRAIS DE SERVICE, LES TAXES ET LE POURBOIRE Menu : https://bit.ly/47JWlPRSPECTACLE SEULEMENT : 20h30 | arrivée à 20hPour toutes questions concernant votre achat en ligne: 1-855-550-7469 ou le 819-694-0555* Important : Pour les gens à mobilité réduite veuillez nous aviser de votre visite sur jpaul@lememphis.com* Billets non remboursables, échangeables sous certaines conditions* 18 ANS ET PLUS "}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_el2251220001#Offer_0"},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_el2251220001#Offer_1"}]},{"@id":"https://lepointdevente.com/billets/el2251220001?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-12-18T01:12:13.650-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/el2251220001?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_el2251220001#Offer_0","http://schema.org/price":[{"@value":"131.28"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Souper-Spectacle | entrée 17H30"}],"http://schema.org/availability":[{"@value":"http://schema.org/InStock"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_el2251220001#Offer_1","http://schema.org/price":[{"@value":"72.13"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Spectacle | entrée 20h"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_ux8251220001","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/ux8251220001?lang=fr"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/A5Kxf9sCBCY?enablejsapi=1"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-12-20T21:00:00-05:00"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/51f8b4cb-d760-43b0-ba6d-dfa8f342050b"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/sameAs":[{"@value":"[\"https://www.facebook.com/dogosuicideband\", \"https://www.facebook.com/thickglassespunk/\"]"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/ux8251220001?lang=fr#WebPage"}],"http://schema.org/name":[{"@language":"fr","@value":"DOGO SUICIDE | Lancement de \"Tristesse Lucrative\""}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/UX8/25/12/20/001/ux8251220001-1152x648-fr-68be30da.png"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_ux8251220001#Offer_0"}]},{"@id":"http://kg.footlight.io/resource/51f8b4cb-d760-43b0-ba6d-dfa8f342050b","@type":["http://schema.org/Place"],"http://schema.org/name":[{"@language":"fr","@value":"Le Zénob"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"bar, point_of_interest, establishment at 171 Rue Bonaventure, Trois-Rivières, QC G9A 2A9, Canada"}],"http://schema.org/address":[{"@id":"http://kg.footlight.io/resource/51f8b4cb-d760-43b0-ba6d-dfa8f342050b#PostalAddress"}],"http://schema.org/longitude":[{"@value":"-72.5407665"}],"http://schema.org/sameAs":[{"@value":"https://maps.google.com/?cid=4825518901611670357"},{"@value":"http://kg.artsdata.ca/resource/K5-487"}],"http://schema.org/latitude":[{"@value":"46.34320959999999"}],"http://schema.org/alternateName":[{"@language":"fr","@value":"Café-Bar Zénob, Le Zenob"}]},{"@id":"https://lepointdevente.com/billets/ux8251220001?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-12-18T01:12:24.336-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/ux8251220001?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_ux8251220001#Offer_0","http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"21.37"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/51f8b4cb-d760-43b0-ba6d-dfa8f342050b#PostalAddress","@type":["http://schema.org/PostalAddress"],"http://schema.org/streetAddress":[{"@language":"fr","@value":"171 Rue Bonaventure"}],"http://schema.org/postalCode":[{"@value":"G9A 2A9"}],"http://schema.org/addressLocality":[{"@language":"fr","@value":"Trois-Rivières"}],"http://schema.org/addressRegion":[{"@language":"fr","@value":"QC"}],"http://schema.org/addressCountry":[{"@language":"fr","@value":"CA"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_b6l251011001","@type":["http://schema.org/Event"],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K10-26"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-12-20T21:30:00-05:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/1e687886-dffe-4bd2-a41f-2a58f1f3af15"}],"http://schema.org/name":[{"@language":"fr","@value":"Éric Masson et ses musiciens (full Band)"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventRescheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/b6l251011001?lang=fr"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/B6L/25/10/11/001/b6l251011001-1152x648-fr-691e0be3.png"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/b6l251011001?lang=fr#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_b6l251011001#Offer_0"}]},{"@id":"http://kg.footlight.io/resource/1e687886-dffe-4bd2-a41f-2a58f1f3af15","@type":["http://schema.org/Place"],"http://schema.org/name":[{"@language":"fr","@value":"Broadway - Microbrasserie"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"night_club, bar, clothing_store, store, restaurant, point_of_interest, food, establishment at 1381 Av. de la Transmission, Shawinigan, QC G9N 8N8, Canada"}],"http://schema.org/address":[{"@id":"http://kg.footlight.io/resource/1e687886-dffe-4bd2-a41f-2a58f1f3af15#PostalAddress"}],"http://schema.org/longitude":[{"@value":"-72.74623059999999"}],"http://schema.org/latitude":[{"@value":"46.5487625"}],"http://schema.org/sameAs":[{"@value":"https://maps.google.com/?cid=3143362354521137244"}]},{"@id":"https://lepointdevente.com/billets/b6l251011001?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-12-18T01:10:54.040-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/b6l251011001?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_b6l251011001#Offer_0","http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"25.11"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/1e687886-dffe-4bd2-a41f-2a58f1f3af15#PostalAddress","@type":["http://schema.org/PostalAddress"],"http://schema.org/streetAddress":[{"@language":"fr","@value":"1381 Av. de la Transmission"}],"http://schema.org/postalCode":[{"@value":"G9N 8N8"}],"http://schema.org/addressLocality":[{"@language":"fr","@value":"Shawinigan"}],"http://schema.org/addressRegion":[{"@language":"fr","@value":"QC"}],"http://schema.org/addressCountry":[{"@language":"fr","@value":"CA"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_tdd260109001","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"P'tit Gros Bison en Duo"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/tdd260109001?lang=fr#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-09T20:00:00-05:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/896cb34d-9e47-4dcb-91e4-4b1eecc665d5"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/_AbEQajaiq0?enablejsapi=1"}],"http://schema.org/sameAs":[{"@value":"[\"https://www.facebook.com/people/Ptit-Gros-Bison/100058924238417/\"]"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/tdd260109001?lang=fr"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/TDD/26/01/09/001/tdd260109001-1152x648-fr-68d299ce.png"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_tdd260109001#Offer_0"}]},{"@id":"https://lepointdevente.com/billets/tdd260109001?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-07T17:31:36.081-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/tdd260109001?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_tdd260109001#Offer_0","http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"20.0"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale +18 ans"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_tdd260123001","@type":["http://schema.org/Event"],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-14T20:00:00-04:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/TDD/26/01/23/001/tdd260123001-1152x648-fr-69601876.png"}],"http://schema.org/name":[{"@language":"fr","@value":"Pink Floyd Acoustic Experience"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/tdd260123001?lang=fr"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/896cb34d-9e47-4dcb-91e4-4b1eecc665d5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventRescheduled"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/tdd260123001?lang=fr#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_tdd260123001#Offer_0"}]},{"@id":"https://lepointdevente.com/billets/tdd260123001?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-02T01:11:58.598-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/tdd260123001?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_tdd260123001#Offer_0","http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale +18 ans"}],"http://schema.org/price":[{"@value":"42.4"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_ser260208001","@type":["http://schema.org/Event"],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-07T19:00:00-05:00"}],"http://schema.org/name":[{"@language":"fr","@value":"Tournée mondiale du Festival du film de montagne de Banff au Québec"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/ser260208001?lang=fr"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/ser260208001?lang=fr#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/SER/26/02/07/001/ser260207001-1152x648-fr-68f0fb41.png"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-696"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/d4f77de6-e3ba-44c9-aec3-61c268bb4cd7"}],"http://schema.org/sameAs":[{"@value":"[\"https://www.facebook.com/BanffQuebec/\", \"https://www.facebook.com/BanffQuebec/\"]"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/description":[{"@language":"fr","@value":" 50e édition! La Tournée mondiale du Festival du film de montagne de Banff est de retour!En janvier et février 2026, la tournée marque 50 ans de pur dépassement. Vibrez devant des images spectaculaires où des hommes et des femmes d’exception poussent souvent leurs exploits à la limite du possible. Une expérience cinématographique incontournable qui célèbre la passion et l’audace. La programmation finale sera révélée en novembre 2025.Avec 23 villes visitées, la tournée au Québec est l’une des plus importantes du World Tour qui se déplace dans plus de 40 pays. Cette année, près de 600 films ont été soumis au jury, un record de tous les temps. Du nombre, environ 80 sont présentés aux festivaliers.Infos pratiques:-- Soirée d’une durée totale de 3 heures, entracte et tirage inclus -- La programmation est la même pour chacune des représentations-- Les films sont présentés en version originale sous-titrée afin de conserver leur authenticité-- De nombreux prix de présence tirés au retour de la pause -- Une exposition photographique dont le thème varie chaque année accompagne la TournéeSite web : www.banffquebec.caFacebook : https://www.facebook.com/BanffQuebec/Instagram : https://www.instagram.com/banffquebec/ "}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_ser260208001#Offer_0"}]},{"@id":"https://lepointdevente.com/billets/ser260208001?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-02T01:10:59.836-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/ser260208001?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/d4f77de6-e3ba-44c9-aec3-61c268bb4cd7","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Arts Visuels"}],"http://www.w3.org/2004/02/skos/core#closeMatch":[{"@id":"http://kg.artsdata.ca/resource/VisualArtsEvent"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_ser260208001#Offer_0","http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_tdd250919001","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-518"}],"http://schema.org/name":[{"@language":"fr","@value":"Mathieu Grégoire"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-07T20:00:00-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/tdd250919001?lang=fr"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/TDD/25/09/19/001/tdd250919001-1152x648-fr-68d299e5.png"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/896cb34d-9e47-4dcb-91e4-4b1eecc665d5"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/tdd250919001?lang=fr#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_tdd250919001#Offer_0"}]},{"@id":"https://lepointdevente.com/billets/tdd250919001?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-02T01:10:38.007-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/tdd250919001?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_tdd250919001#Offer_0","http://schema.org/price":[{"@value":"40.0"}],"@type":["http://schema.org/Offer"],"http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale +18 ans"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_cmoscini11022026","@type":["http://schema.org/Event"],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-698"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/cmoscini11022026?lang=fr"}],"http://schema.org/name":[{"@language":"fr","@value":"Cristina Moscini"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-11T19:30:00-05:00"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/5BQ/26/02/11/001/5bq260211001-1152x648-fr-68ef9859.png"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c04eff2a-2e39-44ba-8091-7f884250912d"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/cmoscini11022026?lang=fr#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_cmoscini11022026#Offer_0"}]},{"@id":"https://lepointdevente.com/billets/cmoscini11022026?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-02T01:11:42.025-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/cmoscini11022026?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_cmoscini11022026#Offer_0","http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"11.14"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_rhimpro11032026","@type":["http://schema.org/Event"],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"RH show impro"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/5BQ/26/03/11/001/5bq260311001-1152x648-fr-68ef993b.png"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-11T19:30:00-04:00"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-698"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/rhimpro11032026?lang=fr#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/rhimpro11032026?lang=fr"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_rhimpro11032026#Offer_0"}]},{"@id":"https://lepointdevente.com/billets/rhimpro11032026?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-02T01:10:43.742-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/rhimpro11032026?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_rhimpro11032026#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"http://schema.org/price":[{"@value":"11.14"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_sbo260311001","@type":["http://schema.org/Event"],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/SBO/26/03/11/001/sbo260311001-1152x648-fr-68f69ac9.png"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-11T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/sbo260311001?lang=fr#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/sbo260311001?lang=fr"}],"http://schema.org/name":[{"@language":"fr","@value":"Vulgaires Machins à Shawinigan"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/896cb34d-9e47-4dcb-91e4-4b1eecc665d5"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_sbo260311001#Offer_0"}]},{"@id":"https://lepointdevente.com/billets/sbo260311001?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-02T01:11:10.756-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/sbo260311001?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_sbo260311001#Offer_0","http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_bruler08042026","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/bruler08042026?lang=fr"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-08T19:30:00-04:00"}],"http://schema.org/name":[{"@language":"fr","@value":"Brûler les langues - soirée de discours"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c04eff2a-2e39-44ba-8091-7f884250912d"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/bruler08042026?lang=fr#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-698"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/5BQ/26/04/08/001/5bq260408001-1152x648-fr-68ef9a51.png"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_bruler08042026#Offer_0"}]},{"@id":"https://lepointdevente.com/billets/bruler08042026?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-02T01:11:36.921-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/bruler08042026?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_bruler08042026#Offer_0","http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"11.14"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_tdd260417001","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K2-1709"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/tdd260417001?lang=fr"}],"http://schema.org/name":[{"@language":"fr","@value":"Maxime Gervais Pour public averti 18+"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/TDD/26/04/17/001/tdd260417001-1152x648-fr-68c178b2.png"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/tdd260417001?lang=fr#WebPage"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-17T20:00:00-04:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/description":[{"@language":"fr","@value":" L’humoriste Maxime Gervais, ex-membre des cultissimes Pic-Bois et tendre humain au blagues déjantées revient en force après le succès de son premier spectacle, Big Max.Place aux expérimentations, à l’absurde, au mystère et la Liberté (avec un gros L) : le show C’était magnifique est là pour vous décoiffer, voire même, vous fucker ben raide. Maxime Gervais vous en fait lui-même la promesse : vous ressortirez complètement transformé.e.s de son spectacle où il multiplie les personnages, célèbre la vie, expie ses cauchemars et – quand même pas pire important de le rappeler – fait des jokes.Spectacle réservé aux 18 ans et plus. "}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/896cb34d-9e47-4dcb-91e4-4b1eecc665d5"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_tdd260417001#Offer_0"}]},{"@id":"https://lepointdevente.com/billets/tdd260417001?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-02T01:11:52.888-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/tdd260417001?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_tdd260417001#Offer_0","http://schema.org/price":[{"@value":"27.99"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission général +18 ans"}],"http://schema.org/availability":[{"@value":"http://schema.org/InStock"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_nuitdoreuniverspartages","@type":["http://schema.org/Event"],"http://schema.org/description":[{"@language":"fr","@value":" KEVROD \u0026 invités — Nuit dorée, univers partagésSoirée intime (50 places) où KEVROD traverse l’intime, le chaos et la fête. Ses chansons originales dialoguent avec des reprises réinventées (David Bowie, Supertramp, Patrick Norman, et pleins d'autres surprises.) pour une nuit élégante, rare et mémorable. Sur scène : KEVROD, ses invités Emmanuelle Julien et Marylène Gendron, accompagnés de trois musiciens (clavier, basse, guitare). Deux parties avec une entracte de 15 minutes et une proximité maximale avec le public. Lieu : Le Repère des mauvaises langues (centre-ville de Trois-Rivières) • Portes : 19 h • Spectacle : 19 h 30 • Durée : ~2 h • Tarif : 40 $ + taxes et frais • Configuration : intime, assis. Réservez tôt : capacité limitée. Accès au site de l'événement Comment s’y rendre — Le Repère des mauvaises langues (Trois-Rivières, centre-ville) Entrez simplement « Le Repère des mauvaises langues » dans Google Maps/Apple Plans. Le lieu est au cœur du centre-ville ; on y accède facilement à pied depuis les principales artères. Stationnement : rues avoisinantes (parcomètres) et stationnements publics à proximité. Transport en commun (STTR) : descendre au terminus Centre-Ville puis marcher quelques minutes. Prévoyez d’arriver un peu plus tôt pour vous stationner et trouver vos sièges. Besoin d’accessibilité ou d’indications précises (entrée, ascenseur, etc.) ? Écrivez-nous et on vous guide. Informations relatives au stationnement Stationnement Du stationnement est disponible dans les rues avoisinantes (parcomètres) et dans plusieurs stationnements publics à 2–5 minutes de marche. Les tarifs et horaires varient selon le secteur : vérifie la signalisation sur place. Prévois d’arriver 10–15 minutes plus tôt pour te garer sereinement. Un court dépose-minute est possible près de l’entrée (respecte les zones interdites/livraisons). Si tu peux, pense au covoiturage! "}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-25T19:30:00-04:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/name":[{"@language":"fr","@value":"KEVROD \u0026 invités : Nuit dorée, univers partagés"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/6OY/26/04/25/001/nuitdoreuniverspartages-1152x648-fr-68e54485.png"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-698"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/nuitdoreuniverspartages?lang=fr"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/nuitdoreuniverspartages?lang=fr#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_nuitdoreuniverspartages#Offer_0"}]},{"@id":"https://lepointdevente.com/billets/nuitdoreuniverspartages?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-02T01:11:20.486-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/nuitdoreuniverspartages?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_nuitdoreuniverspartages#Offer_0","http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_slab13052026","@type":["http://schema.org/Event"],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/slab13052026?lang=fr#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c04eff2a-2e39-44ba-8091-7f884250912d"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/slab13052026?lang=fr"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-698"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/5BQ/26/05/13/001/5bq260513001-1152x648-fr-68ef9b79.png"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-13T19:30:00-04:00"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Steven SLAB"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_slab13052026#Offer_0"}]},{"@id":"https://lepointdevente.com/billets/slab13052026?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-02T01:11:31.408-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/slab13052026?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_slab13052026#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"http://schema.org/price":[{"@value":"11.14"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_tdd260515001","@type":["http://schema.org/Event"],"http://schema.org/description":[{"@language":"fr","@value":" Pour public averti 13+ "}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-15T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/tdd260515001?lang=fr#WebPage"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/TDD/26/05/15/001/tdd260515001-1152x648-fr-68e28330.png"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Zac Bulle Pour public averti 13+"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-691"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/tdd260515001?lang=fr"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_tdd260515001#Offer_0"}]},{"@id":"https://lepointdevente.com/billets/tdd260515001?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-02T01:10:49.227-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/tdd260515001?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_tdd260515001#Offer_0","http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_1yl260516001","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Famille Quessy/Forest - Ensemble"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/1yl260516001?lang=fr"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-16T19:30:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/1yl260516001?lang=fr#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/344af578-31da-4281-a188-c2ca5f0205cc"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/1YL/26/05/16/001/1yl260516001-1152x648-fr-68e6917d.png"}],"http://schema.org/description":[{"@language":"fr","@value":" Pratiquant tous et chacun-e les arts de la scène chez la famille Quessy/Forest, c'est tout naturellement qu'a germé l'idée d'un spectacle dans lequel Alain Quessy, James Forest, Elyann et Amélia Quessy unissent leurs talents. Ils nous présentent compositions originales et reprises coups de coeur en mélangeant leurs couleurs distinctes, dans une approche simple et conviviale, empreinte de sincérité, pour notre grand plaisir. "}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_1yl260516001#Offer_0"}]},{"@id":"https://lepointdevente.com/billets/1yl260516001?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-02T01:10:54.637-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/1yl260516001?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/344af578-31da-4281-a188-c2ca5f0205cc","@type":["http://schema.org/Place"],"http://schema.org/name":[{"@language":"fr","@value":"Le 2800 du parc"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"premise at 2800 Chem. de Saint-Jean-des-Piles, Saint-Jean-des-Piles, QC G0X 2V0, Canada"}],"http://schema.org/address":[{"@id":"http://kg.footlight.io/resource/344af578-31da-4281-a188-c2ca5f0205cc#PostalAddress"}],"http://schema.org/longitude":[{"@value":"-72.7668286"}],"http://schema.org/latitude":[{"@value":"46.7251537"}],"http://schema.org/sameAs":[{"@value":"https://maps.google.com/?q=2800+Chem.+de+Saint-Jean-des-Piles,+Saint-Jean-des-Piles,+QC+G0X+2V0,+Canada\u0026ftid=0x4cc659d3cd8121b1:0x2eb027dfc039fec"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_1yl260516001#Offer_0","http://schema.org/price":[{"@value":"30.0"}],"@type":["http://schema.org/Offer"],"http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/344af578-31da-4281-a188-c2ca5f0205cc#PostalAddress","@type":["http://schema.org/PostalAddress"],"http://schema.org/streetAddress":[{"@language":"fr","@value":"2800 Chem. de Saint-Jean-des-Piles"}],"http://schema.org/postalCode":[{"@value":"G0X 2V0"}],"http://schema.org/addressLocality":[{"@language":"fr","@value":"Shawinigan"}],"http://schema.org/addressRegion":[{"@language":"fr","@value":"QC"}],"http://schema.org/addressCountry":[{"@language":"fr","@value":"CA"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_tdd261031001","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K13-68"}],"http://schema.org/name":[{"@language":"fr","@value":"Gab Bouchard"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/tdd261031001?lang=fr#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-31T20:00:00-04:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/tdd261031001?lang=fr"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/TDD/26/10/31/001/tdd261031001-1152x648-fr-68d29a09.png"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/896cb34d-9e47-4dcb-91e4-4b1eecc665d5"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_tdd261031001#Offer_0"}]},{"@id":"https://lepointdevente.com/billets/tdd261031001?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-02T01:11:05.498-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/tdd261031001?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_tdd261031001#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"http://schema.org/price":[{"@value":"35.0"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_martinfontainestoryshow","http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/martinfontainestoryshow?lang=fr"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/event-groups/EL2/martinfontainestoryshow-1152x648-fr-6792af24.png"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-28T20:30:00-05:00"}],"http://schema.org/name":[{"@language":"fr","@value":"MARTIN FONTAINE Story Show - Lepointdevente.com"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/martinfontainestoryshow?lang=fr#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-706"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_martinfontainestoryshow#Offer_0"},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_martinfontainestoryshow#Offer_1"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-29T20:30:00-05:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_martinfontainestoryshow#2025-11-28T203000-0500"},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_martinfontainestoryshow#2025-11-29T203000-0500"}]},{"@id":"https://lepointdevente.com/billets/martinfontainestoryshow?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-28T01:15:03.747-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/martinfontainestoryshow?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_martinfontainestoryshow#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Souper-Spectacle | entrée 17H30"}],"@type":["http://schema.org/Offer"],"http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"http://schema.org/price":[{"@value":"137.19"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_martinfontainestoryshow#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"Spectacle | entrée 20h"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"78.04"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_martinfontainestoryshow#2025-11-28T203000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"MARTIN FONTAINE Story Show - Lepointdevente.com"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-706"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-28T20:30:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_martinfontainestoryshow#2025-11-29T203000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"MARTIN FONTAINE Story Show - Lepointdevente.com"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-706"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-29T20:30:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_ericmassonhalloween","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/ericmassonhalloween?lang=fr"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-10-31T19:00:00-04:00"}],"http://schema.org/name":[{"@language":"fr","@value":"PARTY D'HALLOWEEN AVEC ÉRIC MASSON"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/8UJ/25/10/31/001/8uj251031001-1152x648-fr-68ca0bcc.png"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/ericmassonhalloween?lang=fr#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/49299024-e10d-436f-b3b8-0802ce9e80de"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_ericmassonhalloween#Offer_0"}]},{"@id":"https://lepointdevente.com/billets/ericmassonhalloween?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-10-29T02:13:40.560-04:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/ericmassonhalloween?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_ericmassonhalloween#Offer_0","http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"25.0"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_kokoandthegang-specialhalloween","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/kokoandthegang-specialhalloween?lang=fr"}],"http://schema.org/name":[{"@language":"fr","@value":"Koko \u0026 The Gang - Spécial Halloween 2025"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/LT2/25/10/31/001/lt2251031001-1152x648-fr-68e13ba7.png"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-10-31T21:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/kokoandthegang-specialhalloween?lang=fr#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/sameAs":[{"@value":"[\"https://www.facebook.com/kokoandthegang9\", \"https://www.facebook.com/kokoandthegang9\"]"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-487"}],"http://schema.org/description":[{"@language":"fr","@value":" Koko \u0026 The Gang - Show d'Halloween au Café-Bar Zénob!Préparez-vous pour la soirée la plus terrifiante de l'année! Koko \u0026 The Gang vous invite à une performance spéciale d'Halloween qui fera frissonner même les plus braves.Plongez dans une ambiance lugubre pour une soirée de musique et de frayeurs qui vous hantera jusqu'au petit matin. Enfilez votre meilleur costume et venez célébrer la nuit des morts-vivants!QUAND ? Vendredi 31 OctobreOÙ ? Café-Bar ZénobHEURE ? 21h00COMBIEN ? 15$ en prévente / 20$ à la porteNe manquez pas cette soirée macabre! Achetez vos billets en prévente pour économiser et garantir votre place. On vous attend, si vous osez... "}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_kokoandthegang-specialhalloween#Offer_0"}]},{"@id":"https://lepointdevente.com/billets/kokoandthegang-specialhalloween?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-10-29T02:17:51.434-04:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/kokoandthegang-specialhalloween?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_kokoandthegang-specialhalloween#Offer_0","http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"16.7"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_z50251101001","@type":["http://schema.org/Event"],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-1199"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/z50251101001?lang=fr#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventCancelled"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/Z50/25/11/01/001/z50251101001-1152x648-fr-688bf3c5.png"}],"http://schema.org/name":[{"@language":"fr","@value":"Mama's House TAKEOVER Trois-rivieres (Halloween Edition)"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-01T19:00:00-04:00"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/z50251101001?lang=fr"}],"http://schema.org/description":[{"@language":"fr","@value":" Mama's House TAKEOVER – Trois-Rivières Édition HalloweenUn événement danse et frissons pour tous les âges !Plongez dans l’univers sombre et terrifiant de Mama's House, une véritable maison d’horreur country où chaque coin réserve son lot de surprises glaçantes… Oserez-vous franchir la porte ? ????4 écoles de danse réunies pour une soirée légendaire à glacer le sang :DB Country PopDanse Country MABDJ SteveCowboy BarbuFrissons garantis dans une atmosphère hantée, entre décors macabres, lumières inquiétantes et musique endiablée. Venez costumés pour réveiller les esprits du passé et danser avec les créatures de la nuit ! Venez en famille, entre amis ou en solo, mais soyez prêts à vivre une expérience unique où danse rime avec terreur et folie ! Déguisements fortement encouragés – ambiance maison hantée garantie !Complexe Laviolette2203 Boulevard des Forges, Trois-Rivières, QC G8Z 1V1Vendredi 1er novembre 2025De 19h à 2h du matin⚠️ Capacité limitée à 500 personnes – les billets vont partir vite, garantie !Ne tardez pas à réserver !Tarifs :Enfants :– Gratuit pour les moins de 12 ans– 15 $ + frais + taxes pour les 12 à 17 ansAdultes :– 100 premiers billets : 20 $ + frais + taxes– 200 billets suivants : 25 $ + frais + taxes– Billets restants : 30 $ + frais + taxes➡️ Rejoignez-nous pour une nuit inoubliable où le country rencontre le côté obscur… de la danse et de l’horreur ! "}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_z50251101001#Offer_0"}]},{"@id":"https://lepointdevente.com/billets/z50251101001?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-10-31T02:12:39.548-04:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/z50251101001?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_z50251101001#Offer_0","http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_nostepback","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/nostepback?lang=fr"}],"http://schema.org/name":[{"@language":"fr","@value":"Rebel sound collective \u0026 Offensive présente: soirée punk rock au Zénob!"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/51f8b4cb-d760-43b0-ba6d-dfa8f342050b"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/3OE/25/11/01/001/3oe251101001-1152x648-fr-68365708.png"}],"http://schema.org/description":[{"@language":"fr","@value":" Soirée punk rock,party ,downtown Sherbrooke présenté par Offensivewww.offensivepunkrock.com "}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-01T20:00:00-04:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/nostepback?lang=fr#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/sameAs":[{"@value":"[\"https://www.facebook.com/nostepbackmusic\", \"https://www.facebook.com/HostageEyesSherbrookePunkRock/\", \"https://www.facebook.com/profile.php?id=100087058321593\"]"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_nostepback#Offer_0"}]},{"@id":"https://lepointdevente.com/billets/nostepback?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-10-31T02:12:47.389-04:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/nostepback?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_nostepback#Offer_0","http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"http://schema.org/price":[{"@value":"16.7"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_gn2251101001","@type":["http://schema.org/Event"],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-01T20:00:00-04:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/9351b579-9410-4bc1-b12a-df3476b09124"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/gn2251101001?lang=fr"}],"http://schema.org/name":[{"@language":"fr","@value":"The Dams, Calling Johnny \u0026 Méchante Haria"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/GN2/25/11/01/001/gn2251101001-1152x648-fr-68db30ac.png"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/gn2251101001?lang=fr#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_gn2251101001#Offer_0"}]},{"@id":"http://kg.footlight.io/resource/9351b579-9410-4bc1-b12a-df3476b09124","@type":["http://schema.org/Place"],"http://schema.org/address":[{"@id":"http://kg.footlight.io/resource/9351b579-9410-4bc1-b12a-df3476b09124#PostalAddress"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"street_address at 1983 Rue Royale, Trois-Rivières, QC G9A 4L1, Canada"}],"http://schema.org/latitude":[{"@value":"46.33766799999999"}],"http://schema.org/longitude":[{"@value":"-72.54682799999999"}],"http://schema.org/name":[{"@language":"fr","@value":"La Taverne Royale"}],"http://schema.org/sameAs":[{"@value":"https://maps.google.com/?q=1983+Rue+Royale,+Trois-Rivi%C3%A8res,+QC+G9A+4L1,+Canada\u0026ftid=0x4cc7c5fe2db3dd0b:0xec29ee93b5876137"}],"http://schema.org/alternateName":[{"@language":"fr","@value":"Taverne Royale"}]},{"@id":"https://lepointdevente.com/billets/gn2251101001?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-10-31T02:12:53.471-04:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/gn2251101001?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_gn2251101001#Offer_0","http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"16.7"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/9351b579-9410-4bc1-b12a-df3476b09124#PostalAddress","@type":["http://schema.org/PostalAddress"],"http://schema.org/addressCountry":[{"@language":"fr","@value":"CA"}],"http://schema.org/addressLocality":[{"@language":"fr","@value":"Trois-Rivières"}],"http://schema.org/addressRegion":[{"@language":"fr","@value":"QC"}],"http://schema.org/postalCode":[{"@value":"G9A 4L1"}],"http://schema.org/streetAddress":[{"@language":"fr","@value":"1983 Rue Royale"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_8uj251102001","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/8uj251102001?lang=fr"}],"http://schema.org/name":[{"@language":"fr","@value":"Les dimanches Fou-Rire du Q\u0026F 2 novembre"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-02T19:00:00-05:00"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/8UJ/25/10/05/001/8uj251005001-1152x648-fr-68ca06fa.png"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/8uj251102001?lang=fr#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/49299024-e10d-436f-b3b8-0802ce9e80de"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_8uj251102001#Offer_0"}]},{"@id":"https://lepointdevente.com/billets/8uj251102001?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-10-31T02:15:44.862-04:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/8uj251102001?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_8uj251102001#Offer_0","http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"25.0"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_xc4251107001","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/xc4251107001?lang=fr"}],"http://schema.org/name":[{"@language":"fr","@value":"A DAY TO REMEMBER Tribute + 2FAST4U + RUNTERRYRUN + FAXIN"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/xc4251107001?lang=fr#WebPage"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-07T20:00:00-05:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/53632e8d-5e80-44fb-a289-4a2eb22a04b7"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/XC4/25/11/07/001/xc4251107001-1152x648-fr-68992911.png"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_xc4251107001#Offer_0"}]},{"@id":"https://lepointdevente.com/billets/xc4251107001?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-06T01:12:56.392-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/xc4251107001?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/53632e8d-5e80-44fb-a289-4a2eb22a04b7","@type":["http://schema.org/Place"],"http://schema.org/name":[{"@language":"fr","@value":"Rock cafe le Stage"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"cafe, food, point_of_interest, establishment at 75 Rue Fusey, Trois-Rivières, QC G8T 2T9, Canada"}],"http://schema.org/address":[{"@id":"http://kg.footlight.io/resource/53632e8d-5e80-44fb-a289-4a2eb22a04b7#PostalAddress"}],"http://schema.org/longitude":[{"@value":"-72.5276717"}],"http://schema.org/latitude":[{"@value":"46.36547789999999"}],"http://schema.org/sameAs":[{"@value":"https://maps.google.com/?cid=2999213381732431777"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_xc4251107001#Offer_0","http://schema.org/price":[{"@value":"21.95"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"http://schema.org/availability":[{"@value":"http://schema.org/InStock"}]},{"@id":"http://kg.footlight.io/resource/53632e8d-5e80-44fb-a289-4a2eb22a04b7#PostalAddress","@type":["http://schema.org/PostalAddress"],"http://schema.org/streetAddress":[{"@language":"fr","@value":"75 Rue Fusey"}],"http://schema.org/postalCode":[{"@value":"G8T 2T9"}],"http://schema.org/addressLocality":[{"@language":"fr","@value":"Trois-Rivières"}],"http://schema.org/addressRegion":[{"@language":"fr","@value":"QC"}],"http://schema.org/addressCountry":[{"@language":"fr","@value":"CA"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_tdd251107001","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/TDD/25/11/07/001/tdd251107001-1152x648-fr-68cd500d.png"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/tdd251107001?lang=fr#WebPage"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-07T20:00:00-05:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/description":[{"@language":"fr","@value":" Accompagné de Florence Labelle (voix, violon), Philippe Noël (guitares), Étienne Lavigueur (claviers), Érika Fogagnolo (voix, basse) et de Davy Duquenoy (batterie), Velours Velours nous partage un spectacle énergique et exaltant avec des grooves auxquels il est impossible de rester indifférent. Mélangeant les chansons de son nouvel album Quand je pleure, je suis content et celles de son premier EP, la soirée nous transporte dans une vaste gamme d’émotions, tantôt touchante, tantôt tranchante, dans l’humour ou dans la mélancolie. Préparez-vous à laisser absorber par l’univers de Velours Velours dans un moment hors du temps. "}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/896cb34d-9e47-4dcb-91e4-4b1eecc665d5"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/tdd251107001?lang=fr"}],"http://schema.org/name":[{"@language":"fr","@value":"Velours Velours"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_tdd251107001#Offer_0"}]},{"@id":"https://lepointdevente.com/billets/tdd251107001?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-06T01:13:02.659-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/tdd251107001?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_tdd251107001#Offer_0","http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"http://schema.org/price":[{"@value":"25.0"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_b6l251107001","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Salut Bob Gorgée (hommage à Bob Bissonnette)"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K10-26"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-16T21:30:00-05:00"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/B6L/25/11/07/001/b6l251107001-1152x648-fr-687a67c9.png"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/b6l251107001?lang=fr#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/b6l251107001?lang=fr"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventRescheduled"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/1e687886-dffe-4bd2-a41f-2a58f1f3af15"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_b6l251107001#Offer_0"}]},{"@id":"https://lepointdevente.com/billets/b6l251107001?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-15T01:10:34.521-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/b6l251107001?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_b6l251107001#Offer_0","http://schema.org/price":[{"@value":"25.11"}],"@type":["http://schema.org/Offer"],"http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_g92251108001","@type":["http://schema.org/Event"],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/8e2332d5-619e-4e77-a7e8-53e1cc22212f"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/g92251108001?lang=fr"}],"http://schema.org/name":[{"@language":"fr","@value":"GFK et Bord en bord"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/g92251108001?lang=fr#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-698"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/G92/25/11/08/001/g92251108001-1152x648-fr-68d49099.png"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-08T20:00:00-05:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_g92251108001#Offer_0"}]},{"@id":"http://kg.footlight.io/resource/8e2332d5-619e-4e77-a7e8-53e1cc22212f","@type":["http://schema.org/Organization"],"http://schema.org/name":[{"@language":"fr","@value":"Voix de Pasaj"}],"http://schema.org/url":[{"@value":"https://www.facebook.com/Voixdepasaj"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Organization"}]},{"@id":"https://lepointdevente.com/billets/g92251108001?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-06T01:13:07.464-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/g92251108001?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_g92251108001#Offer_0","http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"http://schema.org/price":[{"@value":"27.22"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_jjc251108001","@type":["http://schema.org/Event"],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/032ed8bb-8d32-4f7a-ae09-f31ba9fba8f4"}],"http://schema.org/description":[{"@language":"fr","@value":" Accès au site de l'événement L'ENTITÉ. | 18+ 1420 Notre-Dame Centre, Trois-Rivières, Québec, G9A 4X3 "}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"},{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/0a06696f-60e1-4351-aac0-115cf5702bdd"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/jjc251108001?lang=fr#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/jjc251108001?lang=fr"}],"http://schema.org/name":[{"@language":"fr","@value":"Hommage à Jean Leloup à Trois-Rivières | L'ENTITÉ"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/JJC/25/11/08/001/jjc251108001-1152x648-fr-68f00980.png"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-08T20:00:00-05:00"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_jjc251108001#Offer_0"}]},{"@id":"http://kg.footlight.io/resource/032ed8bb-8d32-4f7a-ae09-f31ba9fba8f4","@type":["http://schema.org/Organization"],"http://schema.org/name":[{"@language":"fr","@value":"L'entité"}],"http://schema.org/url":[{"@value":"https://entitetr.com/"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Organization"}],"http://schema.org/alternateName":[{"@language":"fr","@value":"L'entité. | 18+"}]},{"@id":"http://kg.footlight.io/resource/0a06696f-60e1-4351-aac0-115cf5702bdd","@type":["http://schema.org/Place"],"http://schema.org/name":[{"@language":"fr","@value":"L'Entité."}],"http://schema.org/address":[{"@id":"http://kg.footlight.io/resource/0a06696f-60e1-4351-aac0-115cf5702bdd#PostalAddress"}],"http://schema.org/longitude":[{"@value":"-72.5405245"}],"http://schema.org/latitude":[{"@value":"46.3420557"}],"http://schema.org/sameAs":[{"@value":"https://maps.google.com/?cid=3400810918516525688"}],"http://schema.org/alternateName":[{"@language":"fr","@value":"L'Entité"}]},{"@id":"https://lepointdevente.com/billets/jjc251108001?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-06T01:13:22.766-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/jjc251108001?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_jjc251108001#Offer_0","http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"26.32"}],"http://schema.org/name":[{"@language":"fr","@value":"Billet de Spectacle"}]},{"@id":"http://kg.footlight.io/resource/0a06696f-60e1-4351-aac0-115cf5702bdd#PostalAddress","@type":["http://schema.org/PostalAddress"],"http://schema.org/streetAddress":[{"@language":"fr","@value":"1420 Rue Notre Dame Centre"}],"http://schema.org/postalCode":[{"@value":"G9A 4X3"}],"http://schema.org/addressLocality":[{"@language":"fr","@value":"Trois-Rivières"}],"http://schema.org/addressRegion":[{"@language":"fr","@value":"QC"}],"http://schema.org/addressCountry":[{"@language":"fr","@value":"CA"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_mathieurgregoire","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-518"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/E4K/25/10/18/001/e4k251018001-1152x648-fr-67d1a2dd.png"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K12-518"}],"http://schema.org/name":[{"@language":"fr","@value":"Mathieu Grégoire chante Fiori-Séguin \u0026 Cie"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-08T20:00:00-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/mathieurgregoire?lang=fr"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/mathieurgregoire?lang=fr#WebPage"}],"http://schema.org/sameAs":[{"@value":"[\"https://www.facebook.com/Le2800duParc\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/description":[{"@language":"fr","@value":" Un Nouveau Défi : Un Spectacle Solo EnvoûtantToujours en quête de nouveaux défis, Mathieu Grégoire se lance dans un projet solo audacieux, « Mathieu Grégoire ChanteFiori-Séguin \u0026 Cie », fusion musicale raffinée qui promet de raviver la flamme du folk et du rock progressif québécois.Porté par trois décennies d'expérience scénique, il proposera un voyage musical immersif à travers les classiques de Fiori-Séguin, Gilles Valiquette, Claude Dubois, Harmonium et d'autres légendes de la chanson francophone. Avec sa voix chaleureuse, son jeu de guitare expressif et sa sensibilité unique, il rend hommage aux géants de la musique québécoise tout en y insufflant sa propre signature artistique. Accompagné de musiciens talentueux – percussionniste, contrebassiste et multi-instrumentiste aux vents –, il crée des arrangements riches et nuancés, alliant la chaleur de la guitareacoustique à l’élégance des instruments à vent et la profondeur de la contrebasse. Dans ce projet ambitieux, il ne se contentepas d’interpréter ces classiques : il les réinvente, leur redonne souffle et éclat, tout en préservant leur essence poétique et émotive !Mathieu Grégoire, c'est bien plus qu'un interprète : c'est un passeur d'émotions, un artisan de la musique qui, à chaqueperformance, sait faire vibrer les notes avec passion et authenticité pour une célébration riche et vivante de la musique ! "}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/344af578-31da-4281-a188-c2ca5f0205cc"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_mathieurgregoire#Offer_0"}]},{"@id":"https://lepointdevente.com/billets/mathieurgregoire?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-06T01:13:29.193-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/mathieurgregoire?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_mathieurgregoire#Offer_0","http://schema.org/price":[{"@value":"40.0"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"http://schema.org/availability":[{"@value":"http://schema.org/InStock"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_rmlcvanasse12112025","@type":["http://schema.org/Event"],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-12T19:30:00-05:00"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-698"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/rmlcvanasse12112025?lang=fr#WebPage"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/rmlcvanasse12112025?lang=fr"}],"http://schema.org/name":[{"@language":"fr","@value":"CHRISTIAN VANASSE - SOIRÉE D’HUMOUR POUR SALES GAUCHISTES (ÉDITION SPÉCIALE)"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/5BQ/25/11/12/001/5bq251112001-1152x648-fr-68ef94bf.png"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_rmlcvanasse12112025#Offer_0"}]},{"@id":"https://lepointdevente.com/billets/rmlcvanasse12112025?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-10T01:13:24.386-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/rmlcvanasse12112025?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_rmlcvanasse12112025#Offer_0","http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"11.14"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_soireedhumourgrandmere","http://schema.org/image":[{"@value":"https://cdn.lepointdevente.com/themes/tpos/ogp-img-fr-5f32b556.gif"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5a58b6f0-5dc4-4104-bb86-4d22311c478d"}],"http://schema.org/name":[{"@language":"fr","@value":"SOIRÉE DE L'HUMOUR - Lepointdevente.com"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/soireedhumourgrandmere?lang=fr"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-01-28T20:00:00-05:00"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/soireedhumourgrandmere?lang=fr#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_soireedhumourgrandmere#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-12-10T20:00:00-05:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_soireedhumourgrandmere#2025-12-10T200000-0500"},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_soireedhumourgrandmere#2025-01-28T200000-0500"},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_soireedhumourgrandmere#2025-02-25T200000-0500"},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_soireedhumourgrandmere#2025-03-25T200000-0400"},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_soireedhumourgrandmere#2025-04-22T200000-0400"}]},{"@id":"http://kg.footlight.io/resource/5a58b6f0-5dc4-4104-bb86-4d22311c478d","@type":["http://schema.org/Place"],"http://schema.org/name":[{"@language":"fr","@value":" Ô Quai des Brasseurs Grand-Mère"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"bar, restaurant, food, point_of_interest, establishment at 99 Av. Chahoon, Shawinigan, QC G9T 2T5, Canada"}],"http://schema.org/address":[{"@id":"http://kg.footlight.io/resource/5a58b6f0-5dc4-4104-bb86-4d22311c478d#PostalAddress"}],"http://schema.org/longitude":[{"@value":"-72.6795753"}],"http://schema.org/latitude":[{"@value":"46.61928959999999"}],"http://schema.org/sameAs":[{"@value":"https://maps.google.com/?cid=2087617261133635065"}]},{"@id":"https://lepointdevente.com/billets/soireedhumourgrandmere?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-12-08T01:13:02.528-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/soireedhumourgrandmere?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_soireedhumourgrandmere#Offer_0","http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"http://schema.org/price":[{"@value":"20.0"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_soireedhumourgrandmere#2025-12-10T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"SOIRÉE DE L'HUMOUR - Lepointdevente.com"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5a58b6f0-5dc4-4104-bb86-4d22311c478d"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-12-10T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_soireedhumourgrandmere#2025-01-28T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"SOIRÉE DE L'HUMOUR - Lepointdevente.com"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5a58b6f0-5dc4-4104-bb86-4d22311c478d"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-01-28T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_soireedhumourgrandmere#2025-02-25T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"SOIRÉE DE L'HUMOUR - Lepointdevente.com"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5a58b6f0-5dc4-4104-bb86-4d22311c478d"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-02-25T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_soireedhumourgrandmere#2025-03-25T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"SOIRÉE DE L'HUMOUR - Lepointdevente.com"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5a58b6f0-5dc4-4104-bb86-4d22311c478d"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-03-25T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_soireedhumourgrandmere#2025-04-22T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"SOIRÉE DE L'HUMOUR - Lepointdevente.com"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5a58b6f0-5dc4-4104-bb86-4d22311c478d"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-04-22T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/5a58b6f0-5dc4-4104-bb86-4d22311c478d#PostalAddress","@type":["http://schema.org/PostalAddress"],"http://schema.org/streetAddress":[{"@language":"fr","@value":"99 Av. Chahoon"}],"http://schema.org/postalCode":[{"@value":"G9T 2T5"}],"http://schema.org/addressLocality":[{"@language":"fr","@value":"Shawinigan"}],"http://schema.org/addressRegion":[{"@language":"fr","@value":"QC"}],"http://schema.org/addressCountry":[{"@language":"fr","@value":"CA"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_iqo251114001","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Needle Tea - Hennessy - PTB"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-14T20:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/iqo251114001?lang=fr#WebPage"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/iqo251114001?lang=fr"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/IQO/25/11/14/001/iqo251114001-1152x648-fr-68c857e6.png"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/78173530-d109-47fa-82ad-1122216cee9d"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/sameAs":[{"@value":"[\"https://www.facebook.com/profile.php?id=61559425892243\", \"https://www.facebook.com/HENNESSYTheBand\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_iqo251114001#Offer_0"}]},{"@id":"https://lepointdevente.com/billets/iqo251114001?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-12T01:13:28.993-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/iqo251114001?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_iqo251114001#Offer_0","http://schema.org/price":[{"@value":"16.7"}],"@type":["http://schema.org/Offer"],"http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_4rl251122001","@type":["http://schema.org/Event"],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-19T20:00:00-05:00"}],"http://schema.org/description":[{"@language":"fr","@value":" REBEL SOUND COLLECTIVE PRÉSENTE "}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-691"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/4rl251122001?lang=fr"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/4RL/25/11/22/001/4rl251122001-1152x648-fr-68d917c2.png"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/4rl251122001?lang=fr#WebPage"}],"http://schema.org/name":[{"@language":"fr","@value":"CODEFENDANTS - CAPABLE - THE LOOKOUT - WE TOLD YOU SO au TROU DU DIABLE"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_4rl251122001#Offer_0"}]},{"@id":"https://lepointdevente.com/billets/4rl251122001?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-18T01:10:41.302-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/4rl251122001?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_4rl251122001#Offer_0","http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_tdd251120001","@type":["http://schema.org/Event"],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/tdd251120001?lang=fr"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/tdd251120001?lang=fr#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/896cb34d-9e47-4dcb-91e4-4b1eecc665d5"}],"http://schema.org/name":[{"@language":"fr","@value":"Bernard Adamus"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/TDD/25/11/20/001/tdd251120001-1152x648-fr-68f8ccac.png"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-20T20:00:00-05:00"}],"http://schema.org/description":[{"@language":"fr","@value":" Artiste de souche vive, Bernard Adamus roule sa bosse depuis une douzaine d’années au Québec. Par les chemins de traverse, la route et les grands vents, il chante une chanson sincère et personnelle teintée de blues, de ville et de campagne. Ses amours et amitié.e.s sont au cœur de son travail expiatoire. Ayant gagné nombreux prix et trophées dans sa carrière, culminé plus de 1000 spectacles, il offre une présence sur scène rassurante et fraîche malgré les années qui passent. Authentique et sauvage, libre et naturel, c’est à chacun ses tourments. Une débauche intime, un regard parfois cru, Bernard Adamus salue la vie à sa manière. Le 29 février dernier il lançait un 5e opus, Chansons à l'huile "}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_tdd251120001#Offer_0"}]},{"@id":"https://lepointdevente.com/billets/tdd251120001?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-18T01:10:46.819-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/tdd251120001?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_tdd251120001#Offer_0","http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_chx251121003","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"2Frères - En Duo"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K10-435"},{"@id":"http://kg.artsdata.ca/resource/K14-32"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/chx251121003?lang=fr"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/CHX/25/11/21/003/chx251121003-1152x648-fr-67ffb817.png"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/b85d96d0-a152-4630-8b56-054ff2d11c96"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/chx251121003?lang=fr#WebPage"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-21T20:00:00-05:00"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/description":[{"@language":"fr","@value":" Découvrez l’univers intimiste de 2Frères avec leur tout nouveau spectacle acoustique En Duo – Chapitre Un. Dans une atmosphère conviviale et chaleureuse, les deux artistes vous inviteront à revisiter leurs plus grands succès, livrés avec une sincérité et une complicité émouvantes. Ce rendez-vous exceptionnel vous permettra de redécouvrir leurs chansons sous un angle inédit, dans des lieux magiques à travers le Québec. Laissez-vous transporter par la richesse de leur musicalité et le réconfort de leur univers, pour une expérience musicale mémorable et unique en son genre. Accès au site de l'événement De l'autoroute 40, prendre la sortie 229 (Ste-Geneviève-de-Batiscan). La salle Denis-Dupont loge dans l'école secondaire Le Tremplin située au 100, Chemin de la Rivière-à-Veillet. Informations relatives au stationnement Le stationnement se trouve à être celui de l'école secondaire Le Tremplin. Possibilité de se stationner sur le devant ou sur le côté de la bâtisse. "}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_chx251121003#Offer_0"}]},{"@id":"http://kg.footlight.io/resource/b85d96d0-a152-4630-8b56-054ff2d11c96","@type":["http://schema.org/Place"],"http://schema.org/name":[{"@language":"fr","@value":"Salle Denis Dupont"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"street_address at 100 Chem. de la Rivière à Veillet, Sainte-Geneviève-de-Batiscan, QC G0X 2R0, Canada"}],"http://schema.org/address":[{"@id":"http://kg.footlight.io/resource/b85d96d0-a152-4630-8b56-054ff2d11c96#PostalAddress"}],"http://schema.org/longitude":[{"@value":"-72.34401249999999"}],"http://schema.org/latitude":[{"@value":"46.5426586"}],"http://schema.org/sameAs":[{"@value":"https://maps.google.com/?cid=15682333156614950106"}]},{"@id":"https://lepointdevente.com/billets/chx251121003?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-20T01:12:19.322-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/chx251121003?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_chx251121003#Offer_0","http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"45.0"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/b85d96d0-a152-4630-8b56-054ff2d11c96#PostalAddress","@type":["http://schema.org/PostalAddress"],"http://schema.org/streetAddress":[{"@language":"fr","@value":"100 Chem. de la Rivière à Veillet"}],"http://schema.org/postalCode":[{"@value":"G0X 2R0"}],"http://schema.org/addressLocality":[{"@language":"fr","@value":"Sainte-Geneviève-de-Batiscan"}],"http://schema.org/addressRegion":[{"@language":"fr","@value":"QC"}],"http://schema.org/addressCountry":[{"@language":"fr","@value":"CA"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_tdd251121001","@type":["http://schema.org/Event"],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-21T20:00:00-05:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/896cb34d-9e47-4dcb-91e4-4b1eecc665d5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/tdd251121001?lang=fr"}],"http://schema.org/name":[{"@language":"fr","@value":"Bernard Adamus"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/TDD/25/11/21/001/tdd251121001-1152x648-fr-68af277c.png"}],"http://schema.org/description":[{"@language":"fr","@value":" Artiste de souche vive, Bernard Adamus roule sa bosse depuis une douzaine d’années au Québec. Par les chemins de traverse, la route et les grands vents, il chante une chanson sincère et personnelle teintée de blues, de ville et de campagne. Ses amours et amitié.e.s sont au cœur de son travail expiatoire. Ayant gagné nombreux prix et trophées dans sa carrière, culminé plus de 1000 spectacles, il offre une présence sur scène rassurante et fraîche malgré les années qui passent. Authentique et sauvage, libre et naturel, c’est à chacun ses tourments. Une débauche intime, un regard parfois cru, Bernard Adamus salue la vie à sa manière. Le 29 février dernier il lançait un 5e opus, Chansons à l'huile "}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/tdd251121001?lang=fr#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_tdd251121001#Offer_0"}]},{"@id":"https://lepointdevente.com/billets/tdd251121001?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-20T01:13:30.774-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/tdd251121001?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_tdd251121001#Offer_0","http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_9vh251126001","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"SHAWINIGAN SOIS TA MEILLEURE AMIE JOSEE BOUDREAULT"}],"http://schema.org/description":[{"@language":"fr","@value":" ★★★ SHAWINIGAN 26 NOV 2025 à 19h30 - seulement 32,95$ ★★★(réduction 5$ le billets si vous achetez 4 billets ou plus en meme temps. Ca vaut la peine de trouver quelques amies)Et oui! L'événement « Sois ta meilleure amie ! » ENFIN DE RETOUR À SHAWINIGAN à l'Hôtel Énergie Shawinigan, 1100 Prom. Du St-Maurice.C'est une salle intime, parfaite pour vous accueillir, vous rencontrer et passer une super soirée avec vous.LA SOIRÉE LA PLUS DROLE ET POSITIVE. UN ÉVÉNEMENT À NE PAS MANQUER! ON A HATE DE VOUS RENCONTRER.Et malgré le nom c’est autant pour les hommes que les femmes! Louis-Philippe est là avec moi et les sujets s’adressent à tous.Une soirée qui a pour but de vous faire rire un bon coup et vous aider à jeter un regard plus positif sur votre vie ! Un moment drôle, lumineux, rempli d’énergie.----------------Faites vite ! On a hâte de vous voir. On vous accueille à la porte, on vous jase, on passe vraiment une soirée avec vous !A QUOI S’ATTENDRE?On parle de bonheur à travers l'amour de soi et l'auto-compassion. De voir la vie avec un œil plus positif. D’auto-dérision. De gratitude, qui fait toute la différence dans la vie. Vous voulez savoir c’est quoi le \" Ouin Pis?\" Les qualifauts! Le I know I know! C’est ce que vous allez apprendre.Mais au-delà de tout ça, c’est une soirée très drôle. On rit beaucoup.Mon but c’est que vous partiez avec un grand sourire dans le visage. C’est une soirée qui fait du bien.Venez seule, en couple ou avec votre belle gang! De toute façon on va rire en masse!Sois ta (et ton) meilleure amie! Ose tout ce qui t’es possible!19h30 à 21h3032.95$ plus taxes et frais de billeterie.27,95$ plus taxes et frais de billeterie Spécial 4 personnes ou plus.Vous êtes un groupe de 8 personnes ou plus? Écrivez-nous au joseeeventbrite@gmail.com pour voir ce qu'on peut faire pour vous.On a très hâte de vous voir, parlez-en à ceux que vous aimez!VOS BILLETS SERONT ENVOYÉS AUTOMATIQUEMENT DANS VOTRE BOITE COURRIEL. ASSUREZ-VOUS DE BIEN ÉCRIRE VOTRE COURRIEL EN COMMANDANT ET SI VOUS NE LES VOYEZ PAS APPARAITRE, REGARDEZ DANS VOS INDÉSIRABLES.VOUS POURREZ LES IMPRIMER, NOUS LES MONTRER SUR VOTRE TELEPHONE OU SIMPLEMENT NOUS DONNER VOTRE NOM À L'ENTRÉE. C'EST NOUS QUI VOUS ACCUEILLONS ET C'EST TRÈS FACILE. Y'A JAMAIS DE PROBLÈME :)Vous avez des questions après avoir acheté vos billets? Écrivez-nous au joseeeventbrite@gmail.com "}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/9VH/25/11/26/001/9vh251126001-1152x648-fr-68b5d88e.png"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/9vh251126001?lang=fr#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-726"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-26T19:30:00-05:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/9vh251126001?lang=fr"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_9vh251126001#Offer_0"},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_9vh251126001#Offer_1"}]},{"@id":"https://lepointdevente.com/billets/9vh251126001?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-24T01:12:34.934-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/9vh251126001?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_9vh251126001#Offer_0","http://schema.org/price":[{"@value":"39.67"}],"@type":["http://schema.org/Offer"],"http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_9vh251126001#Offer_1","http://schema.org/price":[{"@value":"33.79"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"RABAIS 4 PERSONNES OU PLUS"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_064251128001","@type":["http://schema.org/Event"],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-28T20:30:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/064251128001?lang=fr#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/image":[{"@value":"[\"https://tpos.s3.amazonaws.com/categories/music-5b280390.png\", \"https://cdn.lepointdevente.com/themes/tpos/ogp-img-fr-5f32b556.gif\"]"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/064251128001?lang=fr"}],"http://schema.org/name":[{"@language":"fr","@value":"Reanimator + Genetic Error + Apocalyptic Fear"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/896cb34d-9e47-4dcb-91e4-4b1eecc665d5"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_064251128001#Offer_0"}]},{"@id":"https://lepointdevente.com/billets/064251128001?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-26T01:14:13.337-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/064251128001?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_064251128001#Offer_0","http://schema.org/price":[{"@value":"26.49"}],"@type":["http://schema.org/Offer"],"http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_jjc251129001","@type":["http://schema.org/Event"],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/032ed8bb-8d32-4f7a-ae09-f31ba9fba8f4"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/jjc251129001?lang=fr"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"},{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/JJC/25/11/29/001/jjc251129001-1152x648-fr-68f01040.png"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-02T20:00:00-04:00"}],"http://schema.org/description":[{"@language":"fr","@value":" Accès au site de l'événement L'ENTITÉ. | 18+ 1420 Notre-Dame Centre, Trois-Rivières, Québec, G9A 4X3 "}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/0a06696f-60e1-4351-aac0-115cf5702bdd"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/jjc251129001?lang=fr#WebPage"}],"http://schema.org/name":[{"@language":"fr","@value":"Hommage 80’s - 5 Doors Away à Trois-Rivières | L'ENTITÉ"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_jjc251129001#Offer_0"}]},{"@id":"https://lepointdevente.com/billets/jjc251129001?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-02T01:11:15.750-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/jjc251129001?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_jjc251129001#Offer_0","http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_sexmales291125","@type":["http://schema.org/Event"],"http://schema.org/sameAs":[{"@value":"[\"https://www.facebook.com/mbproductionstr/\"]"}],"http://schema.org/name":[{"@language":"fr","@value":"CANADIAN SEXY MALES"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-29T21:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/sexmales291125?lang=fr#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/SNO/25/11/29/001/sno251129001-1152x648-fr-68b0b8a7.png"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/sexmales291125?lang=fr"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/2fa05002-967b-43c2-b5cb-fc93a25edcb1"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/6b390936-786c-4899-83df-efa1db9fea69"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_sexmales291125#Offer_0"}]},{"@id":"https://lepointdevente.com/billets/sexmales291125?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-28T01:12:38.105-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/sexmales291125?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/2fa05002-967b-43c2-b5cb-fc93a25edcb1","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Danse"}],"http://www.w3.org/2004/02/skos/core#closeMatch":[{"@id":"http://kg.artsdata.ca/resource/DancePerformance"}]},{"@id":"http://kg.footlight.io/resource/6b390936-786c-4899-83df-efa1db9fea69","@type":["http://schema.org/Place"],"http://schema.org/name":[{"@language":"fr","@value":"Club curling Trois-Rivières"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"point_of_interest, establishment at 630 Rue Ste Ursule, Trois-Rivières, QC G9A 1P1, Canada"}],"http://schema.org/address":[{"@id":"http://kg.footlight.io/resource/6b390936-786c-4899-83df-efa1db9fea69#PostalAddress"}],"http://schema.org/longitude":[{"@value":"-72.5411122"}],"http://schema.org/latitude":[{"@value":"46.3482651"}],"http://schema.org/sameAs":[{"@value":"https://maps.google.com/?cid=18152424141150455365"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_sexmales291125#Offer_0","http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"40.22"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale - debout 18 +"}]},{"@id":"http://kg.footlight.io/resource/6b390936-786c-4899-83df-efa1db9fea69#PostalAddress","@type":["http://schema.org/PostalAddress"],"http://schema.org/streetAddress":[{"@language":"fr","@value":"630 Rue Ste Ursule"}],"http://schema.org/postalCode":[{"@value":"G9A 1P1"}],"http://schema.org/addressLocality":[{"@language":"fr","@value":"Trois-Rivières"}],"http://schema.org/addressRegion":[{"@language":"fr","@value":"QC"}],"http://schema.org/addressCountry":[{"@language":"fr","@value":"CA"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_4rl251129001","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/4rl251129001?lang=fr"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/51f8b4cb-d760-43b0-ba6d-dfa8f342050b"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/name":[{"@language":"fr","@value":"K MAN \u0026 the 45’s - Dirty cheetah au ZENOB"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/4rl251129001?lang=fr#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/4RL/25/11/29/001/4rl251129001-1152x648-fr-6866dbbc.png"}],"http://schema.org/description":[{"@language":"fr","@value":" REBEL SOUND COLLECTIVE PRÉSENTE "}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-29T21:00:00-05:00"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_4rl251129001#Offer_0"}]},{"@id":"https://lepointdevente.com/billets/4rl251129001?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-28T01:13:56.156-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/4rl251129001?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_4rl251129001#Offer_0","http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"16.26"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_joe251203001","@type":["http://schema.org/Event"],"http://schema.org/mainEntityOfPage":[{"@id":"https://lepointdevente.com/billets/joe251203001?lang=fr#WebPage"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/JOE/25/12/03/001/joe251203001-1152x648-en-68431a1d.png"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-12-03T20:00:00-05:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/sameAs":[{"@value":"[\"https://www.facebook.com/JacobOspian/\"]"}],"http://schema.org/name":[{"@language":"fr","@value":"Jacob Ospian en Rodage"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-698"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/joe251203001?lang=fr"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/description":[{"@language":"fr","@value":" En devenant Papa, je pensais être capable de subvenir aux besoins de ma famille en faisant 200 000$ par année tout en ayant une relation d’amour, de respect et de plaisir avec mes filles.Aujourd’hui, je conduis un Honda Civic 2009 qui a le même état qu’une auto tamponneuse de La Ronde, je suis père au foyer et la phrase que mes filles me disent en espagnol, c’est «Bro, tu gosses».Nouveau show. Nouvelles blagues. 1 heure de réflexion vraiment stupide. "}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_joe251203001#Offer_0"}]},{"@id":"https://lepointdevente.com/billets/joe251203001?lang=fr#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-12-02T01:12:25.005-05:00"}],"http://schema.org/url":[{"@value":"https://lepointdevente.com/billets/joe251203001?lang=fr"}]},{"@id":"http://kg.footlight.io/resource/culturemauricie_lepointdevente-com_joe251203001#Offer_0","http://schema.org/availability":[{"@value":"http://schema.org/InStock"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"25.0"}],"http://schema.org/name":[{"@language":"fr","@value":"General Admission"}]}]
\ No newline at end of file
diff --git a/data/migration_baseline/gatineau_cloud.jsonld b/data/migration_baseline/gatineau_cloud.jsonld
new file mode 100644
index 00000000..0637a088
--- /dev/null
+++ b/data/migration_baseline/gatineau_cloud.jsonld
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/data/migration_baseline/maisondelaculture_ca.jsonld b/data/migration_baseline/maisondelaculture_ca.jsonld
new file mode 100644
index 00000000..3d6aac02
--- /dev/null
+++ b/data/migration_baseline/maisondelaculture_ca.jsonld
@@ -0,0 +1 @@
+[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-ge-perou","@type":["http://schema.org/Event"],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-28T19:30:00-05:00"}],"http://schema.org/recordedIn":[{"@value":"https://player.vimeo.com/video/1076125183?badge=0\u0026autopause=0\u0026player_id=0\u0026app_id=58479"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/ge-perou.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Les Grands Explorateurs"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K10-427"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"autres-evenements"}],"http://schema.org/description":[{"@language":"fr","@value":"Bienvenue au royaume de la civilisation inca où des sites archéologiques impressionnants se mêlent à une nature époustouflante. Découvrez les richesses ethniques, géographiques et historiques de ce pays, sans oublier les incontournables Machu Picchu et la Montagne des 7 couleurs. Une aventure inoubliable, au rythme des trésors et des rencontres."}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/autres-evenements/ge-perou.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/ge-perou.html#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/656dca7a-5549-494c-acbf-8c05662b23bf"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-ge-perou#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-ge-perou#Offer_1"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-ge-perou#Offer_2"}]},{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5","@type":["http://schema.org/Organization"],"http://schema.org/name":[{"@language":"fr","@value":"Maison de la culture de Gatineau"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Organization"}],"http://schema.org/sameAs":[{"@value":"http://www.wikidata.org/entity/Q111857187"},{"@value":"http://kg.artsdata.ca/resource/K16-67"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/ge-perou.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-26T16:13:28.054-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/ge-perou.html"}]},{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a","@type":["http://schema.org/Place"],"http://schema.org/name":[{"@language":"fr","@value":"Salle Odyssée"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Place at 855 Bd de la Gappe, Gatineau"}],"http://schema.org/address":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a#PostalAddress"}],"http://schema.org/longitude":[{"@value":"-75.6816667"}],"http://schema.org/latitude":[{"@value":"45.48444439999999"}],"http://schema.org/sameAs":[{"@value":"https://maps.google.com/?cid=3955112665367020040"},{"@value":"http://kg.artsdata.ca/resource/K2-197"}]},{"@id":"http://kg.footlight.io/resource/656dca7a-5549-494c-acbf-8c05662b23bf","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#closeMatch":[{"@id":"http://kg.artsdata.ca/resource/ScreeningEvent"}],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Cinéma"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-ge-perou#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E050A\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"33.92"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-ge-perou#Offer_1","http://schema.org/price":[{"@value":"31.04"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"60 ans et +"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-ge-perou#Offer_2","http://schema.org/price":[{"@value":"22.42"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"25 ans et moins"}]},{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a#PostalAddress","@type":["http://schema.org/PostalAddress"],"http://schema.org/streetAddress":[{"@language":"fr","@value":"855 Bd de la Gappe"}],"http://schema.org/postalCode":[{"@value":"J8T 8H9"}],"http://schema.org/addressLocality":[{"@language":"fr","@value":"Gatineau"}],"http://schema.org/addressRegion":[{"@language":"fr","@value":"QC"}],"http://schema.org/addressCountry":[{"@language":"fr","@value":"CA"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-michelle-desrochers","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/b9f8bf8f-e7ec-41fb-a870-92ca3b19fbc2"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/michelle-desrochers.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Michelle Desrochers"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/soirees-cabaret/michelle-desrochers-26.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-06-22T20:00:00-04:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/michelle-desrochers.html#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"soirees-cabaret"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/4vRw9PKXSpQ?si=PZ6tpshNekV2eI1t"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/dad71250-8ec8-4b41-aa18-47eef056a04c"}],"http://schema.org/description":[{"@language":"fr","@value":"Mi-cuit, c’est mon spectacle… presque prêt! Idéal si vous avez envie de rire et si vous souhaitez secrètement qu’une trentenaire qui ne sait pas cuisiner vous donne la recette du bonheur. Mi-cuit, c’est une liste d’ingrédients et beaucoup de bonne volonté, mais ce n'est pas tout à fait terminé. Comme le mot «rodage» a perdu tout son sens, je vous ai préparé cet humble petit gâteau. Je vous promets une soirée qui fait autant de bien que d’éteindre la hotte du four après une longue recette! * Formule cabaret au foyer de la salle Odyssée / Ambiance intime et conviviale / Bar en fonction / Places limitées"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-michelle-desrochers#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-michelle-desrochers#Offer_1"}]},{"@id":"http://kg.footlight.io/resource/b9f8bf8f-e7ec-41fb-a870-92ca3b19fbc2","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Michelle Desrochers"}],"http://schema.org/hasOccupation":[{"@value":"Humoriste"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}],"http://schema.org/url":[{"@value":"https://michelledesrochers.ca/"}],"http://schema.org/sameAs":[{"@value":"http://www.wikidata.org/entity/Q111686001"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/michelle-desrochers.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:31:20.514-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/michelle-desrochers.html"}]},{"@id":"http://kg.footlight.io/resource/dad71250-8ec8-4b41-aa18-47eef056a04c","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"soirees-cabaret"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-michelle-desrochers#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E0566\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"29"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-michelle-desrochers#Offer_1","http://schema.org/price":[{"@value":"25"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-guillaume-pineault","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Guillaume Pineault"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/spec-qc-ca_artiste_guillaume-pineault"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/guillaume-pineault.html"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-20T20:00:00-05:00"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/guillaume-pineault.html#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/6YNLW6Qy34Y?si=XeZ1qFSFrHnvKgi1"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/guillaume-pineault-26.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"C’est reparti pour un 3ème spectacle! Mais avant de le sortir, je dois le travailler, le tester, le peaufiner, bref… le RODER! C’est avec vous que j’ai envie de faire ça! Bienvenue au rodage de mon 3ème spectacle. Hâte de vous voir, Guillaume * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-guillaume-pineault#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-guillaume-pineault#Offer_1"}]},{"@id":"http://kg.footlight.io/resource/spec-qc-ca_artiste_guillaume-pineault","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@value":"Guillaume Pineault"}],"http://schema.org/sameAs":[{"@value":"http://kg.artsdata.ca/resource/K13-77"}],"http://schema.org/image":[{"@value":"https://spec.qc.ca/uploads/artistes/guillaume-pineault.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"Guillaume Pineault a d’abord été ergothérapeute puis ostéopathe; et c’est finalement en humour qu’il a conclu qu'il préférait aider son prochain. Reconnu pour son sens de l’anecdote hautement aiguisé et son regard unique sur les choses qui l’entourent, Guillaume est un narrateur né, candide, sympathique et doté d’un charisme indéniable. Ses réflexions naissent toutes d’histoires personnelles et improbables dans lesquelles il relève tous les petits détails qui provoquent l’hilarité. Actuellement vous pouvez voir Guillaume sur scène un peu partout au Québec où il présente son spectacle Détour et aussi l’entendre à la radio dans l’équipe de Véronique et Les Fantastiques au 107,3 Rouge Fm. Source : guillaumepineault.com"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/guillaume-pineault.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:29:13.187-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/guillaume-pineault.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-guillaume-pineault#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E0567\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"35"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-guillaume-pineault#Offer_1","http://schema.org/price":[{"@value":"31"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-alexandre-forest","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Alexandre Forest"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/alexandre-forest.html"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/alexandre-forest.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-12-19T20:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/alexandre-forest.html#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/4leFnm5PGJ0?si=flWIfDeDe2XMp1Ux"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/description":[{"@language":"fr","@value":"Alexandre Forest entame le rodage de son premier one man show. C’est l'occasion parfaite de le découvrir et de vous laisser emporter dans son univers. Grâce à un alliage subtil entre autodérision et finesse d'esprit, l'humoriste réussit à capturer avec brio l’essence des petites et grandes incongruences de la vie quotidienne. * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-alexandre-forest#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-alexandre-forest#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/alexandre-forest.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:30:13.791-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/alexandre-forest.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-alexandre-forest#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E0565\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"30"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-alexandre-forest#Offer_1","http://schema.org/price":[{"@value":"26"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-sam-breton","http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-11T20:00:00-05:00"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-255"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/name":[{"@language":"fr","@value":"Sam Breton"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/sam-breton.html"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/sam-breton-2026.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/sam-breton.html#WebPage"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/pf4hQBe0VMY?si=MuLkxzJWujlkcG1c"}],"http://schema.org/description":[{"@language":"fr","@value":"Vivre un épuisement professionnel, c'est aussi vivre une recharge professionnelle. Autrement dit: travailler mon deuxième one-man-show de façon encore plus efficace, sans perdre de temps avec des niaiseries. Là, j'aimerais bien te vanter mon spectacle pour te convaincre de venir me voir sur scène, mais ça entrerait dans la catégorie «perte de temps», parce que de toute façon, juste avec ma face, tu l'sais déjà si tu vas venir voir mon show ou non. Avoue? ;) «Son nouveau spectacle est un grand moment d'humour. À voir absolument.» - Louis-Philippe Ouimet, Le Téléjournal «[Sam] vient confirmer sa place parmi les meilleurs humoristes de sa génération.» - Luc Boulanger, La Presse «Un spectacle rythmé et bien ficelé qui ne laisse aucun répit aux spectateurs. Quel raconteur!» - Isabelle Perron, QUB «Son talent de conteur, il est excellent là-dedans, c'est sa très grande force. [...] C'est hallucinant, sa présence sur scène.» - Catherine Brisson, 98,5 - Lagacé le matin «Une livraison magistrale, drôle et touchante, dont seul Sam Breton connaît la recette!» - Anne-Andrée Daneau, TVA/LCN «Ce mélange parfait d'émotion et de rires a donné lieu à un spectacle touchant, intelligent et hilarant.» - Jessica-Émilie Therrien, Hollywood PQ"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-sam-breton#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-sam-breton#Offer_1"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-07-11T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-sam-breton#2026-02-11T200000-0500"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-sam-breton#2026-02-12T200000-0500"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-sam-breton#2026-03-20T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-sam-breton#2026-07-10T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-sam-breton#2026-07-11T200000-0400"}]},{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Humour"}],"http://www.w3.org/2004/02/skos/core#closeMatch":[{"@id":"http://kg.artsdata.ca/resource/ComedyPerformance"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/sam-breton.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:12:10.330-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/sam-breton.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-sam-breton#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E017D\u0026CodeSiteClient=00000V"}],"http://schema.org/price":[{"@value":"43"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-sam-breton#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"39"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-sam-breton#2026-02-11T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Sam Breton"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-11T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-sam-breton#2026-02-12T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Sam Breton"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-12T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-sam-breton#2026-03-20T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Sam Breton"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-20T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-sam-breton#2026-07-10T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Sam Breton"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-07-10T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-sam-breton#2026-07-11T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Sam Breton"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-07-11T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_comedie-et-theatre-musical-menopause","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Ménopause"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-06-05T20:00:00-04:00"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/sO7wVU5qVsw?si=2Up3WdGQQculoY9L"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/5b8f9e4a-cd16-45ae-aa69-8f9dc30c77d9"}],"http://schema.org/keywords":[{"@value":"comedie-et-theatre-musical"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/comedie-et-theatre-musical/menopause.html"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/comedie-et-theatre-musical/menopause-2026.jpg"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/comedie-et-theatre-musical/menopause.html#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"La célèbre parodie musicale Ménopause, présentée par Vitoli, arrive pour la toute première fois au Québec! À l’affiche depuis plus de 15 ans à Las Vegas, triomphant à Paris depuis 2 ans et ayant été applaudi par près de 17 millions de personnes dans 16 pays, ce spectacle festif pose un regard hilarant et sincère sur ce que les femmes vivent lorsqu’elles traversent la ménopause. Mettant en vedette les talentueuses Guylaine Tanguay, Claudine Mercier, Geneviève Charest et Catherine Sénart dans une mise en scène de Alexis Pitkevicht, Ménopause, la parodie musicale vous fera rire aux éclats, mais aussi chanter et danser sur des parodies de grands succès musicaux anglophones des années 60, 70 et 80 que vous adorez et dont l’adaptation francophone humoristique est signée par la prolifique Danièle Lorain. Pertes de mémoire, bouffées de chaleur, sueurs nocturnes, vie sexuelle inerte… ou trop active! Lorsque quatre femmes venant de milieux très différents se rencontrent par hasard dans une vente de lingerie, elles se rendent comptent qu’elles partagent au moins une chose : la ménopause. Après tout, les femmes ménopausées sont toutes dans le même bain… mieux vaut en rire et transformer ce sujet tabou en une célébration remplie de légèreté et de sororité! Psst : Les hommes sont également les bienvenus, alors qu’ils pourront enfin mieux comprendre ce que vivent les femmes de leur vie… tout en riant avec elles! Ménopause, le succès mondial qui défie les règles! Créations/textes Jeanie Linders Mise en scène Alexis Pitkevitch Adaptation Danièle Lorain Distribution Guylaine Tanguay (La star), Claudine Mercier (La dame nature), Geneviève Charest (La femme d’affaire) et Catherine Sénart (La mère de famille) Production Entourage, par licence spéciale avec GFour Productions «Un spectacle feel-good, incroyablement drôle et mené par quatre interprètes d'exception.» - Journal de Montréal «La joie est palpable sur scène et contagieuse. Il y a beaucoup de travail derrière ce spectacle!» - FM 103,3 «Guylaine Tanguay a un talent indéniable pour la comédie.» - Échos Vedettes «Un spectacle hilarant, festif et complètement décomplexé, réussissant brillamment à dédramatiser cette étape marquante de la vie des femmes.» - Les ArtsZé «Toutes les femmes qui traversent cette étape se reconnaîtront à coup sûr.» - Les ArtsZé «Des performance remarquables où chacune brille à sa façon.» - Les ArtsZé «Chanter ses symptômes, c'est libérateur!» - Salut Bonjour «Rarement la femme a-t-elle été autant célébrée.» - Bel âge «Une belle bande de femmes intelligentes capables de \"ventiler\" les bobos de l'âge avec aplomb.» - Bel âge «Rire, rire et rire. Une parodie musicale haute en couleur avec quatre comédiennes remplies de talent!» - Meve et cie"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_comedie-et-theatre-musical-menopause#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_comedie-et-theatre-musical-menopause#Offer_1"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_comedie-et-theatre-musical-menopause#Offer_2"}]},{"@id":"http://kg.footlight.io/resource/5b8f9e4a-cd16-45ae-aa69-8f9dc30c77d9","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Comédie musicale"}],"http://www.w3.org/2004/02/skos/core#closeMatch":[{"@id":"http://kg.artsdata.ca/resource/MusicalComedyPerformance"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/comedie-et-theatre-musical/menopause.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:13:47.070-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/comedie-et-theatre-musical/menopause.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_comedie-et-theatre-musical-menopause#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"69"}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E01N6\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_comedie-et-theatre-musical-menopause#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"65"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_comedie-et-theatre-musical-menopause#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"→ Abonnés théâtre*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"55"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-hosanna","http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-23T20:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/rendez-vous-theatre/hosanna.html#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/name":[{"@language":"fr","@value":"Hosanna ou La Shéhérazade des pauvres"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/description":[{"@language":"fr","@value":"Yannick, un jeune journaliste de Fugues – le magazine LGBTQ+ – a réussi à retrouver Claude Lemieux, celui qui, en 1973, sous le nom d’Hosanna, avait été ridiculisé jusqu’à l’anéantissement par ses «amies» de la Main. Pourtant, ce soir-là, il allait enfin réaliser son rêve: être accepté au sein de leur cénacle en paraissant devant elles déguisée en Cléopâtre telle qu’incarnée par Elizabeth Taylor. Yannick veut l’interroger sur le contexte socioculturel de cette soirée dont la légende est en train de définitivement s’effacer. Or, au journaliste, qui souhaite parler de culture queer et d’expression de genre, Claude Lemieux répond en faisant apparaître devant lui (et devant nous!) les splendeurs et misères passées d’un Montréal marginal en particulier le glorieusement kitsch Hawaïan Lounge et ses extravagantes vedettes: la perfide Sandra et la grandiose Duchesse de Langeais. Aujourd’hui, les drag queens font partie de notre culture populaire. Mais, il y a cinquante ans, qu’est-ce que cela impliquait pour un homme que de se vêtir comme une femme? En public! En assemblant des extraits d’Hosanna et du récent roman de Tremblay La Shéhérazade des pauvres – émaillés de quelques pétillantes pépites d’Il était une fois dans l’Est –, le metteur en scène Maxime Robin réussit un tour de force : à la fois, raconter les avancées fragiles d’un groupe social et l’histoire profondément émouvante d’un homme blessé par ses propres rêves. Maxime Robin a eu l’idée lumineuse d’accorder le rôle d’Hosanna âgé, à Luc Provost qui, par son personnage de Mado Lamotte, a fait entrer les drag queens dans notre quotidien. Ce spectacle, qui a triomphé lors de sa création au Théâtre du Trident en 2023, est enfin présenté à Gatineau. Texte Michel Tremblay Montage et mise en scène Maxime Robin Distribution Gabriel Fournier, Jonathan Gagnon, Valérie Laroche, Jacques Leblanc, Luc Provost, Vincent Roy, Sally Sakho Production Une création du Théâtre du Trident en coproduction avec le Théâtre du Nouveau Monde"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/rendez-vous-theatre/hosanna.html"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/rendez-vous-theatre/hosanna.jpg"}],"http://schema.org/keywords":[{"@value":"rendez-vous-theatre"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/Ru3Ujm61RQs?si=1ZMzQ3Biu9rdWUrJ"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-hosanna#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-hosanna#Offer_1"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-hosanna#Offer_2"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-hosanna#Offer_3"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-24T20:00:00-05:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-hosanna#2026-01-23T200000-0500"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-hosanna#2026-01-24T200000-0500"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/rendez-vous-theatre/hosanna.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-22T16:24:24.263-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/rendez-vous-theatre/hosanna.html"}]},{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Théâtre"}],"http://www.w3.org/2004/02/skos/core#closeMatch":[{"@id":"http://kg.artsdata.ca/resource/TheatrePerformance"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-hosanna#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E01OW\u0026CodeSiteClient=00000V"}],"http://schema.org/price":[{"@value":"54"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-hosanna#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"50"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-hosanna#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"30 ans et - / 60 ans et +"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"49"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-hosanna#Offer_3","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée 30 ans et - / 60 ans et +"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"45"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-hosanna#2026-01-23T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Hosanna ou La Shéhérazade des pauvres"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-23T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-hosanna#2026-01-24T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Hosanna ou La Shéhérazade des pauvres"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-24T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-dom-babin","@type":["http://schema.org/Event"],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-03T20:00:00-04:00"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/dom-babin.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Dom Babin"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/dom-babin.html"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/dom-babin.html#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/description":[{"@language":"fr","@value":"En passant, c’est du rodage. Pas juste du spectacle, j’rode aussi ma personne. J’veux pas être le dude qui finit sa personnalité en même temps que l’secondaire, qui arrive à 30 ans pis fait des stories sur Snapchat. Ça fait que j’essaye de pas m’prendre pour acquis. Mais chose sûre, c’est qu’une grosse partie de qui tu es dépend d’où tu viens. J’descends d’une grande famille de débrouillards de la Gaspésie qui a fini par make it. Des pauvres devenus riches en gardant leurs valeurs de pauvres. J’trouve qu’il y a quelque chose de poétique quand je vois des millionnaires qui mange du stew au baloney à Noël. Mes parents ont tellement make it qu’ils ont déménagé à Rimouski. Ouais, dans l’temps c’était ça make it. J’ai grandi d’une place qui s’passe rien fait que tu sais toute. Y’a aucun crime mais y’a paquet de témoins. Tout le monde voit les mêmes choses mais l’entend différemment. Ça fait que quand j’raconte mes histoires, c’est pour que t’entendes les tiennes. J’descends faire le tour. Viens rire de moi, viens rire de toi, viens rire de nous. En nomination au Gala Les Olivier 2026: Numéro d’humour de l’année Capsule ou sketch Web humoristique de l’année * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-dom-babin#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-dom-babin#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/dom-babin.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:19:01.918-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/dom-babin.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-dom-babin#Offer_0","http://schema.org/price":[{"@value":"38"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E051E\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-dom-babin#Offer_1","http://schema.org/price":[{"@value":"34"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-zenith-le-spectacle","http://schema.org/name":[{"@language":"fr","@value":"Zénith, le spectacle"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-12-11T20:00:00-05:00"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/varietes/zenith-le-spectacle.html#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/varietes/zenith-le-spectacle.jpg"}],"http://schema.org/keywords":[{"@value":"varietes"}],"http://schema.org/description":[{"@language":"fr","@value":"Préparez-vous pour une soirée musicale inoubliable! Véronique Cloutier vous invite à plonger dans un spectacle de variétés époustouflant où la passion pour la musique unit toutes les générations. Venez redécouvrir vos artistes préférés des générations Z, Y, X et boomers, ceux qui ont marqué l’émission et fait vibrer votre cœur. Ils vous offriront des performances grandioses, mêlant les succès emblématiques d’hier aux hits d’aujourd’hui, le tout revisité dans une mise en scène éblouissante. Avec un band live et des danseurs talentueux, attendez-vous à vivre toute une expérience mémorable. Ne manquez pas cette occasion de faire la fête et de chanter à pleins poumons. Soyez de la partie pour une soirée exceptionnelle! Animation : Véronique Cloutier"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/varietes/zenith-le-spectacle.html"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-zenith-le-spectacle#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-zenith-le-spectacle#Offer_1"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-12-12T20:00:00-05:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-zenith-le-spectacle#2026-12-11T200000-0500"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-zenith-le-spectacle#2026-12-12T200000-0500"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/varietes/zenith-le-spectacle.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:36:53.444-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/varietes/zenith-le-spectacle.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-zenith-le-spectacle#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E01QZ\u0026CodeSiteClient=00000V"}],"http://schema.org/price":[{"@value":"94"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-zenith-le-spectacle#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"90"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-zenith-le-spectacle#2026-12-11T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Zénith, le spectacle"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-12-11T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-zenith-le-spectacle#2026-12-12T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Zénith, le spectacle"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-12-12T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-florida","@type":["http://schema.org/Event"],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2027-04-25T20:00:00-04:00"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/7vhVzT3ownw?si=MEp42PSxB7Ij6NQv"}],"http://schema.org/name":[{"@language":"fr","@value":"La Florida"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/la-florida.html"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/theatre-hors-serie/la-florida.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/la-florida.html#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"theatre-hors-serie"}],"http://schema.org/description":[{"@language":"fr","@value":"Trente ans après avoir conquis tout le Québec au cinéma, La Florida atterrit sur scène. Léo L’Espérance, sa merveilleuse femme Ginette, leurs enfants et Pépère prennent la route pour Hollywood Beach, convaincus d’avoir trouvé leur paradis jusqu’à ce que leur rêve se noie dans le fond de la piscine. Clients capricieux, voisins intimidants et rénovations à ne plus finir: bienvenue au Motel Ginette, où le rêve américain se prend une bonne tasse d’eau salée… et de réalité. Dans un décor tropical délicieusement kitsch, les répliques cultes que tout le monde connaît par cœur sont de retour, servies dans une comédie aussi hilarante que touchante, aussi lumineuse que cruelle. Adaptée du film culte de 1993, récipiendaire du Prix Bobine d’Or pour le plus grand succès au box-office québécois, La Florida revient briller sur scène sous la mise en scène de Charles Dauphinais, entourée de nos snowbirds préférés, Louis Champagne, Didier Lucien, Gary Boudreault, Marc St-Martin, Joakim Robillard et plusieurs autres. Enweille au théâtre, maudites chanceuses... et chanceux!"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-florida#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-florida#Offer_1"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-florida#Offer_2"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/la-florida.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:37:02.258-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/la-florida.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-florida#Offer_0","http://schema.org/price":[{"@value":"68"}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E052U\u0026CodeSiteClient=00000V"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-florida#Offer_1","http://schema.org/price":[{"@value":"64"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-florida#Offer_2","http://schema.org/price":[{"@value":"54"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"→ Abonnés théâtre*"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-emmanuelle-querry","@type":["http://schema.org/Event"],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-18T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/emmanuelle-querry.html#WebPage"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/emmanuelle-querry.html"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/soirees-cabaret/emmanuelle-querry.jpg"}],"http://schema.org/name":[{"@language":"fr","@value":"Emmanuelle Querry"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"soirees-cabaret"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/8tz6dknUJ88?si=V6gpFdcrDDh4CGR6"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/dad71250-8ec8-4b41-aa18-47eef056a04c"}],"http://schema.org/description":[{"@language":"fr","@value":"Récipiendaire du prix de la salle Odyssée, remis dans le cadre des Escales en chanson du Festival en chanson de Petite-Vallée. En savoir plus ici. Emmanuelle Querry nous livre une pop à la fois sensible et éclatante, à la croisée du R\u0026B et de l’électro. Originaire de Gatineau, la jeune artiste revient dans sa région natale pour célébrer la sortie de son premier album à travers un spectacle à la fois lumineux et rassembleur. Accompagnée de deux musiciens, elle déploie sur scène une formule trio aussi intime qu’électrisante, où chaque morceau prend vie. Les contrastes d’ambiances et de textures, finement dosés, créent une tension captivante qui garde le public suspendu à chaque parole. Dans une mise en scène suave et satinée, le spectacle alterne entre moments de douceur et élans flamboyants, toujours avec une attention minutieuse portée au détail et à la musicalité. Demi-finaliste des Francouvertes en 2022 et participante à la plus récente cohorte des Escales en chansons de Petite-Vallée, Emmanuelle s’est déjà démarquée sur de nombreuses scènes à travers le Québec. On a également pu la voir dans la région au Festival des Montgolfières, au Bluesfest d’Ottawa, aux Feux sur Glace, à la Fête nationale dans le Vieux-Hull en 2022, ainsi qu’à la célébration des 20 ans de Gatineau en première partie de Vincent Vallières la même année. Portée par une esthétique glamour et une présence magnétique, Emmanuelle Querry s’impose comme une artiste émergente incontournable, faisant rayonner une pop moderne, vibrante et sincère. * Formule cabaret au foyer de la salle Odyssée / Ambiance intime et conviviale / Bar en fonction / Places limitées"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-emmanuelle-querry#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-emmanuelle-querry#Offer_1"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-emmanuelle-querry#Offer_2"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/emmanuelle-querry.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:32:17.404-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/emmanuelle-querry.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-emmanuelle-querry#Offer_0","http://schema.org/price":[{"@value":"28"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E052N\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-emmanuelle-querry#Offer_1","http://schema.org/price":[{"@value":"24"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-emmanuelle-querry#Offer_2","http://schema.org/price":[{"@value":"20"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"étudiants"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mariana-mazza-2","http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-06-11T20:00:00-04:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/4EZ8q23RTNY?si=dOeRwRh6cjEmmB--"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/mariana-mazza-2.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Mariana Mazza"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-382"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/mariana-mazza-2.html#WebPage"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/mariana-mazza-2025.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"Pour son troisième spectacle, Foie Gras, Mariana Mazza décide de s’exprimer avec sa tête et son cœur… pour faire changement. Dans ce one-woman-show bien personnel, la jeune fille qui a grandi à Montréal-Nord, maintenant rendue une femme embourgeoisée qui vit à Saint-Lambert avec ses caniches et ses toiles invendues, se montre aussi brillante qu’hilarante. Étant habituée à dire des choses uniquement pour provoquer un rire, elle souhaite aller plus loin cette fois et faire un coming out au public : elle est émotive et vulnérable. Le chihuahua sur le Red Bull est officiellement remplacé par le tank au cœur de porcelaine. Comme elle le dit si bien : «C’est moi la meilleure tabarnak!» Oui, Mariana a changé. Mais à quel point? En nomination au Gala Les Olivier 2025: Olivier de l’année «Mariana Mazza signe son spectacle le plus abouti, et aussi le plus révélateur» - Dominic Tardif, La Presse «Mariana c’est la reine… elle est féroce, extraordinaire, drôle, touchante, attachante» - Benoit Dutrizac, QUB radio «Mariana m’a fait rire et pleurer, parfois dans la même minute. J’ai ADORÉ chaque seconde de ce spectacle inoubliable.» - Sophie Durocher, Journal de Montréal"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mariana-mazza-2#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mariana-mazza-2#Offer_1"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-29T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mariana-mazza-2#2026-06-11T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mariana-mazza-2#2026-09-29T200000-0400"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/mariana-mazza-2.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:37:58.444-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/mariana-mazza-2.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mariana-mazza-2#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E00XZ\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"54"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mariana-mazza-2#Offer_1","http://schema.org/price":[{"@value":"50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mariana-mazza-2#2026-06-11T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Mariana Mazza"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-06-11T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mariana-mazza-2#2026-09-29T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Mariana Mazza"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-29T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-alicia-moffet","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/e340c64f-1b77-4fe6-b3cd-11cf70028410"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-25T20:00:00-04:00"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/alicia-moffet.html#WebPage"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/B6XlKEGXcss?si=TuJYYMxhqqkMFWE1"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/name":[{"@language":"fr","@value":"Alicia Moffet"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/alicia-moffet.jpg"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/alicia-moffet.html"}],"http://schema.org/description":[{"@language":"fr","@value":"Après le succès de son premier album Billie Ave. (2021) et de son EP Intertwine (2022), qui cumulent plus de 17 millions d’écoutes, Alicia Moffet continue de repousser ses limites, témoignant de son cheminement. Son nouvel album No, I’m Not Crying n’est pas seulement un nouveau chapitre pour elle; c’est un tournant audacieux destiné à captiver ses auditeurs et le témoignage de son évolution. Fusionnant pop, R\u0026B et tout ce qui se trouve entre les deux, l’artiste se découvre comme jamais et offre un regard sans filtre au-delà de l’image qu’elle projette sur ses plateformes. Le titre du projet reflète ce qu’Alicia considère comme une qualité déterminante : sa dualité. «D’une certaine manière, avec No, I’m Not Crying, je dévoile comment je dois souvent mettre un masque et rester résiliente face à l’adversité», affirme-t-elle. En collaboration avec sa nouvelle maison de disques, Cult Nation — un collectif international de créateurs de talents qui compte notamment Charlotte Cardin et Lubalin — Alicia a écrit et co-écrit chaque chanson de l’album avec les réalisateurs et auteurs-compositeurs Jason Brando, Sam Avant et Harper Gordon. Chaque morceau raconte une page de son histoire, tantôt au travers d’hymnes puissants, tantôt via des balades émotionnelles profondément inspirantes. No, I’m Not Crying n’est pas seulement un album; il s’agit d’un manifeste audacieux sur la transformation et l’affirmation de soi, sans complexe, qui vient solidifier la place d’Alicia Moffet comme l’une des voix les plus captivantes de la pop actuelle. Après deux ans d’absence, elle signe enfin son retour avec ce premier projet réalisé aux côtés de sa nouvelle équipe, paru le 30 mai 2025. Veuillez noter que l'artiste pourrait inviter les spectateurs à se lever pendant le spectacle. Nous préférons vous en avertir."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-alicia-moffet#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-alicia-moffet#Offer_1"}]},{"@id":"http://kg.footlight.io/resource/e340c64f-1b77-4fe6-b3cd-11cf70028410","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Alicia Moffet"}],"http://schema.org/hasOccupation":[{"@value":"Musicienne, chanteuse"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Musicienne, chanteuse"}],"http://schema.org/sameAs":[{"@value":"http://www.wikidata.org/entity/Q16240179"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/alicia-moffet.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:39:25.429-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/alicia-moffet.html"}]},{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Chanson"}],"http://www.w3.org/2004/02/skos/core#closeMatch":[{"@id":"http://kg.artsdata.ca/resource/Chanson"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-alicia-moffet#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E054D\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"37"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-alicia-moffet#Offer_1","http://schema.org/price":[{"@value":"33"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-yves-roy-desmarais-30-09-2026","http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-490"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-30T20:00:00-04:00"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/name":[{"@language":"fr","@value":"Pierre-Yves Roy-Desmarais"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/pierre-yves-roy-desmarais.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/pierre-yves-roy-desmarais-30-09-2026.html#WebPage"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/pierre-yves-roy-desmarais-30-09-2026.html"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/description":[{"@language":"fr","@value":"Après avoir remporté les Olivier du spectacle de l’année et d’auteur de l’année, ainsi que le Félix du spectacle d’humour de l’année, Pierre-Yves Roy-Desmarais élève son art à un niveau supérieur avec son deuxième spectacle. Un soir, alors qu’il ne s’attend à rien, Pierre-Yves Roy-Desmarais vit un événement qui changera le cours de son existence. Il tente tant bien que mal de garder le contrôle de sa vie, mais le temps devient flou, les souvenirs se mélangent et les unités de mesure ne veulent plus rien dire. Avec tout ce qui lui arrive, il a l’impression de vivre l’équivalent d’une année complète, en seulement un an. Une année en un an est un spectacle alliant stand-up, chansons et fromage, d’une durée de 75 centimètres. «La bête de scène a un univers inimitable.» - Christian Saint-Pierre, Le Devoir «C'est un génie comique ce Pierre-Yves Roy-Desmarais» - Nathalie Petrowski, La journée (est encore jeune) «Son imaginaire débridé et sa créativité sont et resteront toujours sa plus grande force.» - Josée Lapointe, La Presse «Spectacle punché, réfléchi, éclaté.» - Catherine Brisson, 98,5 FM «Il prouve, encore une fois, qu'il offre un univers unique et rafraîchissant.» - Léa Harvey, Le Soleil «Pierre-Yves Roy-Desmarais maîtrise l'art du clownesque comme personne.» - Pascale Robitaille, TVA «[...] Pierre-Yves Roy-Desmarais demeure donc dans une classe à part.» - Cédric Bélanger, Journal de Québec «Rythmé et dynamique: la mise en scène du spectacle est probablement la plus unique et novatrice au Québec. Un show que tout fan d'humour se doit de voir.» - Alexandre Tétreault, FM93"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-yves-roy-desmarais-30-09-2026#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-yves-roy-desmarais-30-09-2026#Offer_1"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-01T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-yves-roy-desmarais-30-09-2026#2026-09-30T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-yves-roy-desmarais-30-09-2026#2026-10-01T200000-0400"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/pierre-yves-roy-desmarais-30-09-2026.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:16:58.645-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/pierre-yves-roy-desmarais-30-09-2026.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-yves-roy-desmarais-30-09-2026#Offer_0","http://schema.org/price":[{"@value":"46"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E017V\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-yves-roy-desmarais-30-09-2026#Offer_1","http://schema.org/price":[{"@value":"42"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-yves-roy-desmarais-30-09-2026#2026-09-30T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Pierre-Yves Roy-Desmarais"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-30T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-yves-roy-desmarais-30-09-2026#2026-10-01T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Pierre-Yves Roy-Desmarais"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-01T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-bruno-pelletier","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/spec-qc-ca_artiste_bruno-pelletier"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/bruno-pelletier-2026b.jpg"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/bruno-pelletier.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Bruno Pelletier"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-11T20:00:00-04:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/kuRG7tIQHN8?si=l8Im1oK1Kd80zenH"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/bruno-pelletier.html#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/description":[{"@language":"fr","@value":"Un spectacle festif, vivant et plein de surprises! Dans ce nouveau rendez-vous musical, Bruno Pelletier revisite avec authenticité et générosité les grandes chansons qui ont marqué sa carrière — Coriace, Ailleurs c’est comme ici, Le temps des cathédrales — ainsi que plusieurs classiques indémodables qui l’ont personnellement marqué et inspiré, de Supertramp à Elvis Presley, en passant par The Beatles et Grease. Accompagné de ses complices de toujours – Martin Bachand (guitare), Claude Pineault (multi-instrumentiste) et Brigitte Marchand (voix et envolées lyriques) – Bruno nous invite à un moment de pure complicité musicale, où tout peut arriver. Bruno nous offre une soirée où la virtuosité rencontre la spontanéité. Chaque concert est unique : les musiciens s’amusent, improvisent, se répondent et partagent avec le public le pur plaisir de jouer ensemble, dans une atmosphère à la fois intime et vibrante. Bruno chante, joue de la basse et de la guitare, et se livre avec simplicité sur son parcours, ses inspirations et les histoires derrière ses succès. Entre confidences, rires et moments d’émotion, il nous entraîne dans un tourbillon musical où rock, pop, opéra et même un brin de country se côtoient naturellement. Plus qu’un concert, c’est une rencontre chaleureuse entre quatre amis musiciens et un public conquis — un grand jam collectif où la musique rassemble, où l’on se reconnaît, et d’où chacun repart le sourire aux lèvres et la tête pleine de refrains qu’il connaît par coeur."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-bruno-pelletier#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-bruno-pelletier#Offer_1"}]},{"@id":"http://kg.footlight.io/resource/spec-qc-ca_artiste_bruno-pelletier","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@value":"Bruno Pelletier"}],"http://schema.org/description":[{"@language":"fr","@value":"Tout au long de ses trente ans de carrière, sa passion pour la scène et son désir constant d'explorer différentes avenues artistiques ont permis à Bruno Pelletier de concilier une prolifique carrière solo ainsi que des participations remarquables à plusieurs comédies musicales d'envergure. Avec la vente de plus de 2 millions d'unités dans la francophonie, Bruno, triple récipiendaire du prix Félix Interprète masculin de l'année, compte, sans conteste, parmi les artistes les plus marquants de sa génération. Parcours Bruno Pelletier est né le 7 août 1962 à Charlesbourg en banlieue de Québec. Tout au long des années 80, entre sport et musique, son cœur balance. Ceinture noire de karaté en main, il prend d'abord l'initiative d'ouvrir sa propre école d'arts martiaux, mais parallèlement s'adonne à son deuxième centre d'intérêt, la musique. Fort aise à la batterie, mais aussi pourvu d'une très belle voix, c'est à titre de chanteur dans les formations rock Amanite et Sneak Preview que pendant plusieurs années il arpente le circuit des bars et apprend les rudiments du métier d'artiste. La musique prenant peu à peu une place de choix dans sa vie, Bruno décide finalement d'en faire sa priorité et fonde en 1989 le groupe rock francophone Pëll. Avec ses musiciens et quelques compositions en main, il participe à des concours et tente de se faire remarquer par le milieu musical professionnel. En 1990, à la faveur de rencontres heureuses et d'efforts soutenus, la société Les Disques Artiste, présidée par Paul Lévesque, lui offre enfin un premier contrat de disque. La carrière de Bruno Pelletier était lancée! En 1992, parallèlement à la mise en marché de son album éponyme, il se voit successivement offrir des rôles dans la revue musicale; Les fous du rock'n'roll et dans la version québécoise de l'opéra rock signé Plamondon-Berger; La légende de Jimmy. Bien que le succès de ce dernier spectacle soit plutôt modeste, la présence scénique et les qualités vocales de l'artiste impressionnent. En 1993, Luc Plamondon conquis par les talents du chanteur fait de nouveau appel à lui. Cette fois, il lui offre le rôle de Johnny Rockfort dans une nouvelle mouture parisienne de l'opéra rock Starmania. Il incarne le personnage à plus de quatre cents reprises sur une période de deux ans et profite de ses moments libres pour peaufiner son deuxième album en carrière. L'album Défaire l'amour sera lancé au cours de l'année 1995, mais il devra attendre jusqu'en 1997 pour enfin atteindre la consécration. Cette année-là, il joue avec brio le rôle d'un truand dans la très populaire télésérie Omerta II et lance l'album Miserere. Ce troisième opus qui se hissera rapidement au sommet des palmarès se vendra à plus de 250 000 copies et lui vaudra un véritable impact populaire. L'année suivante, au Gala de l'ADISQ, Bruno se verra couronné pour une deuxième année consécutive du prix Félix Artiste masculin de l'année et l'album Miserere des prix Félix: Album de l'année catégorie Pop rock; Album de l'année catégorie Meilleur vendeur et Spectacle de l'année catégorie Interprète. 1998 et 1999 seront les années Notre-Dame de Paris. Le spectacle dans lequel Bruno incarne le poète Gringoire est un véritable triomphe. Le titre Le temps des cathédrales, qu'il interprète brillamment, lui permet de se hisser au sommet des palmarès et de se faire connaître dans toute la francophonie. En 1999, le quatrième album studio D'autres rives est simultanément lancé en Europe et au Québec. Cette sortie sera appuyée par une imposante tournée qui lui permettra de se produire pour la toute première fois en tant qu'artiste solo sur plusieurs scènes prestigieuses d'Europe dont l'Olympia de Paris, La Cigale et Le Bataclan. Bien que très occupé avec sa carrière solo, il fera deux parenthèses, le temps de renfiler les habits de Gringoire dans les versions québécoise et londonienne de Notre-Dame de Paris. En 2001, après avoir roulé sa bosse de façon effrénée pendant plusieurs années, Bruno ressent le besoin de souffler un peu. Mais juste avant de s'accorder une pause bien méritée, il lance Sur scène, album live tiré de la tournée D'autres Rives. S'en suit une période de ressourcement où Bruno fera très peu de sorties publiques. Il profitera de ce moment pour peaufiner un nouvel album et s'impliquer d’avantage dans la Fondation Canadienne Rêves d'Enfants, qu'il parraine depuis de nombreuses années. En août 2002 il lance Un monde à l'envers et entame une tournée de deux ans. En décembre de la même année, il unit sa voix à l'Orchestre Symphonique de Montréal pour présenter une série de concerts à la Basilique Notre-Dame. Ayant pour thématique les grands classiques de Noël, il profitera de l'occasion pour enregistrer l'album Concert de Noël qui connaîtra un immense succès populaire. En 2004, on lui propose le rôle-titre du spectacle musical Dracula - Entre l'amour et la mort (paroles Roger Tabra, musique Simon Leclerc). Toujours à l’affût de nouveaux défis, cette nouvelle aventure lui permet d'ajouter quelques cordes à son arc, puisqu'il s'implique également à titre de directeur artistique et coproducteur. Le spectacle, présenté pour la toute première fois à Montréal le 31 janvier 2006, est un franc succès (vu par plus de 115,000 spectateurs) et l'impose comme une figure incontournable de ce genre au Québec. Présenté à la prestigieuse Maison de la Danse de Lyon (1200 places) en janvier de l'année suivante, Dracula connaîtra, là-bas aussi, un grand succès public et critique. A l’automne 2007, Bruno Pelletier fait une parenthèse et forme le GrosZorchestre en collaboration avec la pianiste de Jazz Julie Lamontagne et de son trio. Ce moment d'expérimentation musical lui permettra, pendant les deux années subséquentes, d'explorer et de peaufiner son art vocal et d'ouvrir encore plus ses horizons. Cette collaboration donnera lieu à un album qui leur vaudra d'ailleurs le prix du Félix de l'année pour \"Album de l'année-Jazz interprétation\". En 2009, il nous revenait avec Microphonium, première réalisation de matériel original en six ans. Cette même année aura aussi été marquée par le premier passage triomphal de l'artiste au mythique théâtre Estrade de Moscou. Depuis le chanteur traverse régulièrement l’Atlantique question d’aller se produire sur le territoire de ses nombreux admirateurs de l'Europe de l’Est. Au printemps 2011, le Théâtre St-Denis est l’hôte de l’Opéra-Folk Les Filles de Caleb dont l’ensemble des paroles et de la musique est signé par l’auteur-compositeur Michel Rivard. Outre Bruno, ce spectacle d’envergure, basé sur l’œuvre d’Arlette Cousture, met en vedette plusieurs noms connus tels Luce Dufault, Daniel Boucher, Yves Soutière, Marie-Michèle Desrosiers et Stéphanie Lapointe. Dans cette fresque historique, Bruno endosse le rôle de Napoléon, amoureux déçu, qui se réfugie dans le service de Dieu pour oublier l’amour de sa vie, Blanche Pronovost. En septembre 2012, beaucoup de nouvelles activités sont à l’agenda. Bruno Pelletier devient le nouveau porte-parole de la Fondation québécoise du cancer et coach vocal à la nouvelle émission hebdomadaire « Un air de famille » diffusée sur les ondes de Radio-Canada. Dynamisé par son onzième album « Rendus là » paru le 25 septembre, opus bonifié d’un DVD Bruno Pelletier… Sur scène à Moscou et avec un tout nouveau spectacle qui lui permet de fouler de nombreuses scènes dès octobre 2012. Pour la première fois dans sa carrière, Bruno fut invité à présenter son spectacle de Noël en Europe de l’Est, accompagné par 3 orchestres symphoniques différents. Les concerts à Kiev, St-Petersbourg et Moscou eurent un succès retentissant. Prenant une pause de sa tournée au printemps 2013, Bruno est retourné une fois de plus en Europe de l’Est, cette fois-ci pour accompagner la troupe originale de Notre-Dame de Paris lors de quatre concerts symphoniques, Les plus grandes chansons de Notre-Dame de Paris, incluant 70 musiciens, 40 choristes et dirigés par Guy Saint-Onge, avant de terminer sa tournée Rendus là au Québec. 2014 a été l'occasion pour Bruno Pelletier de terminer sa tournée \"Rendus-là\" avec la portion européenne de la tournée. L'Ukraine, la Russie, la Pologne mais aussi Paris seront les derniers points de chute de cette tournée amorcée dix-huit mois plus tôt. En 2015 s’est ajouté le projet Bruno Pelletier \u0026 Guy Saint-Onge – Musique et cinéma, une rencontre dont deux albums, un en version symphonique et l’autre en version piano-voix ont été lancés en position numéro 1 des ventes au Palmarès Soundscan. Le tout, suivi d’une impressionnante tournée québécoise de plus de soixante représentations. Suite au succès remporté par le titre ‘’Regarde autour’’ enregistré par Bruno Pelletier pour promouvoir la Semaine de la prévention du suicide en février 2016 et la vidéo interprétée par les comédiens de l’émission Yamaska, l’idée d’un album pop et lumineux, portant le même titre, a germée ayant pour résultat une sortie en magasin le 16 septembre. Une tournée provinciale ‘’Regarde autour’’ pour appuyer l’album de douze nouveaux titres sillonnera les routes du Québec dès la mi-janvier. Bruno Pelletier une valeur sûre, au sommet de sa forme, a toujours autant de plaisir à exercer le plus beau métier du monde !"}],"http://schema.org/sameAs":[{"@value":"http://kg.artsdata.ca/resource/K13-29"}],"http://schema.org/image":[{"@value":"https://spec.qc.ca/uploads/artistes/bruno-pelletier-1.jpg"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/bruno-pelletier.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:20:40.711-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/bruno-pelletier.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-bruno-pelletier#Offer_0","http://schema.org/price":[{"@value":"52"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E054H\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-bruno-pelletier#Offer_1","http://schema.org/price":[{"@value":"48"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-ladies-night","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/varietes/ladies-night.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Ladies Night"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-15T20:00:00-05:00"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/varietes/ladies-night.html#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"varietes"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/varietes/ladies-night-2026b.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"5 chômeurs désespérés. 1 plan fou: se déshabiller! Le phénomène qui fait rire (et rougir) le Québec depuis 25 ans est de retour avec sa distribution originale ! Michel Charette, François Chénier et Marcel Lebœuf reprennent leurs rôles culte, accompagnés des nouvelles recrues Pierre-Paul Alain, Lyndz Dantiste et Frédérike Bédard. L'histoire? Cinq chums au chômage depuis 6 mois, fauchés et prêts à tout pour s'en sortir. Leur plan B? Se déshabiller pour se remplir les poches! Parce que quand on n'a plus rien à perdre, aussi bien tout enlever! Avec l'aide (et la patience) de Glenda, une ex-danseuse, ces amateurs tenteront l'impossible: créer un vrai spectacle de strip-tease… ou presque! Entre les fous rires, les maladresses et les strings mal ajustés, ils foncent vers la présentation ultime! Un spectacle ridiculement sexy... et sérieusement divertissant!"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-ladies-night#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-ladies-night#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/varietes/ladies-night.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:38:42.233-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/varietes/ladies-night.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-ladies-night#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"64"}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E054J\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-ladies-night#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"60"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-le-petit-roi-pour-toujours","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Le Petit Roi pour toujours"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/le-petit-roi-pour-toujours.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2027-01-10T20:00:00-05:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/le-petit-roi-pour-toujours.html#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/le-petit-roi-pour-toujours.html"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/zo0ls-PC1hk?si=ClqRvjPAM1hSmfx4"}],"http://schema.org/description":[{"@language":"fr","@value":"Jean-Pierre Ferland, icône de la chanson québécoise, restera à jamais Le Petit Roi pour tous ceux et celles qui ont eu la chance d’entendre, de ressentir et de vivre sa musique. Auteur-compositeur-interprète parmi les plus marquants et les plus importants du patrimoine musical et culturel du Québec, il laisse une œuvre riche et intemporelle. Le spectacle Le Petit Roi pour toujours rend un hommage sincère et rassembleur à ce pilier de la chanson québécoise, célébrant son héritage unique et s’adressant à tous les amoureux de sa musique et de sa poésie. Ainsi, une quarantaine de succès, témoins de plus de soixante ans de création seront interprétés par une distribution remarquable composée de certaines des plus belles voix du Québec avec Johanne Blouin, Kevin Parent, Marc-André Fortin, Éric Paulhus, ainsi que Julie Anne Saumur. Accompagné de Lynn Jodoin, choriste et complice de longue date de Jean-Pierre, et par cinq musiciens sur scène, les voix, les archives et les projections d’images sur écran, plongeront le public dans un univers empreint de poésie, d’humour et de tendresse – celui d’un artiste inoubliable et de son immense répertoire. Producteur GSI Musique Artistes Johanne Blouin – Kevin Parent – Marc-André Fortin – Éric Paulhus – Julie Anne Saumur Choriste Lynn Jodoin Direction musicale Alain Leblanc Mise en scène Pierre Séguin"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-le-petit-roi-pour-toujours#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-le-petit-roi-pour-toujours#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/le-petit-roi-pour-toujours.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:38:59.074-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/le-petit-roi-pour-toujours.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-le-petit-roi-pour-toujours#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"84"}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E054L\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-le-petit-roi-pour-toujours#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"80"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mario-tessier-2","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/mario-tessier-2.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Mario Tessier"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-249"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/mario-tessier.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"L’humoriste Mario Tessier présente son 3e one-man-show intitulé Champion, alors qu’il est de retour avec l’énergie explosive qu’on lui connait. Champion, c’est un concentré d’humour irrésistible qui mélange autodérision et situations improbables. Dans ce spectacle, Mario affronte ses propres contradictions avec une dose de folie : suivre un entraînement de boxe professionnel juste après une séance de botox, devenir propriétaire d’une jument alors qu'il n’est jamais monté à cheval ou encore se prendre pour un génie alors que… bon, vous voyez le tableau! Avec Champion, Mario met en lumière ce «grand champion» qui sommeille en chacun de nous, celui qui nous pousse à viser plus haut… ou à nous ridiculiser de la plus drôle des façons. Si vous l’aimez à la radio sur Énergie, attendez de le voir sur scène! Il vous fera encore plus rire grâce à son énergie contagieuse, son efficacité comique légendaire et cette tête de vainqueur qu’on ne peut qu’aimer! «Un \"champion\" pour mettre son humour au goût du jour tout en restant le bon vieux chum des 30 dernières années!» - Boom FM «Hilarant du début à la fin, le spectacle d'humour à voir cette année!» - ÉNERGIE «Des punchlines redoutables, des échanges en choeur avec le public, des imitations éclatées... c'est du bonbon!» - pulsations.net «[le] numéro sur la colonoscopie, c'est un vrai morceau d'anthologie de l'humour québécois! J'ai adoré!» - QUB radio «Authentique et captivant... assurément un des spectacles incontournables de l'année!» - Noovo Moi «Champion est un excellent exemple de spectacle d'un vieux routier de l'humour qui a su demeurer alerte et adapter son propos au goût du jour.» - Hollywood PQ «Mario Tessier met à profit ses plus puissants outils en matière de comique physique et de burlesque.» - La Presse «Maniant bien les imitations, l'autodérision et toujours baveux à souhait, Tessier sur scène montre qu'il est aussi à L'aise avec l'humour physique.» - Le Soleil «Un spectacle intime et rythmé... atteint brillamment sa cible» - Journal de Montréal"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2027-01-13T20:00:00-05:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/mario-tessier-2.html#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/X238mJSXSvc?si=908Aj8p7Jqd6sePU"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mario-tessier-2#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mario-tessier-2#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/mario-tessier-2.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:39:08.235-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/mario-tessier-2.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mario-tessier-2#Offer_0","http://schema.org/price":[{"@value":"47"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E054N\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mario-tessier-2#Offer_1","http://schema.org/price":[{"@value":"43"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-charles-brunet","http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/d352e5b3-a9b6-4d36-bf0e-0af5614b3e13"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/charles-brunet.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-13T20:00:00-04:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/charles-brunet.html#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/charles-brunet.html"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/97xqH13GMyA?si=wL9ByO6AfTDiB4G7"}],"http://schema.org/description":[{"@language":"fr","@value":"J’ai 24 ans. J’suis extra vieux. Vraiment full mature pis profond. J’comprends un peu rien à la vie mais j’commence à comprendre mon métier. Le nom le dit, c’est un très très bon show. Ça parle d’un peu de tout. Ça parle à tout le monde. Vieux, jeune. Tiktok, Msn. Gauche, Droite. Bonjour, hi. C’est surtout fucking drôle. C’est l'œuvre dont je suis le plus fier de ma jeune carrière. Merci aux gens qui me permettent de continuer à vivre mon rêve de p'tit cul. Je vous le devais et le voici, un très très bon show. En nomination au Gala Les Olivier 2026: Capsule ou sketch Web humoristique de l’année * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7"}],"http://schema.org/name":[{"@language":"fr","@value":"Charles Brunet"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-charles-brunet#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-charles-brunet#Offer_1"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-31T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-charles-brunet#2026-03-13T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-charles-brunet#2026-10-31T200000-0400"}]},{"@id":"http://kg.footlight.io/resource/d352e5b3-a9b6-4d36-bf0e-0af5614b3e13","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Charles Brunet"}],"http://schema.org/hasOccupation":[{"@value":"Humoriste"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/charles-brunet.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:35:32.778-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/charles-brunet.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-charles-brunet#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"40"}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E01PT\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-charles-brunet#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"36"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-charles-brunet#2026-03-13T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Charles Brunet"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-13T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-charles-brunet#2026-10-31T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Charles Brunet"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-31T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-k-maro","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/k-maro.html"}],"http://schema.org/name":[{"@language":"fr","@value":"K.Maro"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-12T20:00:00-04:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/description":[{"@language":"fr","@value":"Avec plus de 5 millions d’albums vendus dans le monde et des titres intemporels comme Femme Like U, Let's Go, Crazy et Histoires de Luv, K.Maro s’impose comme l’une des figures les plus marquantes de la scène pop urbaine francophone. Artiste multi-platine, auteur, compositeur et producteur, il a contribué à définir le son d’une génération en mêlant R\u0026B, hip-hop et électro-pop, bien avant que les frontières entre les genres ne s’effacent. En parallèle de sa carrière d’interprète, Cyril Kamar a bâti un parcours impressionnant d’auteur-compositeur et de producteur pour de nombreux artistes, tout en développant un univers créatif riche à la croisée de la musique, de l’art contemporain et de l’entrepreneuriat. Ses œuvres et collaborations lui ont valu plusieurs distinctions et une reconnaissance internationale, de l’Europe au Canada en passant par le Moyen-Orient. Après plusieurs années consacrées à l’écriture et à la production pour d’autres, K.Maro revient avec un nouveau chapitre artistique: Place de Loréane — un projet où la chanson française rencontre les textures électroniques modernes. Inspiré par des références telles que M83, Kavinsky, The Weeknd ou The 1975, ce nouvel univers explore le point de rencontre entre la nostalgie et la modernité, mêlant synthés analogiques, narration sensible et sonorités cinématiques. La prochaine tournée Place de Loréane marquera la première fois que K.Maro revisitera ses plus grands classiques aux côtés de ses nouvelles créations. Sur scène, ces morceaux seront réinterprétés à travers des synthétiseurs analogues, des beat machines et une scénographie intimiste, offrant au public une expérience immersive, vibrante et sincère — à la croisée du groove, de l’émotion et de l’énergie. Un retour au rythme. Une célébration de la mélodie. Place de Loréane — là où le passé et le futur se rencontrent. Veuillez noter que l'artiste pourrait inviter les spectateurs à se lever pendant le spectacle. Nous préférons vous en avertir. Première partie : Jalouse Jalouse est le projet solo du musicien montréalais Olivier Pépin, actif depuis plus de vingt ans comme guitariste et directeur musical, notamment auprès de Pierre Kwenders. Entièrement autonome à ses débuts, le projet déploie une pop nocturne et posément expérimentale, nourrie de synthwave et de goth-pop des années 80, où se croisent introspection, mélodies accrocheuses et plaisir coupable. Après l’EP Compostelle (2022), Jalouse fait paraître son premier album Nature Morte en 2023 sous Les Disques Dur Vie."}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/k-maro.jpg"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/-TGl_150wYA?si=tX07Te0itx-LsCVQ"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/k-maro.html#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-k-maro#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-k-maro#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/k-maro.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:14:08.123-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/k-maro.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-k-maro#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"44"}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E0526\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-k-maro#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"40"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-guillaume-pineault","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/spec-qc-ca_artiste_guillaume-pineault"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Guillaume Pineault"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-08T20:00:00-05:00"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/zstb5cs_pu4?si=FbgShw1v3Fy8ZS5m"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/guillaume-pineault.html"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/guillaume-pineault.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/guillaume-pineault.html#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/description":[{"@language":"fr","@value":"Malgré l’air calme et confiant que je peux dégager sur mon affiche, derrière cette façade, se cachent plusieurs mécanismes de défense, beaucoup d’anxiété et 65 % d’eau (oui, c’est vraiment le pourcentage d’eau dans le corps adulte; je le sais, je l’ai appris à l’université, et j’adore rappeler aux gens que j’ai des connaissances biologiques grâce à mon passé académique). Dans ce spectacle, il n’y a plus de filtre, aucune carapace : vous aurez accès à un Guillaume plus intime, authentique et vulnérable. Bien entendu, mon désir premier est de vous faire rire, mais également de vous faire découvrir l’humain derrière l’humoriste. Au plaisir de vous voir et de vous présenter : VULNÉRABLE Crédit photo de l’affiche : Félix Renaud Lauréat au Gala Les Olivier 2025: Texte de l'année / spectacle d'humour «Guillaume Pineault se découvre et se dévoile, déployant sur scène toutes ses habiletés de conteur.» - La Presse «Un spectacle beaucoup plus abouti et assumé que le premier, mais aussi plus intime et franchement drôle.» - Journal de Montréal «Guillaume est en plein contrôle de ses moyens [...] il nous fait vivre une soirée drôle et humaine!» - CKOI"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-guillaume-pineault#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-guillaume-pineault#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/guillaume-pineault.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-07T15:27:13.872-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/guillaume-pineault.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-guillaume-pineault#Offer_0","http://schema.org/price":[{"@value":"38"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E0182\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-guillaume-pineault#Offer_1","http://schema.org/price":[{"@value":"34"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-alain-choquette","@type":["http://schema.org/Event"],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-22T19:00:00-05:00"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/varietes/alain-choquette-2026.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/varietes/alain-choquette.html#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/wlqRGv8XOK4?si=edPWPDFE9azoHYr4"}],"http://schema.org/keywords":[{"@value":"varietes"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-259"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Alain Choquette"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/varietes/alain-choquette.html"}],"http://schema.org/description":[{"@language":"fr","@value":"Maître de l’insaisissable et raconteur hors pair, Alain Choquette revient sur scène avec une nouvelle proposition aussi divertissante qu’inspirante, Entre histoires et illusions. Dans ce spectacle, l’illusionniste québécois le plus charismatique et expérimenté offre à un public de tous âges de plonger avec lui dans son grand livre de la magie. Cette fois-ci, ce sont ses récits captivants, allant de l’histoire de la magie (Robert-Houdin, Houdini, Copperfield, Magic Tom…) aux surprenantes expériences vécues au cours de ses 35 années de carrière, qui amèneront le célèbre juge de La magie des stars (TVA) à vous éblouir tant par ses performances insaisissables que par ses connaissances historiques et sa capacité à raconter. Plus de 2 millions de billets vendus en carrière, premier Québécois à présenter un spectacle en résidence à Las Vegas, plusieurs tournées à succès en Europe, création et distribution de grands numéros magiques à l’international : Alain Choquette fait partie des grands noms de la magie à travers le monde et ne manque pas de récits et de numéros pour surprendre, faire rire et éblouir. Ayant une place de choix dans le coeur des Québécois depuis des décennies, Alain veut redonner au public en lui proposant une expérience intime, unique et interactive, qui créera des souvenirs indélébiles. Entre histoires et illusions, un spectacle dans lequel la magie prend tout son sens!"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-alain-choquette#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-alain-choquette#Offer_1"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-alain-choquette#Offer_2"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/varietes/alain-choquette.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:40:37.352-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/varietes/alain-choquette.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-alain-choquette#Offer_0","http://schema.org/price":[{"@value":"48"}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04WX\u0026CodeSiteClient=00000V"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-alain-choquette#Offer_1","http://schema.org/price":[{"@value":"44"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-alain-choquette#Offer_2","http://schema.org/price":[{"@value":"34"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"étudiants"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-martin-petit","@type":["http://schema.org/Event"],"http://schema.org/description":[{"@language":"fr","@value":"Un monde meilleur devient réalité avec le 5ème one-man-show de Martin Petit! Les politiciens nous le promettent à tous les 4 ans, les religions à la fin de nos jours mais Martin Petit a décidé que c’était maintenant. Une invitation à regarder le monde avec enthousiasme et entrain, et rendre ce monde meilleur encore meilleur!"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-246"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Martin Petit"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-22T20:00:00-04:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/martin-petit.html#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/martin-petit.jpg"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/martin-petit.html"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-martin-petit#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-martin-petit#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/martin-petit.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:13:30.729-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/martin-petit.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-martin-petit#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E0513\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"45"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-martin-petit#Offer_1","http://schema.org/price":[{"@value":"41"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-les-trois-accords","http://schema.org/description":[{"@language":"fr","@value":"De retour de l’un de leurs nombreux voyages initiatiques, LES TROIS ACCORDS prennent une pause de leurs aventures le temps d’une nouvelle série de spectacles à travers le Québec. Désormais marqués d’une sagesse et de coups de soleil profonds, ces grands explorateurs nous racontent leurs récits de voyage, des faits vécus hautement rythmés, des expéditions musicales qui nous mènent tantôt sur des sentiers inconnus, tantôt vers des rencontres étranges… On attend impatiemment la sortie du nouvel album le 24 octobre prochain! Veuillez noter que les artistes pourraient inviter les spectateurs à se lever pendant le spectacle. Nous préférons vous en avertir."}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/les-trois-accords.html"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/fe196384-a517-4d97-9198-db25234f8f7b"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/les-trois-accords-2026.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-23T20:00:00-04:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/les-trois-accords.html#WebPage"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/8anTDznRlt8?si=lTHyCGV088sMCf1J"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/name":[{"@language":"fr","@value":"Les Trois Accords"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-les-trois-accords#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-les-trois-accords#Offer_1"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-les-trois-accords#Offer_2"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-12-17T20:00:00-05:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-les-trois-accords#2026-05-23T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-les-trois-accords#2026-12-17T200000-0500"}]},{"@id":"http://kg.footlight.io/resource/fe196384-a517-4d97-9198-db25234f8f7b","@type":["http://schema.org/Organization"],"http://schema.org/name":[{"@language":"fr","@value":"Les Trois Accords"}],"http://schema.org/url":[{"@value":"http://www.lestroisaccords.com/"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Organization"}],"http://schema.org/sameAs":[{"@value":"https://www.facebook.com/lestroisaccords/"},{"@value":"http://www.wikidata.org/entity/Q3086695"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/les-trois-accords.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:24:31.434-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/les-trois-accords.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-les-trois-accords#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E0180\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/price":[{"@value":"52"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-les-trois-accords#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"48"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-les-trois-accords#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"par billet sera remis à la Fondation Cowboys Fringants"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"1"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-les-trois-accords#2026-05-23T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Les Trois Accords"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-23T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-les-trois-accords#2026-12-17T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Les Trois Accords"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-12-17T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-coeur-de-pirate","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/coeur-de-pirate.html"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-1002"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Coeur de pirate"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-30T20:00:00-04:00"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/coeur-de-pirate.jpg"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/rQtHk1X722s?si=uAk51PLkgzGvgOYT"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/coeur-de-pirate.html#WebPage"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/description":[{"@language":"fr","@value":"Incontournable de la chanson francophone, Cœur de pirate est capable d’introspection et d’exaltation aussi bien que de subversion; avec sa voix intime et affilée, elle séduit comme elle surprend. Icône pop internationale partageant sa vie et sa carrière entre le Québec et l'Europe, elle s’est produite à travers le monde depuis ses fulgurants débuts en 2008 – et, en 2025, elle revient sur scène pour présenter l’album Cavale. Icône pop internationale partageant sa vie et sa carrière entre le Québec et l'Europe, elle s’est produite à travers le monde depuis ses fulgurants débuts en 2008 – et, en 2025, elle revient sur scène pour présenter l’album Cavale. La voyant passer de la douceur aux envolées, ce nouveau spectacle à la scénographie grandiose présente autant la femme au piano que la star qui danse. On y retrouve sa distinctive manière – à la fois mélancolique et vibrante – de livrer l’émotion tout en invitant la foule à célébrer. Une soirée avec Cœur de pirate, c’est la volatilité des sentiments présentée avec un souci d’orfèvre, entre recueillement et exaltation. Première partie : Billie du Page Billie du Page, artiste canadienne et française de 20 ans, incarne une pop libre et décomplexée où s’entremêlent féminisme, sincérité et mélodies accrocheuses. Révélée par Fake Friends, numéro 1 au top radio québécois et top 10 canadien, elle enchaîne les succès avec ses EP Billie du Page (2024) et Not Your Princess (2025). Capable de nous émouvoir avec ses ballades comme de nous faire danser sur ses refrains effrontés, elle transforme ses peines en hymnes pop lumineux. Sur scène, que ce soit en première partie de Cœur de pirate, Aliocha Schneider ou Grand Corps Malade, Billie séduit par son authenticité, sa présence et sa générosité."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-coeur-de-pirate#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-coeur-de-pirate#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/coeur-de-pirate.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:22:13.543-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/coeur-de-pirate.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-coeur-de-pirate#Offer_0","http://schema.org/price":[{"@value":"49"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04P0\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-coeur-de-pirate#Offer_1","http://schema.org/price":[{"@value":"45"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-2freres-derniere-chance","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K10-435"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-02T20:00:00-04:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/2freres-derniere-chance.html"}],"http://schema.org/name":[{"@language":"fr","@value":"2Frères"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/2freres-derniere-chance.html#WebPage"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/hebqUgYTBHo?si=aavjFkcxJxXqtcru"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/2freres.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"Le duo le plus aimé du Québec est de retour sur scène accompagné de leurs quatre musiciens afin de présenter un tout nouveau spectacle! Regroupant des pièces de leur nouvel album Science humaine, on y retrouvera également leurs plus grands succès. Avec des musiques accrocheuses et des textes rassembleurs, laissez-vous charmer par leur complicité et leurs magnifiques harmonies vocales. Les 2Frères, c’est aussi plus de 275 000 albums vendus en carrière, 70 millions d’écoutes sur les plateformes en ligne et 14 chansons #1 à la radio!"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-2freres-derniere-chance#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-2freres-derniere-chance#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/2freres-derniere-chance.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:30:38.233-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/2freres-derniere-chance.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-2freres-derniere-chance#Offer_0","http://schema.org/price":[{"@value":"49"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04Y8\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-2freres-derniere-chance#Offer_1","http://schema.org/price":[{"@value":"45"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_country-folk-daraiche-et-filles","@type":["http://schema.org/Event"],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-03T20:00:00-05:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/country-folk/daraiche-et-filles.html"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Daraîche et filles"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/country-folk/daraiche-et-filles.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/country-folk/daraiche-et-filles.html#WebPage"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/ZQUn6zPt96w?si=tRfLUN_2lCa-apu6"}],"http://schema.org/keywords":[{"@value":"country-folk"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/description":[{"@language":"fr","@value":"Daraîche : À lui seul, le nom est déjà très évocateur. Dans le grand dictionnaire de la musique, il pourrait se définir ainsi: famille de la Gaspésie ayant marqué la musique country par son authenticité, sa persévérance et le respect de son public. Imaginez maintenant quand le patriarche, Paul, s’entoure sur scène des «filles» Daraîche : ses enfants Émilie et Katia ainsi que sa nièce Dani, fille de la pionnière du country au Québec, Julie Daraîche. Ensemble, ils représentent ce que le Québec offre de plus authentique à l’héritage country d’ici et d’ailleurs. Le répertoire de la famille Daraîche sur scène s’inspire des grands classiques de la musique country, comme on les a connus et comme on veut les réentendre. Comptez sur Daraîche \u0026 Filles pour poursuivre la tradition. «Oscillant entre balades et chansons rythmées, comme Le Lumberjack ou encore Kaw-Liga, le programme, bien dosé, a plu au public.» - Frédérique de Simone, Le Journal de Montréal «Ce fut un véritable plaisir de vivre cette belle communion entre la famille Daraîche et le public. J'ai été particulièrement émue par la présence de Katia Daraîche sur scène. Et bien sûr, je ne pas passer sous silence l'extraordinaire et émouvante interprétation de Paul Daraîche chantant le thème En attendant que tu reviennes m'a ému jusqu'aux larmes.» - Blanca Victoria Solorzano, CKUT 90.3 FM"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_country-folk-daraiche-et-filles#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_country-folk-daraiche-et-filles#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/country-folk/daraiche-et-filles.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:36:03.530-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/country-folk/daraiche-et-filles.html"}]},{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Musique"}],"http://www.w3.org/2004/02/skos/core#closeMatch":[{"@id":"http://kg.artsdata.ca/resource/MusicPerformance"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_country-folk-daraiche-et-filles#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04WP\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/price":[{"@value":"54"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_country-folk-daraiche-et-filles#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"50"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_country-folk-clement-jacques","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/country-folk/clement-jacques.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-03T20:00:00-04:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/country-folk/clement-jacques.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Clement Jacques"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/country-folk/clement-jacques.html#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"country-folk"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/877X-D7IU24?si=QgIXepqL4FvCsQ1u"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/description":[{"@language":"fr","@value":"Clement Jacques présente le spectacle IRIS qui fait suite à la sortie de son 5e album en carrière. Un album qualifié d’exceptionnel de par la qualité de ses textes, de sa musique, de ses interprétations et de sa réalisation si bien menée par David Laflèche. À l’image de ce qui est véhiculé sur l’album, Clement partage sur scène autant de réflexions, révélant ainsi avec sensibilité sa vision des relations humaines et du monde qui l’entoure. À la fois simple et sincère, ce spectacle mis en scène par Daniel Fortinest résolument ancré dans son époque et présente un amalgame harmonieux de nouveau et d’anciens répertoires de l’artiste. Veuillez noter que l'artiste pourrait inviter les spectateurs à se lever pendant le spectacle. Nous préférons vous en avertir."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_country-folk-clement-jacques#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_country-folk-clement-jacques#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/country-folk/clement-jacques.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:38:18.358-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/country-folk/clement-jacques.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_country-folk-clement-jacques#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E052A\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"36"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_country-folk-clement-jacques#Offer_1","http://schema.org/price":[{"@value":"32"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-alex-roy","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Alex Roy"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/09fd17c7-a410-4054-813d-f9652697ef9d"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/alex-roy.html"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/alex-roy-2025.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"Après plus de 100 représentations et près de 25 000 billets vendus pour son premier one-man-show Des choses qui arrivent, Alex Roy, qui vous a fait hurler de rire durant les premières parties de Lise Dion, Les Grandes Crues et José Gaudet, nous replonge, dans son univers maladroit, surprenant et hilarant avec son tout nouveau spectacle Debout! Debout parce que oui, c’est du stand-up, mais pas que! Dans Debout, je vous raconte tout ce qui me pousse à sortir du lit chaque matin : mon métier, mes jumelles (qui n’ont aucune pitié pour la grasse matinée), et un quotidien rempli d’anecdotes aussi farfelues qu’inattendues. Un spectacle drôle, sincère et énergique… parce que rester debout, c’est déjà une victoire! Debout, c’est plus qu’un simple spectacle de stand-up, c’est une plongée dans tout ce qui me donne envie de me lever le matin. Entre rêves, galères, absurdités du quotidien et grandes questions existentielles, je vous embarque dans un voyage drôle et sincère à travers mes motivations (parfois étranges) pour affronter chaque journée. Alors, qu’est-ce qui me tient debout? Venez le découvrir… et peut-être que vous repartirez avec des raisons en plus de vous lever demain! Alex Roy est un humoriste qui manie l’art du stand-up avec une énergie débordante et un sens aigu de l’autodérision. Avec son franc-parler et sa capacité à transformer les situations du quotidien en histoires hilarantes, il vous embarque dans un univers où chaque détail devient matière à rire. Que ce soit pour parler de sa famille, de ses expériences de vie ou de ses réflexions improbables, Alex Roy sait comment captiver son public en mêlant sincérité, absurdité et une bonne dose d’impertinence. Préparez-vous à rire… et à vous reconnaître (parfois malgré vous) dans ses anecdotes décapantes! * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7 «Le show le plus drôle que j'ai vu en 2025!» - FM93 «Il manie l'art du stand up, il est énergique, drôle, nous partage des parcelles de vie qui nous ressemble et excelle en beat box.» - Arts \u0026 Culture «Cette soirée est un mélange unique: des images absurdes, des aveux qui coulent comme des confidences, des maladresses qui deviennent anecdotes. Une façon différente de dire que, derrière chaque rire, il y a toujours un peu de vérité qui cherche son chemin.» - Sors-tu.ca «[...] l'humour de Roy touche la cible à fréquence d'un tireur d'élite, les rires se faisant entendre tout au long de la soirée.» - Destinationvilledequebec.com «Drôle, authentique et sympathique avec ses blagues parfois coquines, l'humoriste Alex Roy a charmé le public. Une excellente découverte pour ma part.» - Choc FM «Debout m'a rappelé que la plus belle victoire, parfois, c'est simplement de continuer à aimer ce qu'on fait et ceux qui nous entourent.» - Le Bêta-Testeur"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-10T20:00:00-04:00"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/alex-roy.html#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/Cn5HTasGijc?si=m3MwUa8f_2KKyPyD"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-alex-roy#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-alex-roy#Offer_1"}]},{"@id":"http://kg.footlight.io/resource/09fd17c7-a410-4054-813d-f9652697ef9d","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Alex Roy"}],"http://schema.org/hasOccupation":[{"@value":"Humoriste"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}],"http://schema.org/url":[{"@value":"https://www.sixiemesens.ca/artiste/alex-roy/"}],"http://schema.org/sameAs":[{"@value":"https://www.facebook.com/alexroyfan"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/alex-roy.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:36:28.353-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/alex-roy.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-alex-roy#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E0529\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/price":[{"@value":"34"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-alex-roy#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"30"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-luc-langevin","@type":["http://schema.org/Event"],"http://schema.org/keywords":[{"@value":"varietes"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/varietes/luc-langevin.html"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K13-124"}],"http://schema.org/name":[{"@language":"fr","@value":"Luc Langevin"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-22T19:00:00-04:00"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/varietes/luc-langevin.jpg"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/varietes/luc-langevin.html#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"Luc Langevin est enfin de retour avec un tout nouveau spectacle à grand déploiement. Au sommet de son art et en parfaite maîtrise de son talent unique, il exécute avec finesse et précision des numéros de grandes illusions, créés sur mesure avec son complice de toujours, le magicien Stéphane Bourgoin. Dans un écrin de lumière où la magie opère, les effets sont époustouflants et touchants à la fois, la musique nous envoûte et nous transporte. Le quatrième spectacle de l’illusionniste frappe l’imaginaire. Il explore la dualité entre la tête et le cœur, nous invitant dans un monde fabriqué de toutes pièces pour fuir le réel. Un spectacle étonnant qu’il présentera dans toute la Francophonie et un peu partout à travers le monde au cours des prochaines années. «Impossible de ne pas être émerveillé! L'enfant en chacun de nous vous remerciera.» - Rythme FM Québec «Luc Langevin, magicien à coeur ouvert. Ce nouveau spectacle n'est pas une classique succesion de numéros. Le storytelling est réglé au cordeau, mais sa dextérité impressionne aussi. Tous les numéros donnent des frissons, même s'ils sont de différentes nature. Il va volontiers au contact du public.» - Le Figaro «Luc Langevin maîtrise son art. Tout impressionne les gens.» - mattv.ca «La magie de Luc Langevin épate et réchauffe le coeur.» - Showbizz.net «L'illusionniste Luc Langevin touche la cible [...] avec des numéros réglés au quart de tour présentés dans une ambiance théâtrale par ce gentleman de la magie qui ouvre du même coup son coeur au public.» - Le Soleil «[...] le spectateur peut enfin croire, l'espace de quelques heures, que tout devient possible. L'ovation de cinq minutes à la fin témoigne de l'appréciation pour ce moment [...] de partage entre étonnement et rêve.» - impactcampus.ca"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-luc-langevin#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-luc-langevin#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/varietes/luc-langevin.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:16:15.496-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/varietes/luc-langevin.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-luc-langevin#Offer_0","http://schema.org/price":[{"@value":"63"}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E050L\u0026CodeSiteClient=00000V"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-luc-langevin#Offer_1","http://schema.org/price":[{"@value":"59"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-les-etoiles-outaouais","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/les-etoiles-outaouais.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Les Étoiles de l'Outaouais"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-06-02T20:00:00-04:00"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/description":[{"@language":"fr","@value":"Le Spectacle des Étoiles 2026 promet une soirée vibrante d’émotion, de musique et de solidarité. Porté par Boom Desjardins, porte-parole de la Fondation Red Robin, l’événement met en lumière une invitée spéciale de taille: Roxane Bruneau, auteure, compositrice et interprète adorée du public et bien ancrée dans la scène artistique québécoise. Au-delà du plaisir du spectacle, cette soirée soutient une cause essentielle : la prévention du suicide et la promotion du bien-être par la thérapie assistée par les chevaux, au cœur de la mission de la Fondation Red Robin. Les autres artistes invités et les Étoiles de l’Outaouais (la relève artistique de la région) seront annoncés bientôt. Une présentation du 104.7 Outaouais"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/les-etoiles-outaouais.html#WebPage"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/autres-evenements/les-etoiles-outaouais-26.jpg"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"autres-evenements"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-les-etoiles-outaouais#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-les-etoiles-outaouais#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/les-etoiles-outaouais.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:14:58.283-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/les-etoiles-outaouais.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-les-etoiles-outaouais#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E055B\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"40"}],"http://schema.org/name":[{"@language":"fr","@value":"régulier"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-les-etoiles-outaouais#Offer_1","http://schema.org/price":[{"@value":"100"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"VIP (inclus un cocktail dînatoire, 2 consommations et une rencontre avec les artistes)"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-marie-elaine-thibert","@type":["http://schema.org/Event"],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-219"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/name":[{"@language":"fr","@value":"Marie-Élaine\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tThibert"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-10T15:00:00-04:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/MLqy4gYZVm4?si=1oe3NYmWEaDfDOv3"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/marie-elaine-thibert.html"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/marie-elaine-thibert.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/marie-elaine-thibert.html#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"Un hommage éblouissant à Dalida Pour donner suite à la sortie de son plus récent album 11 bis, rue d’Orchampt, Marie-Élaine Thibert présente Parlez-moi d’elle, un nouveau spectacle à grand déploiement qui promet d’émerveiller son public. Avec des interprétations puissantes, un décor parisien enchanteur et une sélection de chansons intemporelles, ce spectacle promet d'être une expérience unique et une célébration de l'héritage musical de Dalida. Une Interprète touchante Marie-Élaine Thibert a su s'imposer comme une figure incontournable de la musique québécoise. Avec sa voix puissante et émotive, elle a touché le coeur de nombreux admirateurs au Québec et ailleurs. Son spectacle Parlez-moi d’elle promet de célébrer la légende de Dalida en offrant des interprétations saisissantes des plus grands succès de l'icône. J’attendrai, Gigi l'amoroso, Il venait d'avoir 18 ans, Paroles paroles, Parlez-moi de lui, Darla dirladada, Besame mucho et bien d'autres. Un Voyage à Paris Le décor du spectacle sera parisien, recréant l’atmosphère envoûtante des rues de Paris, où Dalida a marqué l'histoire de la chanson française. Ce cadre romantique et élégant transportera les spectateurs sur les traces de la légendaire artiste, offrant une expérience immersive et nostalgique. Les spectateurs auront l'impression de se promener dans Montmartre, de flâner sur les boulevards parisiens et de ressentir l'âme et la magie de la Ville lumière."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-marie-elaine-thibert#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-marie-elaine-thibert#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/marie-elaine-thibert.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:15:27.439-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/marie-elaine-thibert.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-marie-elaine-thibert#Offer_0","http://schema.org/price":[{"@value":"45"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E050O\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-marie-elaine-thibert#Offer_1","http://schema.org/price":[{"@value":"41"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-ouellet","@type":["http://schema.org/Event"],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-03T20:00:00-04:00"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/MI4mwdLkfKc?si=fWtyjgINZXtTj-c2"}],"http://schema.org/name":[{"@language":"fr","@value":"Ouellet"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/ouellet.html"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/ouellet.html#WebPage"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/ouellet-humoriste.jpg"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/description":[{"@language":"fr","@value":"OUELLET – Le stand-up mordant du moment! Vous l’avez peut-être vu au ComédiHa! Fest, à Juste pour rire ou en première partie de Jean-Michel Anctil… Mais cette fois, Ouellet débarque avec SON show, 100 % inédit et pleinement assumé. Gaspésien fier et barbu attachant, il incarne cette génération d’humoristes qui allient observations sociales aiguisées et efficacité comique redoutable. Révélation du ComédiHa! Fest 2023, il ne recule devant aucun sujet et n’épargne personne — surtout pas ceux qu’il aime! Ouellet peaufine en ce moment même son prochain one-man-show. Un rodage cru, drôle, mordant et surtout authentique, livré avec toute la fougue d’un gars de chez nous. C’est franc, c’est drôle, et ça frappe juste. Venez voir Ouellet avant que tout le monde en parle. * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-ouellet#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-ouellet#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/ouellet.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:34:57.044-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/ouellet.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-ouellet#Offer_0","http://schema.org/price":[{"@value":"33"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04YJ\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-ouellet#Offer_1","http://schema.org/price":[{"@value":"29"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-paul-a-la-maison","http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/rendez-vous-theatre/paul-a-la-maison.html"}],"http://schema.org/keywords":[{"@value":"rendez-vous-theatre"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-08T20:00:00-04:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/name":[{"@language":"fr","@value":"Paul à la maison"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/r0iXrM1Vopo?si=uKIynpLzrtyuZ8RN"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/rendez-vous-theatre/paul-a-la-maison.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/rendez-vous-theatre/paul-a-la-maison.html#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"Maintenant dans la cinquantaine, Paul est auteur de bande dessinée à temps plein et lance un nouvel ouvrage au Salon du livre de Montréal. Depuis son divorce, il vit seul dans sa maison avec son chien Biscuit. Avec le départ imminent de sa fille pour Londres et l’état de santé de sa mère qui se dégrade, Paul sent bien que le temps l’a rattrapé. À travers les aléas de la vieillesse et du célibat, il est alors confronté au deuil, sous de multiples formes. L’œuvre de Michel Rabagliati est un incontournable de la culture québécoise. Paul touche toutes les générations, par sa justesse, son humour et sa grande vérité. De la case de bande dessinée jusqu’à la scène, Lorraine Côté promet de transposer l’univers de ce héros du quotidien en empruntant les codes du théâtre d’ombres, du théâtre d’objets et du cinéma en direct, tout en laissant une place aux mythiques dessins de Michel Rabagliati qui ont conquis les lecteur·rice·s du monde entier. Texte Michel Rabagliati Adaptation pour la scène Anne-Marie Olivier Mise en scène Lorraine Côté Distribution Hugues Frenette, Odile Gagné-Roy, Marie-Ginette Guay, Étienne D’Anjou, Paul Fruteau de Laclos, Nadia Girard Eddahia et Valérie Boutin Production Le Trident en coprésentation avec le Grand Théâtre de Québec «Le charme opère.» – Josianne Desloges, Revue Jeu «Aussi drôle que touchant.» – Valérie Marcoux, Le Soleil «On rit, on est ému, on n'a pas le temps de s'ennuyer.» – Tifa Bourjouane, ICI Première «Le décor donne l’impression de tourner les pages de l’album sans même s’en rendre compte.» – Alexandre Caputo, Le Journal de Québec"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-paul-a-la-maison#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-paul-a-la-maison#Offer_1"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-paul-a-la-maison#Offer_2"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-paul-a-la-maison#Offer_3"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-09T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-paul-a-la-maison#2026-05-08T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-paul-a-la-maison#2026-05-09T200000-0400"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/rendez-vous-theatre/paul-a-la-maison.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:35:13.462-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/rendez-vous-theatre/paul-a-la-maison.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-paul-a-la-maison#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"56"}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E01PB\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-paul-a-la-maison#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"52"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-paul-a-la-maison#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"30 ans et - / 60 ans et +"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"51"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-paul-a-la-maison#Offer_3","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée 30 ans et - / 60 ans et +"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"47"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-paul-a-la-maison#2026-05-08T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Paul à la maison"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-08T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-paul-a-la-maison#2026-05-09T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Paul à la maison"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-09T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-maxim-martin","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/maxim-martin.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Maxim Martin"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-17T20:00:00-05:00"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-275"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/maxim-martin.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/maxim-martin.html#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/description":[{"@language":"fr","@value":"Même après cinq one-man-show, un livre et une série sur son quotidien avec sa fille, Maxim Martin vous fera découvrir de nouveaux aspects de son attachante personnalité. Toujours aussi authentique, il se dévoile dans cette nouvelle période où il tente par tous les moyens de simplifier sa vie dans une époque où tout lui semble de plus en plus complexe. Bon chien, son sixième one-man-show, se veut une fenêtre ouverte sur son chemin parsemé de petites et grandes frustrations vers la zénitude. * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-maxim-martin#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-maxim-martin#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/maxim-martin.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-16T16:21:59.224-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/maxim-martin.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-maxim-martin#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04R7\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/price":[{"@value":"40"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-maxim-martin#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"36"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-the-musical-box","@type":["http://schema.org/Event"],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/the-musical-box.html#WebPage"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/CqJXzuCl2So?si=tVxyGfC1WXcbhNGi"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/373af9ef-4cf8-48c8-9e45-e69bf2c9f3db"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-19T20:00:00-05:00"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/the-musical-box.jpg"}],"http://schema.org/name":[{"@language":"fr","@value":"The Musical Box"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/the-musical-box.html"}],"http://schema.org/description":[{"@language":"fr","@value":"En 1973, GENESIS débarque au Québec pour la 1ère fois. C’est le début d’une longue histoire d’amour entre le groupe britannique et les Québécois, qui sont dès les premières notes du concert immédiatement subjugués. Rien ne pouvait préparer les jeunes spectateurs à SUPPER’S READY - une aventure musicale de 23 min. et quintessence du rock théâtre - et à la performance bouleversante d’un jeune PETER GABRIEL et ses mystérieux costumes. Un spectacle qui deviendra l’événement culte d’une génération. De ce même spectacle sera tiré l’album GENESIS LIVE (1973), une fenêtre sur l’univers dramatique de GENESIS sur scène, qui inclut les pièces maîtresses des albums TRESSPASS (1970), NURSERY CRYME (1971) et FOXTROT (1972). 50 ans plus tard, THE MUSICAL BOX, reconnu internationalement pour la qualité historique de son interprétation de l’oeuvre de GENESIS, présente le concert original de GENESIS LIVE. Une chance unique de remonter dans le temps."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-the-musical-box#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-the-musical-box#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/the-musical-box.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-18T16:24:45.468-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/the-musical-box.html"}]},{"@id":"http://kg.footlight.io/resource/373af9ef-4cf8-48c8-9e45-e69bf2c9f3db","@type":["http://schema.org/Organization"],"http://schema.org/name":[{"@language":"en","@value":"The Musical Box"}],"http://schema.org/url":[{"@value":"https://www.themusicalbox.net/"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Organization"}],"http://schema.org/sameAs":[{"@value":"http://www.wikidata.org/entity/Q1428406"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-the-musical-box#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"59"}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04R9\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-the-musical-box#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"55"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-laurent-paquin","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Laurent Paquin"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/spec-qc-ca_artiste_laurent-paquin"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-14T20:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/laurent-paquin.html#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/laurent-paquin.jpg"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/Mci3IJm9Vbo?si=EcBvm8Jkd-EKgVxw"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/laurent-paquin.html"}],"http://schema.org/description":[{"@language":"fr","@value":"Malgré son titre qui évoque la candeur et la naïveté, Crocodile distrait propose un humour incisif, baveux, corrosif, parfois cynique, mais toujours réfléchi. Dans ce 5e spectacle solo, Laurent Paquin affirme ne pas avoir le «bonheur facile». Ça ne l’empêche pas pourtant de réfléchir sur la question du bonheur. Qui est heureux? Pourquoi? Comment? Et pour combien de temps? Son précédent spectacle, Déplaire, avait marqué un tournant dans la carrière de l’humoriste, proposant un humour plus cru et plus direct. Avec Crocodile distrait, Laurent confirme son désir d’égratigner davantage, de mordre, de provoquer… mais avec ce sourire qui nous donne envie de lui pardonner d’avance. «Son sens du timing, son flow, sa calme assurance et sa capacité d’improviser autour de ses thèmes de prédilection font de Laurent Paquin un solide humoriste.» - La Presse «…Un cynisme aussi acéré que des dents de crocodile… Laurent Paquin n'a laissé à son public aucune chance de reprendre son souffle.» - Journal de Montréal «… J’ai ri du début jusqu’à la fin. Puis dans mon cas, dans un spectacle d’humour, je souris plus que je ris... » - 98.5 FM"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-laurent-paquin#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-laurent-paquin#Offer_1"}]},{"@id":"http://kg.footlight.io/resource/spec-qc-ca_artiste_laurent-paquin","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@value":"Laurent Paquin"}],"http://schema.org/image":[{"@value":"https://spec.qc.ca/uploads/artistes/laurent-paquin.jpg"}],"http://schema.org/sameAs":[{"@value":"http://kg.artsdata.ca/resource/K12-221"}],"http://schema.org/description":[{"@language":"fr","@value":"Laurent Paquin lance son premier one-man-show Première Impression en 2001 et charme le public québécois. En 2006, il présente son deuxième spectacle solo, Tout est relatif qui remporte un succès retentissant. Ce spectacle est vu par plus de 125 000 personnes et se mérite 2 Olivier pour le Spectacle d’humour et Auteur de l’année. Au théâtre, il joue dans la pièce L’emmerdeur et la comédie musicale à grand déploiement Chicago présentée à Montréal et Paris. À la télévision, il joue dans Histoires de filles pendant les six dernières saisons et dans les deux dernières saisons de Caméra Café. En publicité, tout le monde se souviendra de Bertrand le castor, auquel il prête sa voix pendant plus de trois ans pour le compte de Bell. À la radio, il est un des animateurs les plus populaires de la région métropolitaine lorsqu’il anime Debout Montréal à CKOI de 2009 à 2012. À l’été 2012, il réalise son rêve en lançant son premier album musical Laurent Paquin chante Laurent Paquin. Puis, en mars 2013, Laurent lance son troisième one-man-show, l’Ereure est humaine qui cumule plus de 150 000 billets vendus et 250 représentations. Nous l’avons vu dans le film Il était une fois Les Boys ainsi qu’aux Bye Bye 2013, 2014 et 2015 dans lesquels il s'est démarqué par son interprétation de Robin Thicke (Blurred lines) et de Denis Coderre. C’est avec Pénélope McQuade que Laurent co-anime le Gala des Jutra, en mars 2014 et c'est en 2015 qu'il anime le 17e Gala les Olivier ainsi que son 12ième Gala Juste pour rire, un record pour le nombre d’années consécutives. On le retrouvera cet été encore une fois à la barre d’un Gala, cette fois-ci en co-animation, avec Cathy Gauthier."}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/laurent-paquin.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-12T16:17:20.433-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/laurent-paquin.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-laurent-paquin#Offer_0","http://schema.org/price":[{"@value":"46"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E00WG\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-laurent-paquin#Offer_1","http://schema.org/price":[{"@value":"42"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-melanie-couture","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/melanie-couture.jpg"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K13-161"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-23T20:00:00-05:00"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/melanie-couture.html#WebPage"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/melanie-couture.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Mélanie Couture"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/zjJmQIIm7s4?si=GUBhjfuhiWfQGW1x"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/description":[{"@language":"fr","@value":"Dans ce deuxième spectacle solo, Mélanie Couture vous amènera à voir les choses d’un autre angle. À travers sa paire de lunettes, vous verrez qu’avoir des profs de maths qui se retrouvent avec la charge d’enseigner l’éducation sexuelle, ça change toute; que prendre un bain relaxant quand tu as un petit bain et des grosses fesses, ça change toute; qu’avoir des enfants quand tu as besoin d’une bonne nuit de sommeil, ça change toute; que de ne pas consulter de femme pour écrire un personnage féminin dans un film hollywoodien, ça change toute. Grâce à sa façon intelligente d’attaquer les sujets les plus intimes, son rire contagieux, ses propos crus et son talent pour dénouer les tabous, Mélanie Couture nous fait réaliser que parfois c'est long avant que tout change. D’autres fois, ça ne prend qu’un e. En nomination au Gala Les Olivier 2026: Spectacle d'humour de l'année Texte de l’année / Spectacle d’humour de l’année * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7 «L'humoriste et la bachelière en sexologie nous a amenés à réfléchir, elle a prouvé que les sujets tabous à propos de la sexualité n'ont pas à l'être et, surtout, elle nous a beaucoup fait rire.» - Marissa Groguhé, La Presse «Mélanie Couture présente un spectacle avec une proposition originale avec un discours peu entendu. À voir en couple ou en gang de filles!» - Catherine Beauchamp, 98,5 FM «Gros coup de coeur!» - Joanie Duquette, CKOI «Mélanie Couture a le sens du punch et n'hésite pas à explorer des zones qui pourraient faire sourciller les plus prudes.» - Marie-Ève Leclerc, Échos Vedettes «Très drôle!» - Isabelle Perron, QUB Radio"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-melanie-couture#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-melanie-couture#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/melanie-couture.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-22T16:30:29.641-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/melanie-couture.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-melanie-couture#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04RX\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/price":[{"@value":"35"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-melanie-couture#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"31"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mario-tessier-champion","@type":["http://schema.org/Event"],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-249"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/X238mJSXSvc?si=908Aj8p7Jqd6sePU"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-06T20:00:00-04:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/mario-tessier-champion.html"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/mario-tessier.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"L’humoriste Mario Tessier présente son 3e one-man-show intitulé Champion, alors qu’il est de retour avec l’énergie explosive qu’on lui connait. Champion, c’est un concentré d’humour irrésistible qui mélange autodérision et situations improbables. Dans ce spectacle, Mario affronte ses propres contradictions avec une dose de folie : suivre un entraînement de boxe professionnel juste après une séance de botox, devenir propriétaire d’une jument alors qu'il n’est jamais monté à cheval ou encore se prendre pour un génie alors que… bon, vous voyez le tableau! Avec Champion, Mario met en lumière ce «grand champion» qui sommeille en chacun de nous, celui qui nous pousse à viser plus haut… ou à nous ridiculiser de la plus drôle des façons. Si vous l’aimez à la radio sur Énergie, attendez de le voir sur scène! Il vous fera encore plus rire grâce à son énergie contagieuse, son efficacité comique légendaire et cette tête de vainqueur qu’on ne peut qu’aimer! «Un \"champion\" pour mettre son humour au goût du jour tout en restant le bon vieux chum des 30 dernières années!» - Boom FM «Hilarant du début à la fin, le spectacle d'humour à voir cette année!» - ÉNERGIE «Des punchlines redoutables, des échanges en choeur avec le public, des imitations éclatées... c'est du bonbon!» - pulsations.net «[le] numéro sur la colonoscopie, c'est un vrai morceau d'anthologie de l'humour québécois! J'ai adoré!» - QUB radio «Authentique et captivant... assurément un des spectacles incontournables de l'année!» - Noovo Moi «Champion est un excellent exemple de spectacle d'un vieux routier de l'humour qui a su demeurer alerte et adapter son propos au goût du jour.» - Hollywood PQ «Mario Tessier met à profit ses plus puissants outils en matière de comique physique et de burlesque.» - La Presse «Maniant bien les imitations, l'autodérision et toujours baveux à souhait, Tessier sur scène montre qu'il est aussi à L'aise avec l'humour physique.» - Le Soleil «Un spectacle intime et rythmé... atteint brillamment sa cible» - Journal de Montréal"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/mario-tessier-champion.html#WebPage"}],"http://schema.org/name":[{"@language":"fr","@value":"Mario Tessier"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mario-tessier-champion#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mario-tessier-champion#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/mario-tessier-champion.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:11:54.270-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/mario-tessier-champion.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mario-tessier-champion#Offer_0","http://schema.org/price":[{"@value":"46"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04Y4\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mario-tessier-champion#Offer_1","http://schema.org/price":[{"@value":"42"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-mathieu-cyr","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/1ace79d9-d268-439d-a198-f1feec79c6b3"}],"http://schema.org/name":[{"@language":"fr","@value":"Mathieu Cyr"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/mathieu-cyr.html#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-10T20:00:00-05:00"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/PA90tBWnmXc?si=4wtMIxgR5VZmn25j"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/mathieu-cyr.html"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/mathieu-cyr.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"Avec son humour décomplexé et explosif, Mathieu Cyr punch de manière hilarante sur nos travers humains, avec la délicatesse d’une main de ciment dans un gant de plomb. Hyperactif infatigable et déficient d’attention troublé, Mathieu Cyr aime avoir une opinion sur tout en n’étant spécialiste de rien. Comme il le dit si bien : «avoir la langue dans sa poche, ça sert juste à mouiller tes jeans…» Sur les réseaux sociaux, il est suivi par plus de 600 000 fans. Chez lui, il est suivi par trois enfants et deux chats. Il tient à ce qu’on écrive ça ici, pour qu’il se rappelle de ne pas oublier son ti-rouleau à poils avant de monter sur scène. Un show qui défoule et qui fait du bien. 90 minutes de gros fun! CV : Mathieu Cyr a participé à des dizaines de galas juste pour rire et Comediha, animé les émissions de télé Comment devenir un gourou, Skate Le Monde et Slaque la cravate avec Mat. On a pu l’entendre sur les ondes de CKOI 96,9. Sur scène, il a récemment présenté le spectacle Avant la fin du monde (en duo avec Olivier Martineau), qui a été présenté devant plus 45 000 spectateurs. Spectacle pour public averti – 16 ans et plus. * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-mathieu-cyr#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-mathieu-cyr#Offer_1"}]},{"@id":"http://kg.footlight.io/resource/1ace79d9-d268-439d-a198-f1feec79c6b3","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Mathieu Cyr"}],"http://schema.org/hasOccupation":[{"@value":"Humoriste"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}],"http://schema.org/url":[{"@value":"https://mathieucyr.ca/"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/mathieu-cyr.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-08T16:30:52.456-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/mathieu-cyr.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-mathieu-cyr#Offer_0","http://schema.org/price":[{"@value":"40"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E0505\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-mathieu-cyr#Offer_1","http://schema.org/price":[{"@value":"36"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-claude-dubois","http://schema.org/description":[{"@language":"fr","@value":"Après nous avoir présenté son coffret Dubois Solide, un imposant recueil de 51 chansons, Claude Dubois, l'auteur-compositeur et interprète, va à la rencontre de son public dans un spectacle intitulé Dubois solide en liberté où il nous emmène visiter son univers. Avec sa voix puissante et son indéniable charisme, ce géant de la chanson québécoise, amoureux inconditionnel et défenseur de la langue française, nous livre sur scène son répertoire intemporel. Un moment unique où il interprète ses chansons les plus demandées, les incontournables, qui sont la signature de ce grand artiste."}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/spec-qc-ca_artiste_claude-dubois"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/claude-dubois.html"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/claude-dubois.jpg"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-01T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/claude-dubois.html#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/Qsm4EMTgv4E"}],"http://schema.org/name":[{"@language":"fr","@value":"Claude Dubois"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-claude-dubois#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-claude-dubois#Offer_1"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-04T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-claude-dubois#2026-05-01T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-claude-dubois#2026-09-04T200000-0400"}]},{"@id":"http://kg.footlight.io/resource/spec-qc-ca_artiste_claude-dubois","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@value":"Claude Dubois"}],"http://schema.org/description":[{"@language":"fr","@value":"Né à Montréal le 24 avril 1947, Claude Dubois est un auteur-compositeur-interprète Québécois. La passion pour la musique habite Claude dès l’enfance et c’est à l’âge de douze ans qu’il commence sa carrière dans le groupe country-western Claude Dubois et ses Montagnards. C’est d’ailleurs avec eux qu’il enregistre Stampede canadien, son tout premier disque, en 1959. À l’âge de quinze ans, c’est en faisant du pouce, guitare à la main, que Claude Dubois rencontre Gilles Vigneault, poète et chanteur qu’il admire. Il ne laisse pas passer sa chance et en profite pour lui jouer quelques morceaux. Le poète le trouve intéressant et lui propose alors de faire la première partie de quelques spectacles. Les deux artistes s’entendent bien et c'est le début d'une amitié qui dure depuis plusieurs décennies. En 1964, à l’âge de 17 ans, Dubois a déjà écrit un répertoire qu’il présente régulièrement à la boîte à chansons Le Patriote. En 1966, c’est avec un disque intitulé simplement Claude Dubois que l’on découvre les futurs succès J’ai souvenir encore et Ma petite vie. Ce disque vaut à l’artiste de gagner le prix spécial du jury au Festival du disque de Montréal. En 1967, on lui décerne le trophée Découverte de l’année. Cette même année, lors de l’Expo 67, il offre une performance au Pavillon de la jeunesse et sa chanson Cerveau gelé devient le thème d’un court-métrage documentaire sur Montréal. On le retrouve ensuite, avec Raymond Lévesque, faisant la première partie de Gilles Vigneault sur la scène de la Comédie-Canadienne. C’est la fin des années 60. La musique, la politique, les valeurs, tout est en mouvement, en transformation. À ce moment-là, l’artiste a envie de découvrir un peu plus le monde et d’autres cultures. Il s’installe d’abord à Londres, puis à Paris. Les événements de l’époque, mai 68, tout comme le vent de changement qui souffle sur le Québec, amènent Claude Dubois à enregistrer la chanson Comme un million de gens. Lancée au Québec en 1969, celle-ci obtient un succès instantané. D’ailleurs, elle sera sacrée Classique de la SOCAN en 1994 et intronisée au Panthéon des auteurs et compositeurs canadiens en 2008. Poussé par le goût de l’aventure, assoiffé de liberté et de voyages, Claude décide de parcourir le monde jusqu’en 1972. Son retour au Québec est souligné avec l’album Claude Dubois. Signé en France sur l’étiquette Disques Vogue, cet album comprend une chanson qui deviendra un incontournable de son répertoire : Le Labrador. Cette dernière, enregistrée avec François Rauber, transcende son amour pour le Québec. C’est une période prolifique où Claude Dubois réussit à allier le style chansonnier au style plus rock et populaire. En 1973, Touchez Dubois, enregistré en majeure partie à Los Angeles avec les musiciens de Nancy Sinatra, confirme l’ascension de l’auteur-compositeur-interprète avec des succès instantanés : Femmes de rêve, Besoin pour vivre, La vie à la semaine et Bébé Jajou Latoune, pour ne nommer que celles-là. L’album jouit d’une forte popularité dès sa sortie. Cette année-là, Claude participe aussi à un grand événement, Le ODD Show, présenté au Centre sportif de l’Université de Montréal. Il partage alors la scène avec Diane Dufresne et Offenbach. Désormais bien ancré dans l’industrie du spectacle, l’artiste propose, en 1974, l’album Claude Dubois. Celui-ci est enregistré au Bearsville Studios, près de Woodstock, et sort sur l’étiquette Barclay. Le disque, enregistré avec des musiciens de Janis Joplin, Jimmy Hendrix et David Bowie, séduit immédiatement les auditeurs avec d’autres succès, notamment les chansons En voyage, Hibou et L’Infidèle. Soucieux de bien gérer ses droits d’auteur, Claude Dubois est éditeur de ses chansons. D’abord avec Les Éditions du Son, puis avec Les Éditions C.D. et, enfin, avec Les Éditions Musicales Pingouin. L’artiste a plus d’une corde à son arc. En 1973, il anime l’émission musicale Décibels à la télévision de la SRC. L’expérience se répète à Télé-Métropole, en 1975 et en 1976, avec l’émission Showbizz. Porté par l’inspiration des voyages et par le goût de créer différemment, Claude Dubois part enregistrer, à Londres et à Miami, un album aux saveurs antillaises. Accompagné par le groupe The Wailers, musiciens de Bob Marley, le disque Mellow Reggae voit le jour en 1977 et c’est sur celui-ci que l’on découvre la magnifique chanson Artistes. À l’époque, Dubois est le premier francophone à chanter du reggae. En 1978, c’est sur son étiquette, Disques Pingouin, qu’il enregistre Fable d’espace, un album qui propose les grands succès Chasse-galerie et Au bout des doigts. Cette même année Claude présente le spectacle Dubois deboutte, au Théâtre Saint-Denis, à Montréal. À travers son agenda déjà bien chargé, il trouve le temps d’aller à Paris pour enregistrer Le Blues du businessman de Michel Berger et de Luc Plamondon. Chanson phare du célèbre opéra-rock Starmania, elle obtient un succès fracassant en France et au Québec. Claude Dubois remporte d’ailleurs le trophée Félix de l’interprète masculin de l’année lors du tout premier Gala de l’ADISQ en 1979. C’est en 1982 qu’est lancé Sortie Dubois, un disque dont le titre évoque un passage de l’homme. L’album se vend à plus de 200 000 exemplaires et ses succès tels Plein de tendresse, Apocalypse, Femmes ou filles… se hissent à la tête des palmarès. La même année il lance le simple de la chanson Femme de société, un autre grand succès, en duo avec Léo Kay. Le 4 juin 1982, c’est au Forum de Montréal, accompagné des musiciens du groupe UZEB, que Claude Dubois offre une performance électrisante devant une salle pleine à craquer. Le spectacle est aussi présenté au Colisée de Québec et bat pour l’occasion des records d’assistance. C’est une année riche en émotions car lors du gala de l’ADISQ, Claude Dubois marque le moment en remportant cinq trophées Félix : l’interprète masculin; la chanson de l’année pour la pièce Plein de tendresse; le spectacle de l’année; l’album auteur-compositeur-interprète et l’album toutes catégories. En 1983, Dubois remporte à nouveau le Félix de l’interprète masculin. L’album Implosif sort la même année et devient disque d’or avec 50 000 exemplaires vendus rapidement. En juillet 1984, 25 000 personnes sont présentes au Vieux-Port de Montréal pour le spectacle de l’artiste. L’année suivante, la chanson Un chanteur chante devient le titre d’une tournée québécoise. En février 1985, Claude se joint à un collectif d’artistes du Canada pour enregistrer le simple Tears Are Not Enough afin de lever des fonds pour combattre la famine qui sévit en Éthiopie depuis 1983. Puis en 1986 il est à nouveau élu Interprète masculin de l’année à l’ADISQ. En 1987, Claude Dubois est présent aux Francofolies de La Rochelle pour offrir un spectacle hommage à Léo Ferré. Le grand artiste, qui est présent, est ému. Claude en garde un souvenir inoubliable. En 1989, le réalisateur québécois Jacques Benoît réalise un film d’après le premier roman à succès de Dany Laferrière, écrivain majeur de la littérature d'expression française. Claude Dubois est l’interprète et le compositeur, avec Dany Laferrière, de la chanson thème du film : On vit de femmes. Manu Dibango signe les arrangements de la pièce. Durant l’été 89 Claude est sur scène à Paris, au Théâtre Marigny, pour la première fois dans l’opéra-rock Starmania. Le 29 novembre 1991, les Francofolies de Montréal rendent hommage à l’artiste avec le spectacle La Fête à Claude Dubois. Le 19 juillet 1992, pour la clôture du 25e Festival d’été de Québec, Dubois embarque Charlebois sur sa moto et arrive sur scène à la Place d’Youville. Il offre une performance époustouflante devant plus de 50 000 spectateurs. En 1996, l’artiste propose l’album et le théâtre musical Raconte-moi Gelsonima, dont la chanson Si Dieu existe deviendra un grand classique de son répertoire. Ce disque est un témoignage d’estime au cinéaste Federico Fellini pour son film La Strada. En 2001, lors du 23e gala de l’ADISQ, Claude Dubois reçoit le prix Hommage destiné à récompenser l’ensemble de sa carrière. C’est le 7 juillet 2006 que le Festival d’été de Québec présente le spectacle Femmes de rêve. Des interprètes féminines telles Diane Dufresne, Nanette Workman et d’autres excellentes chanteuses viennent rendre hommage à Claude Dubois devant une foule de 100 000 personnes. Quelques mois plus tard, en mai 2007, des artistes québécois et français de grande renommée unissent leur voix à celle de l’artiste et l'album Duos Dubois voit le jour. Le disque est chaudement accueilli et connaît un franc succès. On y retrouve Céline Dion, Francis Cabrel, Gilles Vigneault, Richard Desjardins, André- Philippe Gagnon ainsi que plusieurs autres grands artistes. Lors du Gala de l'ADISQ, Claude Dubois reçoit le Félix de l'Album populaire de l'année. Il sera honoré à nouveau en 2008 pour Duos Dubois en recevant le Félix de l’Album de l’année-Meilleur vendeur. Après avoir vu plusieurs de ses chansons devenir des Classiques de la SOCAN (Société des auteurs compositeurs et éditeurs de musique) : Comme un million de gens et Femme de rêve (1994), Artistes, En voyage et Infidèle (1995), Plein de tendresse (1996), Besoin pour vivre et Femme de société (1998), Bébé jajou la toune (2001), Femme ou fille (2005), Claude Dubois est admis au Panthéon des auteurs et compositeurs canadiens en 2008. En 2017, deux autres Classiques s’ajouteront aux autres : J’ai souvenir encore et Si Dieu existe. Le 22 août 2008, Dubois est invité à participer au méga-concert Céline Dion sur les plaines en compagnie de plusieurs artistes du Québec. Ce spectacle souligne les 400 ans de la ville de Québec et 250 000 personnes écoutent avec fébrilité la chanson Si Dieu existe, interprétée en duo par Claude Dubois et Céline Dion. En 2011 et 2012, Claude fait ses valises et part faire des concerts en France avec l’équipe Âge tendre, la tournée des idoles. Zampano, le 38e album en carrière de l’artiste, est lancé en 2016. Cette année-là, Claude Dubois est honoré par la Fondation SPACQ (Société professionnelle des auteurs et des compositeurs du Québec) et reçoit le prix Luc Plamondon. L’année suivante il lance l’album Mes racines. Le 26 mars 2017, Claude Dubois est honoré dans le cadre du 42e anniversaire d’Imperatif français (organisme culturel québécois de défense et de promotion de la langue française), de la Journée internationale de la Francophonie et de la Francofête. L’artiste reçoit le prestigieux prix Imperatif français, car il s’est illustré par sa contribution exceptionnelle à la vitalité de la langue et de la culture d’expression française. Le 17 juin 2017, l’artiste chante aux Francos de Montréal. Quelques jours plus tard, le 23 juin, il prend part au grand spectacle de la Fête nationale. Le 16 juillet 2017, lors du 50e Festival d'été de Québec, Claude Dubois reçoit le prix Miroir de la Renommée. Le Festival d'été de Québec honore l'auteur-compositeur-interprète afin de souligner l'impact qu’il a eu sur la culture musicale du Québec et sur l'histoire du festival. Une année bien remplie, car le 23 septembre plusieurs artistes lui rendent hommage à l’émission, En direct de l’univers, présentée à Radio-Canada. Claude est très touché par cela. Deux mois plus tard, le 29 novembre, il est le premier artiste à inaugurer le Théâtre Gilles-Vigneault situé à St-Jérôme. Le 9 juin 2018, dans le cadre des Francos de Montréal, il présente le spectacle Dubois en Liberté au Théâtre Maisonneuve de la Place des Arts. Devant une salle comble, l’auteur-compositeur et interprète présente un répertoire qui fait partie de la culture québécoise depuis plus de 50 ans. Le même mois, Claude Dubois participe à nouveau au grand spectacle de la Fête nationale, à Montréal. Un événement auquel il participe toujours avec fierté. En juillet 2018, lors du FestiVoix de Trois-Rivières, Claude est sur scène et chante ses plus grands succès devant un public conquis d’avance. Il termine en beauté le 25e anniversaire d’une édition qualifiée d’historique selon les organisateurs. Début 2021, Claude Dubois lance le coffret Dubois Solide. Trois disques contenant 51 coups de cœur de la mémoire collective. Une anthologie qui nous plonge dans nos souvenirs et nous fait rêver à ceux de demain. Cette démarche souligne en quelque sorte le travail d’un artiste qui a su laisser sa marque à travers plus de soixante ans de carrière et qui parcourt encore les routes du Québec et de la francophonie. Entre de nombreux spectacles et des disques, malgré un infarctus en 1998 et un cancer de la moelle osseuse diagnostiqué en mars 2016 – qui ne guérira jamais – mais qui est stabilisé grâce aux divers traitements de chimiothérapie, Dubois est toujours aussi passionné et motivé. Le public lui donne de l’énergie et de l’amour. Depuis 2017 il présente à guichets fermés son spectacle Dubois en liberté. Un titre qui n’aura jamais autant pris son sens. Claude Dubois, une voix touchante sur des textes intemporels. Une feuille de route impressionnante, une vie d’artiste bien remplie."}],"http://schema.org/sameAs":[{"@value":"http://kg.artsdata.ca/resource/K12-240"}],"http://schema.org/image":[{"@value":"https://spec.qc.ca/uploads/artistes/claude-dubois-1.jpg"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/claude-dubois.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:21:08.046-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/claude-dubois.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-claude-dubois#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E00Z3\u0026CodeSiteClient=00000V"}],"http://schema.org/price":[{"@value":"75"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-claude-dubois#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"71"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-claude-dubois#2026-05-01T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Claude Dubois"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-01T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-claude-dubois#2026-09-04T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Claude Dubois"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-04T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_country-folk-lendemain-de-veille","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/1c195e6f-a466-400b-ae99-eda224f01f21"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/country-folk/lendemain-de-veille.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Lendemain de\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tveille"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-29T20:00:00-05:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"country-folk"}],"http://schema.org/description":[{"@language":"fr","@value":"Originaire d’un petit village de la Rive-Sud de Montréal, Lendemain de veille est une formation de cinq musiciens d’exception qui brûle les planches depuis plus de 15 ans. Fort de plusieurs prix, notamment le Felix de l’Album de l’année – Country à l’ADISQ 2022 et le prix du Spectacle et du Groupe de l’année au Gala Country 2023, Lendemain de veille a su rassembler les foules autour de leurs trois premiers albums. La formation dévoile en 2024 son troisième opus, On s’en vient!, prête à repartir sur les routes du Québec pour un spectacle plus festif que jamais. Mettez la bière au frette parce qu’on s’en vient! Faits saillants : Le party est pogné sacré Album de l’année – Country à l’ADISQ 2022 Récipiendaire des prix Groupe et Spectacle de l’année au Gala Country 2023 Récipiendaire du prix Groupe de l’année au Gala Country en 2022 et 2024 Les trois premiers albums cumulent plus de 35,8 millions d’écoutes sur les plateformes Plus de 109 000 auditeurs mensuels sur Spotify Plus de 4.6 millions de vues pour le vidéoclip On était soûls Veuillez noter que les artistes pourraient inviter les spectateurs à se lever pendant le spectacle. Nous préférons vous en avertir. Première partie : Vince Lemire Véritable bête de scène à l’énergie contagieuse, Vince Lemire est un incontournable du mouvement new country actuel. Son premier album Gaz au fond se taille une place de choix sur les ondes radiophoniques, atteignant le sommet des palmarès avec ses succès On aime le Whisky, Belle vie et Vieux Bucké. Son deuxième album À la bonne place apporte une perspective urbaine et fraîche au genre, en incorporant autant des éléments de musique rap/pop que country rock. La complicité entre Vince et ses musiciens est indéniable: chaque performance est comme la dernière et c’est festif à souhait! Ce country boy des temps modernes est assurément à la bonne place sur scène et surtout, il sait comment faire lever une foule!"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/lF2OLUpusFY?si=7cmKzNESQX7JzI3U"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/country-folk/lendemain-de-veille-2026.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/country-folk/lendemain-de-veille.html#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_country-folk-lendemain-de-veille#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_country-folk-lendemain-de-veille#Offer_1"}]},{"@id":"http://kg.footlight.io/resource/1c195e6f-a466-400b-ae99-eda224f01f21","@type":["http://schema.org/Organization"],"http://schema.org/name":[{"@language":"fr","@value":"Lendemain de veille"}],"http://schema.org/url":[{"@value":"https://lendemaindeveille.ca/"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Organization"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/country-folk/lendemain-de-veille.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-28T16:24:51.486-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/country-folk/lendemain-de-veille.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_country-folk-lendemain-de-veille#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04WM\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"36"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_country-folk-lendemain-de-veille#Offer_1","http://schema.org/price":[{"@value":"32"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-classe-moyenne","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/classe-moyenne.html"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/soirees-cabaret/classe-moyenne.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/MJFPq6_4GDE?si=3aCjOLqIqhKkyDXO"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/classe-moyenne.html#WebPage"}],"http://schema.org/name":[{"@language":"fr","@value":"Classe moyenne"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-06-10T20:00:00-04:00"}],"http://schema.org/keywords":[{"@value":"soirees-cabaret"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/dad71250-8ec8-4b41-aa18-47eef056a04c"},{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/description":[{"@language":"fr","@value":"Tik Tok les adore, Instagram les suit pas à pas, les plateformes musicales brûlent tant on les écoute : 8,42 millions de clics en quelques mois. Le collectif de musiciens Classe moyenne est la sensation de l’heure, un phénomène comme il s’en produit peu. C’est dans un sous-sol près de La Prairie que le feu prend, là où cinq amis puisent dans leur quotidien pour écrire des chansons qui ressemblent au quotidien des trentenaires, mais que tout le monde aime. Nous autres on s’appelle Classe moyenne, les Colocs c’t’ait déjà pris écrivent-ils, et déjà la séduction opère. Quand ils ajoutent On voulait aller à l’uni, on a tous fini chez Benji, parle-moi d’une école de la vie, on en redemande. Ils s’appellent Classe moyenne, donc, mais aussi Benjamin Nadeau (réalisateur et producteur ayant collaboré avec Marc Dupré, William Cloutier, Ludovick Bourgeois, Dee Holt, Mc12), Gabriel Fredette, Justin Roy, Phil Rxcket et Zach Chico. Des amis qui se sont retrouvés ensemble dans le sous-sol de Benjamin, qui ont écrit une chanson vite devenue virale, qui en ont écrit treize depuis et qui vont continuer de le faire parce qu’ils s’amusent. Et que le monde aime ça, beaucoup, beaucoup. Ne cherchez pas de règles ni de recettes, il n’y en a pas chez Classe moyenne. Il n’y a que la liberté, le plaisir, le positif, le gros party, et pour vous, un bateau qu’il ne faut manquer, car il ne passe pas souvent! * Spectacle présenté au foyer de la salle Odyssée / Admission générale / Formule debout / Bar en fonction / Places limitées"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-classe-moyenne#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-classe-moyenne#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/classe-moyenne.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:17:42.355-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/classe-moyenne.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-classe-moyenne#Offer_0","http://schema.org/price":[{"@value":"34"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E050Y\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-classe-moyenne#Offer_1","http://schema.org/price":[{"@value":"30"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-olivier-martineau","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-237"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/name":[{"@language":"fr","@value":"Olivier Martineau"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/olivier-martineau.html"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-31T20:00:00-05:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/bI3UqANNPkE?si=XZ51fFXXDGAzWkfJ"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/olivier-martineau-2026.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"Société, c’est fini. Oui, on a eu de beaux moments, mais là, c’est trop. T’as changé. Moi, je m’en vais. Je te laisse tes règles farfelues et tes promesses en plastique. J’en ai assez de cette mascarade. C’EST PAS MOI, C’EST TOI, c’est du stand-up pur et dur, un véritable feu roulant de gags où chaque punch frappe en plein cœur. Un regard sans filtre, mordant, mais surtout hilarant, sur une société où la rupture était inévitable. Après trois spectacles, celui-ci est le plus audacieux, le plus osé, le plus authentique. Bref, c’est la fin d’une histoire… et le début d’un show qui décoiffe. Mais moi, je m’en fous, j’ai pas de cheveux."}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/olivier-martineau.html#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-olivier-martineau#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-olivier-martineau#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/olivier-martineau.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-30T16:41:24.463-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/olivier-martineau.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-olivier-martineau#Offer_0","http://schema.org/price":[{"@value":"42"}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04WO\u0026CodeSiteClient=00000V"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-olivier-martineau#Offer_1","http://schema.org/price":[{"@value":"38"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-stephane-fallu","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Stéphane Fallu"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-14T20:00:00-05:00"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/36PiwajqjlM?si=KG-hrfemmF3IvbgP"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/b3d8b21b-4138-4f4d-9fdd-6ec3ef086d5f"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/stephane-fallu.html#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/stephane-fallu.jpg"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/stephane-fallu.html"}],"http://schema.org/description":[{"@language":"fr","@value":"À 55 ans, Fallu s’assume. L'humoriste se dévoile sans filtre, invitant le public à explorer toutes les facettes de sa personnalité complexe et surprenante. Entre contradictions, états d'âme et révélations inattendues, il dévoile une part de lui-même jusque-là inédite. Avec Assumé, Stéphane Fallu renoue avec ses premiers amours dans un stand-up audacieux et résolument personnel, abandonnant son image de «bon garçon» pour révéler un côté plus incisif et mordant. Chaque anecdote devient une fenêtre ouverte sur sa vie, livrée avec un mélange unique de légèreté et de profondeur. Stéphane Fallu assume sans retenue ce qui le fait vibrer et ce qu'il exècre. Il offre une performance où l'humour se conjugue à l'introspection, transformant chaque sujet en une invitation à voir le monde sous un angle hilarant et percutant. Une performance où l'humour vif se conjugue à une réflexion sur la vie quotidienne. * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7 «Vous ne vous tromperez pas en choisissant de passer une soirée en compagnie de Stéphane Fallu pour une bonne dose de dire!» - Marie-Eve Lanthier, MEVE ET CIE «Il réussit à faire naître des rires francs avec des sujets plus sérieux.» - Léa Harvey, LE SOLEIL «Un peu moins bon enfant et un peu plus corrosif» - Frédérique de Simone, JOURNAL DE MONTRÉAL «Stéphane Fallu nous rend de bonne humeur avec son nouveau spectacle» - Marie-Soleil Lajeunesse, NOOVO MOI"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-stephane-fallu#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-stephane-fallu#Offer_1"}]},{"@id":"http://kg.footlight.io/resource/b3d8b21b-4138-4f4d-9fdd-6ec3ef086d5f","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Stéphane Fallu"}],"http://schema.org/hasOccupation":[{"@language":"fr","@value":"Humoriste"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}],"http://schema.org/sameAs":[{"@value":"http://www.wikidata.org/entity/Q3501749"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/stephane-fallu.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:23:48.562-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/stephane-fallu.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-stephane-fallu#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"40"}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04WS\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-stephane-fallu#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"36"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-la-tournee-juste-pour-rire","@type":["http://schema.org/Event"],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/name":[{"@language":"fr","@value":"La Tournée Juste pour\n\t\t\t\t\t\t\t\t\t\t\t\t\t\trire"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K10-374"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-31T20:00:00-05:00"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/la-tournee-juste-pour-rire.html#WebPage"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/la-tournee-juste-pour-rire-b.jpg"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/la-tournee-juste-pour-rire.html"}],"http://schema.org/description":[{"@language":"fr","@value":"Préparez-vous à accueillir une nouvelle génération de talents humoristiques! Quatre humoristes au style distinctif et à l’énergie contagieuse vous promettent une tournée mémorable. C’est l’opportunité parfaite de découvrir les étoiles montantes de l’humour! Ces quatre humoristes, aux univers uniques, apportent chacun leur touche personnelle : l’humour authentique et percutant de Douaa Kachache, l’attachant Steve Biko avec son humour à la fois engagé et sarcastique, la satire énergique de la polyvalente Magali Saint-Vincent, ainsi que l’audace et l’autodérision du surprenant Thomas Bédard. Un rendez-vous incontournable pour tous les passionnés d’humour! * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-la-tournee-juste-pour-rire#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-la-tournee-juste-pour-rire#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/la-tournee-juste-pour-rire.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-30T16:29:13.959-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/la-tournee-juste-pour-rire.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-la-tournee-juste-pour-rire#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"26"}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04XP\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-la-tournee-juste-pour-rire#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"22"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-alex-nevsky","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/2b08c5a8-1e50-4e8e-8878-a07e45552ac6"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/alex-nevsky.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-13T20:00:00-05:00"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/alex-nevsky.html#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/alex-nevsky.html"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-alex-nevsky#Offer_0"}]},{"@id":"http://kg.footlight.io/resource/2b08c5a8-1e50-4e8e-8878-a07e45552ac6","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"en","@value":"Alex Nevsky"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Singer-songwriter"}],"http://schema.org/sameAs":[{"@value":"http://www.wikidata.org/entity/Q16525960"},{"@value":"https://www.facebook.com/AlexNevskyOfficiel/"}],"http://schema.org/url":[{"@value":"https://www.alexnevsky.ca/"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/alex-nevsky.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:26:49.351-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/alex-nevsky.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-alex-nevsky#Offer_0","http://schema.org/price":[{"@value":"0"}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://www.ovation.qc.ca/pesite.asp?CodeEtablissement=000014\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-sam-vigneault","@type":["http://schema.org/Event"],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/sam-vigneault.html#WebPage"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/4f61be27-f0dd-40ba-b6e9-a24a10bdaddf"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/soirees-cabaret/sam-vigneault-26.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-17T20:00:00-05:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/dad71250-8ec8-4b41-aa18-47eef056a04c"},{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/tFb92ejxBAE?si=9wLMZ-z5llmp6P4z"}],"http://schema.org/keywords":[{"@value":"soirees-cabaret"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/sam-vigneault.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Sam Vigneault"}],"http://schema.org/description":[{"@language":"fr","@value":"Originaire de Plessisville et fils d’une longue lignée d’agriculteurs, Sam Vigneault, qui présente son tout premier spectacle en rodage, se démarque grâce à son style, son look et son humour unique et rafraichissant. Celui qu’on surnomme l’humoriste à l’accordéon a déjà conquis de nombreux publics, entre autres en première partie de Dominic Paquet, sur le web et lors de ses prestations acclamées sur les galas ComediHa!. Observateur du quotidien qui ne ressemble à personne, Sam transforme les petites absurdités de la vie en moments franchement hilarants. * Formule cabaret au foyer de la salle Odyssée / Ambiance intime et conviviale / Bar en fonction / Places limitées"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-sam-vigneault#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-sam-vigneault#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/sam-vigneault.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:25:59.032-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/sam-vigneault.html"}]},{"@id":"http://kg.footlight.io/resource/4f61be27-f0dd-40ba-b6e9-a24a10bdaddf","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Sam Vigneault"}],"http://schema.org/hasOccupation":[{"@language":"fr","@value":"Humoriste"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-sam-vigneault#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04WT\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/price":[{"@value":"29"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-sam-vigneault#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"25"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-jf-otis","@type":["http://schema.org/Event"],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/a0JxB0FAv40?si=aYmwBdtGmRzPSp8l"}],"http://schema.org/name":[{"@language":"fr","@value":"JF Otis"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/jf-otis.html"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-18T20:00:00-05:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/dad71250-8ec8-4b41-aa18-47eef056a04c"},{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/keywords":[{"@value":"soirees-cabaret"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/jf-otis.html#WebPage"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/soirees-cabaret/jf-otis.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"JF est un gars de Rimouski qui prend d’excellentes mauvaises décisions. Bachelier en administration des affaires devenu humoriste, star du web, il dit que ce fut une décision qui l’a rendu heureux mais qui a rendu ses parents… nerveux. La meilleure décision qu’il a prise selon lui après avoir commencé à faire des vidéos sur internet et s’acheter un lave vaisselle. L'improvisation est un élément important de son stand-up. Chaque représentation devient alors unique, mais toujours aussi efficace. Lorsqu'il est sur scène, on a envie d’écouter et de rire avec ce «gros nounours». L’assistance est placée dans une ambiance festive, comme si l’on était tous autour d’un feu de camp en compagnie d’un vieil ami racontant des anecdotes sans queue ni tête à nous en faire renverser notre verre! En entendant ses histoires vous allez être découragés ou encore vous allez le juger mais une chose est sûre, vous allez rire. * Formule cabaret au foyer de la salle Odyssée / Ambiance intime et conviviale / Bar en fonction / Places limitées"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-jf-otis#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-jf-otis#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/jf-otis.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:20:30.637-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/jf-otis.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-jf-otis#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04WU\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"31"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-jf-otis#Offer_1","http://schema.org/price":[{"@value":"27"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-simon-delisle","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/simon-delisle.html"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/02e0aa9d-4319-420f-98a3-0418e6138f9f"}],"http://schema.org/name":[{"@language":"fr","@value":"Simon Delisle"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-21T20:00:00-05:00"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/simon-delisle.html#WebPage"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/simon-delisle.jpg"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/description":[{"@language":"fr","@value":"Suite à son premier one-man show acclamé par la critique, le public et ses pairs, Simon présente enfin son deuxième spectacle : TACHE. À travers le regard unique qu’il porte sur la vie et le monde, avec son humour percutant et rempli d’images puissantes, Simon Delisle s’y révélera sans filtre, abordant la large notion de «tache» de diverses façons… D’abord, au premier degré : son apparence atypique, sa trajectoire particulière et, littéralement, ses taches (vitiligo). Bref, tout ce qu’il a dû développer comme aptitude pour ne pas être celui de qui on rit, mais celui qui fait rire ! Il jasera d’autodérision et de résilience, des forces qui, aussi nobles soient-elles, viennent parfois avec leur part de noirceur… Au niveau plus figuré, on en apprendra davantage sur ces gens, ces « taches », qui ont ralenti son parcours par leur manque d’ouverture, par les raccourcis simplistes… Enfin, il y a les marques, les « taches », que la vie laisse sur chacun, abordant les thèmes du doute, des remises en question et des épreuves. Une espèce de match de boxe humoristique avec une cadence de rires qui donne mal aux joues. À l’aide d’une écriture de haut niveau et d’une confiance à toute épreuve, Simon Delisle sort de ses gonds pour faire exploser la place à coup sûr. «La vie, ce n’est pas un cadeau. C’est une fatalité… Raison de plus pour lui rire en pleine face avec ce nouveau spectacle!» — Simon Delisle * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-simon-delisle#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-simon-delisle#Offer_1"}]},{"@id":"http://kg.footlight.io/resource/02e0aa9d-4319-420f-98a3-0418e6138f9f","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Simon Delisle"}],"http://schema.org/hasOccupation":[{"@value":"Humoriste"}],"http://schema.org/url":[{"@value":"http://simondelisle.ca/"}],"http://schema.org/sameAs":[{"@value":"http://kg.artsdata.ca/resource/K12-526"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/simon-delisle.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:35:48.315-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/simon-delisle.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-simon-delisle#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"35"}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04WW\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-simon-delisle#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"31"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-bobby-bazini","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-138"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-23T20:00:00-05:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/bobby-bazini.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Bobby Bazini"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/bobby-bazini.jpg"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/dyudAMo_EdM?si=drZkF0PI3l_o6zZa"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/bobby-bazini.html#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"Après avoir lancé l’album Pearl en 2023 et le EP Stone Of June l’an dernier, Bobby Bazini fera paraître l’automne prochain un premier album francophone, Seul au cinéma. S’en suivra une tournée du même nom qui parcourra le Québec. Un premier extrait, Élégante solitude, est déjà disponible. Une chanson inspirée du son «stoner» des années 1990. Elle traite de la solitude qui, parfois, ne rime pas toujours avec le sentiment de se sentir seul. Elle est précédée de Solitude, une pièce instrumentale. «Bien que les deux soient séparées, elles vont mieux ensemble, se mêlant parfaitement l’une dans l’autre, avec élégance, elles forment un tout… Tout comme la solitude et moi», explique Bobby. À propos de son album Seul au cinéma : «Bobby Bazini lance un premier album en français (et c'est superbe)» - Journal de Montréal «Un travail spectaculaire et méticuleux avec son complice à la réalisation, Connor Seidel (Half Moon Run, Les Sœurs Boulay), qui a également travaillé sur son précédent album. La formule est gagnante : Bazini la pousse au maximum de son potentiel.» - BP Arts Media «[...] il se met en danger en français sur un album complet pour la toute première fois avec Seul au cinéma. On y retrouve sa voix unique qui a fait son succès tout en découvrant une toute autre facette au chanteur.» - critiquedesalon.ca"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-bobby-bazini#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-bobby-bazini#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/bobby-bazini.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:24:23.031-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/bobby-bazini.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-bobby-bazini#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04WY\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"49"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-bobby-bazini#Offer_1","http://schema.org/price":[{"@value":"45"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-dave-gaudet","@type":["http://schema.org/Event"],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/uwQFruCADF0?si=FjabuZs_MpuFUqOK"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/dave-gaudet.jpg"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/d5cb0feb-55f2-426b-9f00-6d5c46ed7696"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-28T20:00:00-05:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/name":[{"@language":"fr","@value":"Dave Gaudet"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/dave-gaudet.html#WebPage"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/dave-gaudet.html"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/description":[{"@language":"fr","@value":"La carrière de l’humoriste à l’énergie débordante et au grand charisme Dave Gaudet est en plein essor depuis la pandémie. Il a vendu avec son dernier spectacle La Légende près de 50 000 billets et sa tournée s’est achevé le 12 avril 2025 à la Place Bell, le positionnant comme premier humoriste québécois à avoir présenté son spectacle dans cet aréna. Au terme de la représentation du 12 avril, il annoncera son nouveau spectacle IA (Intelligence Artificielle), un spectacle inspiré par et créé en partie avec l’intelligence artificielle, un concept bien aligné avec sa créativité et sa folie des grandeurs. Encore une fois, un spectacle unique. La légende ne fait que se concrétiser. Encore. * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-dave-gaudet#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-dave-gaudet#Offer_1"}]},{"@id":"http://kg.footlight.io/resource/d5cb0feb-55f2-426b-9f00-6d5c46ed7696","@type":["http://schema.org/Person"],"http://schema.org/sameAs":[{"@value":"https://www.facebook.com/davegaudetofficiel/"}],"http://schema.org/name":[{"@language":"fr","@value":"Dave Gaudet"}],"http://schema.org/hasOccupation":[{"@value":"Humoriste"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}],"http://schema.org/url":[{"@value":"https://davegaudet.com/"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/dave-gaudet.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:31:37.359-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/dave-gaudet.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-dave-gaudet#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04WZ\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"45"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-dave-gaudet#Offer_1","http://schema.org/price":[{"@value":"41"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-toc-toc","http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/toc-toc.html"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/theatre-hors-serie/toc-toc.jpg"}],"http://schema.org/name":[{"@language":"fr","@value":"Toc Toc"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-08T20:00:00-04:00"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/toc-toc.html#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/keywords":[{"@value":"theatre-hors-serie"}],"http://schema.org/description":[{"@language":"fr","@value":"Monarque vous invite à vivre une aventure hilarante et émouvante avec TOC TOC. Cette pièce de théâtre, ovationnée à travers le monde, met en scène six fascinants, patients chacun avec ses Troubles Obsessionnels Compulsifs (TOC), réunis dans la salle d’attente d’un célèbre docteur. On y rencontre des personnages hauts en couleur souffrant du Syndrome de Gilles de la Tourette (tics moteurs et sonores), de l’arithmomanie (obsession des chiffres), de la nosophobie (peur des maladies), du TOC de vérification, de la palilalie (répétition incessante) et du TOC d’ordre et de symétrie. Pierre-François Legendre, le metteur en scène, précise : «Le psychiatre étant en retard, nos six patients, qui pensaient tous avoir un rendez-vous avec lui, se retrouvent à improviser une thérapie de groupe. Leurs TOC s’entrechoquent dans des interactions aussi drôles que touchantes, révélant des êtres humains vulnérables et courageux. Entre éclats de rire, entraide inattendue, tensions explosives et réconciliations émouvantes, cette rencontre va bouleverser leurs vies et les amener à se comprendre, à se soutenir et, finalement, à s’apprécier». Texte Laurent Baffie Mise en scène Pierre-François Legendre Distribution Pierre Brassard, Josée Deschênes, Marie-Soleil Dion, Brigitte Lafleur, Marcel Leboeuf et Steve Gagnon Production Monarque"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-toc-toc#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-toc-toc#Offer_1"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-toc-toc#Offer_2"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-09T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-toc-toc#2026-03-08T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-toc-toc#2026-03-09T200000-0400"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/toc-toc.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:39:41.653-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/toc-toc.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-toc-toc#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"70"}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E011H\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-toc-toc#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"66"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-toc-toc#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"→ Abonnés théâtre*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"56"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-toc-toc#2026-03-08T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Toc Toc"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-08T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-toc-toc#2026-03-09T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Toc Toc"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-09T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-tristan-guay","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/soirees-cabaret/tristan-guay.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/name":[{"@language":"fr","@value":"Tristan Guay"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-17T20:00:00-04:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"soirees-cabaret"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/dad71250-8ec8-4b41-aa18-47eef056a04c"},{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/EzSQtdYxcDo?si=lP9R65v4-WDWqw39"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/tristan-guay.html"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/tristan-guay.html#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"Naviguant entre folk rock et pop, Tristan explore des sonorités surfs modernes avec une couleur qui lui est propre. L’artiste de Gatineau lance son premier titre Rien n’arrive pour rien en 2022 et participe au Festival International de la Chanson de Granby où il remporte le prix Taxi Promo - Disques Passeport. Il foule ensuite la scène de plusieurs concours à travers le Québec tel que Ma première place des arts, Le Tremplin de Dégelis et le Festival de la chanson de Saint-Ambroise. On le découvre plus amplement sur son tout premier EP Dans une autre vie (2023) où Tristan écrit le journal intime de la collision de deux vies parallèles, parfois contradictoires, mais qu’il cherche toujours à conjuguer. Avec les chansons qui composent le prochain album, Tristan se réincarne de morceau en morceau, vit ses vies multiples rapiécées en une seule: la sienne. * Spectacle présenté au foyer de la salle Odyssée / Admission générale / Formule debout / Bar en fonction / Places limitées"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-tristan-guay#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-tristan-guay#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/tristan-guay.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:39:33.459-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/tristan-guay.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-tristan-guay#Offer_0","http://schema.org/price":[{"@value":"28"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04YW\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-tristan-guay#Offer_1","http://schema.org/price":[{"@value":"24"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-pelch","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/c4be1ef2-2eac-4b1e-becd-ac29c6b55adb"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/pelch.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Pelch"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-04T20:00:00-04:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/kuXKLoQInPU?si=IUxsr2anGp4xaFeE"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/pelch.html#WebPage"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/pelch.jpg"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/description":[{"@language":"fr","@value":"Pelch présente un nouveau spectacle en formule trio, à la fois intime et vibrant. Sur scène, il dévoile de nouvelles chansons nées d’émotions récemment traversées dans un nouveau chapitre de vie, entre transformation et vulnérabilité, tout en revisitant les pièces qui ont marqué son parcours. Préparez-vous à une rencontre sincère et rythmée, où chaque chanson invite le public à entrer dans cet instant de vérité partagée. * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-pelch#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-pelch#Offer_1"}]},{"@id":"http://kg.footlight.io/resource/c4be1ef2-2eac-4b1e-becd-ac29c6b55adb","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"en","@value":"Pelch"}],"http://schema.org/hasOccupation":[{"@value":"Musical artist"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Musical artist"}],"http://schema.org/url":[{"@value":"https://www.pelchmusic.com/"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/pelch.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:18:20.514-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/pelch.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-pelch#Offer_0","http://schema.org/price":[{"@value":"35"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04XB\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-pelch#Offer_1","http://schema.org/price":[{"@value":"31"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-louis-morissette","@type":["http://schema.org/Event"],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/4KK6O9PR0AY?si=YPi__KCe4qNgiX4V"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/louis-morissette-derniere-chance.jpg"}],"http://schema.org/name":[{"@language":"fr","@value":"Louis Morissette"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-02T20:00:00-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/louis-morissette.html"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-288"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/louis-morissette.html#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"Maintenant dans la cinquantaine, Louis Morissette porte un regard humoristique et caustique sur son parcours, le pouvoir des échecs professionnels, et la société de performance. Avec une authenticité déroutante, il se met à nu et pose la question : après quoi on court? Première partie : Justin Morissette «Pari réussi.» - Tout un matin, ICI Première «Sous pression ne ressemble à aucun autre spectacle d'humour.» - La Presse «Louis Morissette [...] est dans le coeur, l'émotion, le rire, les larmes et c'est très payant.» - Katherine Verebely, ICI Première «Il nous parle vraiment de sa vie intime [...] et c'est drôle.» - 98,5 Montréal «Louis Morissette [...] propose le meilleur show d'humour que j'ai vu cette année!» - WKND «Un premier spectacle solo très efficace» - Rouge FM «Soirée réussie, de père en fils.» - TVA «Un excellent spectacle! Du beau, du bon et du très très drôle avec Louis Morissette [...] j'ai vraiment adoré la soirée.» - FM93 «Morissette est aussi un habile conteur qui sait mettre le public dans sa poche avec ses histoires et qui sait aussi le faire réfléchir.» - Le Soleil"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-louis-morissette#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-louis-morissette#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/louis-morissette.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:11:09.300-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/louis-morissette.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-louis-morissette#Offer_0","http://schema.org/price":[{"@value":"49"}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E01HF\u0026CodeSiteClient=00000V"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-louis-morissette#Offer_1","http://schema.org/price":[{"@value":"45"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-verdict-2","http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/gntjA_1laSo?si=5xsH_bL6Td0wwZS5"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/rendez-vous-theatre/verdict-2.jpg"}],"http://schema.org/name":[{"@language":"fr","@value":"Verdict 2"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-17T20:00:00-04:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/rendez-vous-theatre/verdict-2.html"}],"http://schema.org/keywords":[{"@value":"rendez-vous-theatre"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/rendez-vous-theatre/verdict-2.html#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"Après l’immense succès de Verdict 1 présenté en tournée 2022, nous sommes ravis de revenir sur scène pour un deuxième volet mettant en vedette Paul Doucet et Sonia Vachon dans de nouvelles plaidoiries abordant divers débats de société. Que l’on pense à la détresse des proches aidants, aux cas d’atteinte à la réputation ou encore à la violence conjugale, la société québécoise a débattu lorsque ces causes ont été entendues en justice. Ce spectacle offre une expérience interactive, enrichie par une mise en contexte multimédia, où le public est invité à se positionner lors du dernier procès en rendant un verdict, comme le feraient les membres d’un jury. Étape ultime d’un procès, les plaidoiries sont imagées, percutantes et touchantes. Un regard poignant et éclairé sur ce qui nous définit comme société. Adaptation Nathalie Roy et Yves Thériault Direction artistique Pierre Bernard Mise en scène Michel-Maxime Legault Distribution Paul Doucet et Sonia Vachon Production Agents Doubles"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-verdict-2#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-verdict-2#Offer_1"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-verdict-2#Offer_2"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-verdict-2#Offer_3"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-18T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-verdict-2#2026-04-17T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-verdict-2#2026-04-18T200000-0400"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/rendez-vous-theatre/verdict-2.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:17:55.166-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/rendez-vous-theatre/verdict-2.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-verdict-2#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E01PA\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"54"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-verdict-2#Offer_1","http://schema.org/price":[{"@value":"50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-verdict-2#Offer_2","http://schema.org/price":[{"@value":"49"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"30 ans et - / 60 ans et +"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-verdict-2#Offer_3","http://schema.org/price":[{"@value":"45"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée 30 ans et - / 60 ans et +"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-verdict-2#2026-04-17T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Verdict 2"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-17T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-verdict-2#2026-04-18T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Verdict 2"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-18T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-marie-josee-lord","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-488"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/name":[{"@language":"fr","@value":"Marie-Josée Lord"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-29T15:00:00-04:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/marie-josee-lord.jpg"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/marie-josee-lord.html"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/marie-josee-lord.html#WebPage"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/t9Yhdu_551M?si=BQsHqiAQwBRdt1Gp"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/description":[{"@language":"fr","@value":"Avec LES CHANTS DE MON PAYS, Marie-Josée Lord partage avec le public les musiques avec lesquelles elle a grandi. Dans la maison familiale, c’était les grandes chansons québécoises qui tournaient sous l'aiguille du tourne-disque du salon. Gilles Vigneault, Félix Leclerc, Diane Juster, Monique Leyrac chantant les poèmes de Nelligan, Claude Léveillée, Claude Dubois, Robert Charlebois, Sylvain Lelièvre et bien d'autres. Chaque mot de ces chansons devenait alors une histoire inspirant des images qui la transportaient dans un univers de rêverie et d'évasion. Des histoires de guerre, de folie, de printemps, d'un pays à construire, d'une langue à protéger, d'un avenir meilleur pour nos enfants ! Histoires de coeur, de jeune fille à marier, de souvenirs d'enfance, d'histoires d'antan, de veillées et de vieux pianos, histoire de quartier et des gens qui nous entourent... Ces grands poètes et paroliers, elle les porte en elle, ils font partie de sa vie, de son quotidien. C'est tout ça LES CHANTS DE MON PAYS! MARIE-JOSÉE LORD est accompagnée d'un pianiste et d'un quatuor à cordes."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-marie-josee-lord#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-marie-josee-lord#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/marie-josee-lord.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:13:59.326-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/marie-josee-lord.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-marie-josee-lord#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04X9\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"53"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-marie-josee-lord#Offer_1","http://schema.org/price":[{"@value":"49"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-michel-rivard","http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/michel-rivard.html"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-187"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/name":[{"@language":"fr","@value":"Michel Rivard"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-06-03T20:00:00-04:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/michel-rivard.html#WebPage"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/description":[{"@language":"fr","@value":"Durant les deux semaines (symboliques) que dure la magnifique floraison du magnolia qui illumine la fenêtre de sa chambre, «l'homme qui me ressemble» (74 ans) se questionne sur sa vie, avant et après la mort, en confrontant ses doutes et ses certitudes. Entre un rendez-vous médical et le choix d'une urne dans un catalogue, entre un premier amour et une marche en forêt avec son petit-fils de sept ans, «l'homme qui me ressemble» monologue, poétise et chante sa recherche du «plus-grand-que-soi». Texte, chansons et interprétation Michel Rivard Musiciens.nes François Richard, Blanche Baillargeon et Guillaume Bourque Mise en scène Marie-Thérèse Fortin Production La maison fauve Créée en collaboration avec le CTD'A"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/michel-rivard-2026.jpg"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-michel-rivard#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-michel-rivard#Offer_1"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-06-04T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-michel-rivard#2026-06-03T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-michel-rivard#2026-06-04T200000-0400"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/michel-rivard.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:21:33.318-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/michel-rivard.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-michel-rivard#Offer_0","http://schema.org/price":[{"@value":"56"}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E014A\u0026CodeSiteClient=00000V"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-michel-rivard#Offer_1","http://schema.org/price":[{"@value":"52"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-michel-rivard#2026-06-03T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Michel Rivard"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-06-03T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-michel-rivard#2026-06-04T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Michel Rivard"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-06-04T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-hebert","http://schema.org/name":[{"@language":"fr","@value":"Pierre Hébert"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-23T20:00:00-04:00"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/pierre-hebert.html"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/pierre-hebert.jpg"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/pierre-hebert.html#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"Tous les matins, on se lève avec le même objectif : que notre vie soit grandiose! On en rêve, on s’épuise, on se dénature, on se casse la gueule et parfois, pendant un court instant, on y arrive. C’est à ce moment qu’on réalise que tout ça en valait la peine. Tout ce chemin, tous ces efforts pour vivre ce moment parfait qui veut tout dire, avant que tout se termine pour de bon. Quatre ans après la fin de la tournée Le goût du risque, Pierre Hébert reprend enfin la route avec son nouveau spectacle: Grandiose. On y retrouve l’humoriste souvent moqueur, parfois touchant, qui porte un regard plein d’humour et d’émerveillement sur le monde qui l’entoure. Lauréat au Gala Les Olivier 2025: Numéro d'humour de l'année"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-hebert#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-hebert#Offer_1"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-15T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-hebert#2026-03-23T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-hebert#2026-03-24T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-hebert#2026-04-29T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-hebert#2026-06-01T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-hebert#2026-10-15T200000-0400"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/pierre-hebert.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:30:29.066-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/pierre-hebert.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-hebert#Offer_0","http://schema.org/price":[{"@value":"50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E00XY\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-hebert#Offer_1","http://schema.org/price":[{"@value":"46"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-hebert#2026-03-23T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Pierre Hébert"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-23T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-hebert#2026-03-24T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Pierre Hébert"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-24T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-hebert#2026-04-29T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Pierre Hébert"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-29T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-hebert#2026-06-01T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Pierre Hébert"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-06-01T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-hebert#2026-10-15T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Pierre Hébert"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-15T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-maude-landry","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-493"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Maude Landry"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/Et-jJQrw50M?si=vptmAHIhtvBE3pGy"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-21T20:00:00-04:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/maude-landry.html"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/maude-landry-2026.jpg"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/additionalType":[{"@id":"http://kg.artsdata.ca/resource/PerformingArtsEvent"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/description":[{"@language":"fr","@value":"Dans un monde où il faut se démarquer tout en suivant les tendances, Maude Landry se demande pourquoi elle n’a jamais fait partie de la gang des cools. C’est ce qui la rend attachante. Décontractée et pince-sans-rire, elle pose son regard taquin sur elle-même et sur la société, en passant délibérément par quatre chemins. Un spectacle moderne et assumé, pendant lequel on se dit parfois «C’est niaiseux!... Mais fallait y penser». Maude vous invite dans un périple au cœur de ses réflexions, ses doutes et ses déboires, pour que vous sortiez de la salle en disant «C’était trop cool!» * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/maude-landry.html#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-maude-landry#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-maude-landry#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/maude-landry.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:28:01.100-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/maude-landry.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-maude-landry#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"36"}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04O3\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-maude-landry#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"32"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-silvi-tourigny-en-feu","@type":["http://schema.org/Event"],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/name":[{"@language":"fr","@value":"Silvi Tourigny"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K2-2206"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/silvi-tourigny.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"L’humoriste Silvi Tourigny s'apprête à brûler les planches avec son premier spectacle solo, En feu! Ce one-woman-show est une plongée hilarante dans son vécu, mais surtout dans ses envies, celles d’une femme libérée et assumée. Au cours des dernières années, il s'en est passé des choses dans sa vie : une maison, un enfant, une moto, une séparation, une autre maison, des rencontres douteuses, une autre relation et c’est sans compter sa passion beaucoup trop intense pour les animaux! Silvi et sa fidèle acolyte, Carole, sont plus enflammées que jamais et prêtes à non seulement vous faire rire, mais également à vous donner le goût de danser! Avec son naturel désarmant, la rouquine fait souffler un vent d'énergie et d'humour qui séduit à grands éclats de rire. Comme dirait Carole : «Pas besoin de t’avertir que tu vas avoir du fun parce que Silvi est EN FEU!!!» * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7 «Assumée, charismatique et surtout hilarante, Silvi Tourigny est un modèle à suivre pour les prochaines générations d'humoristes au Québec.» - Noovo Moi «Silvi Tourigny brille par sa lumineuse légèreté... comme du comfort food humoristique, mais au sens le plus positif de l'expression.» - La Presse «Silvi Tourigny est beaucoup plus que l'interprète de Carole» - Hollywood PQ «Accueillie comme une véritable pop-star américaine, Silvi Tourigny a brûlé les planches... Un spectacle de stand-up bien ficelé.» - Journal de Montréal «Un spectacle mémorable... son humour fin, sa présence et son charisme en font une humoriste à découvrir absolument.» - BP Arts Média «De l'humour du quotidien qui a fait éclater de rire la foule! Et attendez de la voir danser! Du pur bonheur!» - LCN «Je suis fan de carole, qui fait la première partie... ça me fait énormément rire cette femme qui est morte par en-dedans.» - 98.5"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-09T20:00:00-05:00"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/u3mhhvw24O8?si=eVfu5OTfFFyX6WOM"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/silvi-tourigny-en-feu.html"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/silvi-tourigny-en-feu.html#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-silvi-tourigny-en-feu#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-silvi-tourigny-en-feu#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/silvi-tourigny-en-feu.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-08T16:11:25.551-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/silvi-tourigny-en-feu.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-silvi-tourigny-en-feu#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E052I\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/price":[{"@value":"39"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-silvi-tourigny-en-feu#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"35"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_country-folk-dave-fenley","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/country-folk/dave-fenley.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Dave Fenley"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/e6d56f73-5349-4077-b0e1-87bb18713a22"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/country-folk/dave-fenley.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/country-folk/dave-fenley.html#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-28T20:00:00-04:00"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/ctc7foW3t0U?si=NDDWK_917N9YX1k9"}],"http://schema.org/keywords":[{"@value":"country-folk"}],"http://schema.org/description":[{"@language":"fr","@value":"**Spectacle présenté en anglais** Avec près de 40 000 billets vendus et plus de 50 représentations à travers le Québec, le chouchou du public québécois est de retour avec un tout nouveau spectacle : TRIO. Révélé en 2022 grâce à l’émission En direct de l’Univers, Dave Fenley charme par sa voix puissante et son style folk-country authentique. Pour cette nouvelle aventure sur scène, il s’entoure de sa mentore Lise Dion à la direction artistique, ainsi que des talentueux musiciens québécois Gabriella Laberge et John Antony Gagnon Robinette. Un spectacle riche en émotions et parsemé de surprises, à ne pas manquer. Veuillez noter que l'artiste pourrait inviter les spectateurs à se lever pendant le spectacle. Nous préférons vous en avertir."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_country-folk-dave-fenley#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_country-folk-dave-fenley#Offer_1"}]},{"@id":"http://kg.footlight.io/resource/e6d56f73-5349-4077-b0e1-87bb18713a22","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"en","@value":"Dave Fenley"}],"http://schema.org/hasOccupation":[{"@value":"Singer"}],"http://schema.org/url":[{"@value":"https://www.davefenley.com/"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Singer"}],"http://schema.org/sameAs":[{"@value":"https://www.facebook.com/davefenleylovesyou"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/country-folk/dave-fenley.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:11:00.751-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/country-folk/dave-fenley.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_country-folk-dave-fenley#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E052O\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"58"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_country-folk-dave-fenley#Offer_1","http://schema.org/price":[{"@value":"54"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-rita-baga","@type":["http://schema.org/Event"],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/9475525c-10fa-4a53-b89b-8c7d9ba68726"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K13-191"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/name":[{"@language":"fr","@value":"Rita Baga"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-25T20:00:00-05:00"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/description":[{"@language":"fr","@value":"Rita Baga, la plus célèbre drag queen québécoise qui s’illustre aussi en Europe, revient chez vous avec sa toute nouvelle tournée musicale SPRAYNET! Rita a conquis le Québec avec Créature, la plus longue tournée solo d’une drag en Amérique, vue par 75 000 spectacteur.trices et récipiendaire d’un Olivier. En 2025, l’artiste drag présente un nouveau spectacle bien différent, mais tout autant flamboyant. Accompagnée par son band sous la direction musicale de Jacob Roberge, SPRAYNET vous convie à un véritable party des années 80. Rita revisitera les plus grands succès de cette décennie qu’elle adore, en plus d’interpréter les chansons de son premier album original à paraître en 2025. Ce spectacle met en valeur la voix puissante de Rita, que plusieurs ont découvert à Chanteurs Masqués. La tournée SPRAYNET est évidemment accentuée de costumes à couper le souffle, de l’humour et du charisme propres à Rita! Préparez votre plus imposante coiffure pour une soirée éblouissante où vous ne pourrez pas vous empêcher de rire, chanter et danser! Pour publics de tous âges. Achetez vite vos billets, nombre de représentations limité. «Véritable force de la nature, Rita Baga livre hit après hit, enfilant les plaisirs coupables avec tout l'aplomb et l'humour qu'on lui connaît. Un spectacle irrésistible, mené de main de maître par une artiste au somment de son art.» - Bruno Lapointe, Journal de Montréal «Les années 80 à l'honneur, version Rita! Du chant, des paillettes et beaucoup de plaisirs coupables!» - Annie Roy, BP Arts Média «Rita Baga a le sens de la scène.» - Marie-Claude Doyle, Échos Vedettes «On s'est levé, on a dansé et chanté avec elle. Impossible d'y résister.» - Sylvie Tardif, MAtv «Baga s'offre possiblement l'un des trips d'une vie en se prenant pour Madonna le temps d'une tournée. Elle y saupoudre quelques pièces de son premier album, FLASH [...] notamment l'excellente Comme ça me chante, interprétée avec l'ensemble vocal Extravaganza. Un rythme soutenu et un très beau texte, une joyeuse ode à l'unicité.» - Marie-Josée R. Roy, Hollywood PQ"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/varietes/rita-baga-2025.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/varietes/rita-baga.html#WebPage"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/varietes/rita-baga.html"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"varietes"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-rita-baga#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-rita-baga#Offer_1"}]},{"@id":"http://kg.footlight.io/resource/9475525c-10fa-4a53-b89b-8c7d9ba68726","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Variétés"}],"http://www.w3.org/2004/02/skos/core#closeMatch":[{"@id":"http://kg.artsdata.ca/resource/VarietyShow"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/varietes/rita-baga.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:11:26.555-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/varietes/rita-baga.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-rita-baga#Offer_0","http://schema.org/url":[{"@value":"https://www.ovation.ca/00000V/fr/Event/?seriesId=54F8D00B%2D7FF9%2D458D%2DB611%2D2CE460218358\u0026venueId=0E%2D0E0001"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"44"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-rita-baga#Offer_1","http://schema.org/price":[{"@value":"40"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-ge-sri-lanka","@type":["http://schema.org/Event"],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/ge-sri-lanka.html#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/656dca7a-5549-494c-acbf-8c05662b23bf"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-22T13:30:00-04:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/autres-evenements/ge-sri-lanka.jpg"}],"http://schema.org/name":[{"@language":"fr","@value":"Les Grands Explorateurs"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K10-427"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/ge-sri-lanka.html"}],"http://schema.org/keywords":[{"@value":"autres-evenements"}],"http://schema.org/recordedIn":[{"@value":"https://player.vimeo.com/video/1076126782?badge=0\u0026autopause=0\u0026player_id=0\u0026app_id=58479"}],"http://schema.org/description":[{"@language":"fr","@value":"Cette île resplendissante dont l’histoire culturelle est aussi importante que passionnante se caractérise par son abondante végétation, la beauté de ses plages bordées de cocotiers et ses ressources minérales et végétales. Son insularité en fait à la fois un sanctuaire et un refuge pour divers éléments de la culture indienne préservés ainsi qu’un pays ouvert sur le monde."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-ge-sri-lanka#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-ge-sri-lanka#Offer_1"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-ge-sri-lanka#Offer_2"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/ge-sri-lanka.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:24:55.658-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/ge-sri-lanka.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-ge-sri-lanka#Offer_0","http://schema.org/price":[{"@value":"33.92"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E050C\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-ge-sri-lanka#Offer_1","http://schema.org/price":[{"@value":"31.04"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"60 ans et +"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-ge-sri-lanka#Offer_2","http://schema.org/price":[{"@value":"22.42"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"25 ans et moins"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-jo-cormier","@type":["http://schema.org/Event"],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/jo-cormier.html"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-13T20:00:00-05:00"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/b0b27b7c-481e-4041-bc62-40f558274f65"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/name":[{"@language":"fr","@value":"Jo Cormier"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/jo-cormier-2026.jpg"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/SmltVzFpPro?si=GWN7oQcRwt0ZZHMI"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/jo-cormier.html#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"L’incomparable humoriste Jo Cormier débarque avec son deuxième one-man-show : MACHINE. Alors que dans son premier spectacle il comparait l’humain à un animal, cette fois-ci il pousse la réflexion sur l’être humain encore plus loin, toujours avec son regard unique. Ou comme dirait Jo: un regard donné par un genre de gars fin trentaine, la bédaine, qui pense avoir 23 ans mentalement, mais qui en a 4 en réalité et qui aime regarder le soleil le plus longtemps possible. Avec la technologie, on optimise nos vies comme si on était des ordinateurs, mais est-ce qu’on se demande encore c’est quoi, être un humain? Dans ce spectacle plus personnel et plus honnête, Jo se montre tout aussi vulnérable que drôle alors qu’il se questionne à savoir si oui ou non il est humain. MACHINE est un spectacle simple, amusant, nono. Un spectacle où personne n’a besoin d’être parfait, seulement humain. * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-jo-cormier#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-jo-cormier#Offer_1"}]},{"@id":"http://kg.footlight.io/resource/b0b27b7c-481e-4041-bc62-40f558274f65","@type":["http://schema.org/Person"],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}],"http://schema.org/name":[{"@language":"fr","@value":"Jo Cormier"}],"http://schema.org/hasOccupation":[{"@value":"Humoriste"}],"http://schema.org/url":[{"@value":"https://jocormier.ca/"}],"http://schema.org/sameAs":[{"@value":"http://kg.artsdata.ca/resource/K5-212"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/jo-cormier.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:27:12.532-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/jo-cormier.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-jo-cormier#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E0519\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/price":[{"@value":"40"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-jo-cormier#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"36"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_sorties-famille-le-songe-dune-nuit-dete","@type":["http://schema.org/Event"],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/sorties-famille/le-songe-dune-nuit-dete.html#WebPage"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/sorties-famille/le-songe-dune-nuit-dete-2025.jpg"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/sorties-famille/le-songe-dune-nuit-dete.html"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"sorties-famille"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-12T19:00:00-04:00"}],"http://schema.org/name":[{"@language":"fr","@value":"Le songe d'une nuit d'été"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/q3HOBvWHt5o?si=YwSGo1UmkP9Le32X"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/fbb87285-64ba-4bae-9bec-ae259276fca6"},{"@id":"http://kg.footlight.io/resource/2fa05002-967b-43c2-b5cb-fc93a25edcb1"}],"http://schema.org/description":[{"@language":"fr","@value":"Ballet Jörgen présente la classique comédie de William Shakespeare avec une touche canadienne, accompagnée de la célèbre partition de Mendelssohn. Le songe d'une nuit d'été se déroule dans le mystérieux nord du Canada et captivera les spectateurs de tous âges. Ce ballet est une histoire d'amour, souvent guidée par une magie mal interprétée, mise en mouvement par le farceur espiègle Puck."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_sorties-famille-le-songe-dune-nuit-dete#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_sorties-famille-le-songe-dune-nuit-dete#Offer_1"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_sorties-famille-le-songe-dune-nuit-dete#Offer_2"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/sorties-famille/le-songe-dune-nuit-dete.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:35:23.912-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/sorties-famille/le-songe-dune-nuit-dete.html"}]},{"@id":"http://kg.footlight.io/resource/fbb87285-64ba-4bae-9bec-ae259276fca6","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"sorties-famille"}]},{"@id":"http://kg.footlight.io/resource/2fa05002-967b-43c2-b5cb-fc93a25edcb1","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Danse"}],"http://www.w3.org/2004/02/skos/core#closeMatch":[{"@id":"http://kg.artsdata.ca/resource/DancePerformance"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_sorties-famille-le-songe-dune-nuit-dete#Offer_0","http://schema.org/price":[{"@value":"35"}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04XC\u0026CodeSiteClient=00000V"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_sorties-famille-le-songe-dune-nuit-dete#Offer_1","http://schema.org/price":[{"@value":"31"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_sorties-famille-le-songe-dune-nuit-dete#Offer_2","http://schema.org/price":[{"@value":"30"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"étudiants"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-2freres","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K10-435"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/2freres.jpg"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/hebqUgYTBHo?si=aavjFkcxJxXqtcru"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/2freres.html#WebPage"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-02T20:00:00-04:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/2freres.html"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/description":[{"@language":"fr","@value":"Le duo le plus aimé du Québec est de retour sur scène accompagné de leurs quatre musiciens afin de présenter un tout nouveau spectacle! Regroupant des pièces de leur nouvel album Science humaine, on y retrouvera également leurs plus grands succès. Avec des musiques accrocheuses et des textes rassembleurs, laissez-vous charmer par leur complicité et leurs magnifiques harmonies vocales. Les 2Frères, c’est aussi plus de 275 000 albums vendus en carrière, 70 millions d’écoutes sur les plateformes en ligne et 14 chansons #1 à la radio! Lauréats au Gala de l'ADISQ 2025 dans la catégorie : Groupe ou duo de l'année"}],"http://schema.org/name":[{"@language":"fr","@value":"2Frères"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-2freres#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-2freres#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/2freres.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:23:23.204-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/2freres.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-2freres#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E00Z1\u0026CodeSiteClient=00000V"}],"http://schema.org/price":[{"@value":"49"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-2freres#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"45"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-rachelle-elie","@type":["http://schema.org/Event"],"http://schema.org/additionalType":[{"@id":"http://kg.artsdata.ca/resource/PerformingArtsEvent"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/rachelle-elie-2026.jpg"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/4e8C21_u5Ts?si=hrsO4ooQ3F44kf6u"}],"http://schema.org/name":[{"@language":"fr","@value":"Rachelle Elie"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-16T20:00:00-05:00"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/rachelle-elie.html"}],"http://schema.org/description":[{"@language":"fr","@value":"Rachelle Elie, bête de scène à paillettes et humoriste anglophone préférée des Québécois, présente Lâchée Lousse, son tout premier one-woman show en français! Dans ce spectacle haut en couleur, Rachelle célèbre la liberté qu’elle a trouvée dans sa nouvelle vie au Québec, sa passion pour la culture d’ici et son affection pour nos expressions savoureuses. Avec humour et franchise, elle nous parle de sa vie de femme nouvellement célibataire, de ses aventures amoureuses et de la mission (pas toujours simple!) d’élever des garçons pour en faire des hommes bons. Une soirée éclatée, pleine de rires et de pur plaisir, à l’image de Rachelle! * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/rachelle-elie.html#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-rachelle-elie#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-rachelle-elie#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/rachelle-elie.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-14T16:23:37.759-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/rachelle-elie.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-rachelle-elie#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"29"}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04OF\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-rachelle-elie#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"25"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-gala-comedie-star","@type":["http://schema.org/Event"],"http://schema.org/description":[{"@language":"fr","@value":"Notre Gala Comédie Star de retour à Gatineau! Un classique, digne des plus prestigieux galas d’humour. Le seul Gala annuel en programmation régulière qui met en vedette nos grands de l’humour. Gala Comédie Star, c’est 5 humoristes chevronnés qui viennent à tour de rôle présenter un numéro de leur propre chef! Une liberté inégalée qui permet d’assurer surprises et émotions! Pour l’édition 2026, l’animateur STEPHANE FALLU recevra 4 humoristes de renom. RÉAL BÉLAND, MEHDI BOUSAIDAN, RACHELLE ELIE et OLIVIER MARTINEAU! Faites vite, les places pour notre Gala partent rapidement!"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/gala-comedie-star-2026.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-28T20:00:00-04:00"}],"http://schema.org/name":[{"@language":"fr","@value":"Gala Comédie Star"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/gala-comedie-star.html#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/gala-comedie-star.html"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-gala-comedie-star#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-gala-comedie-star#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/gala-comedie-star.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:25:19.773-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/gala-comedie-star.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-gala-comedie-star#Offer_0","http://schema.org/price":[{"@value":"49"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04XI\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-gala-comedie-star#Offer_1","http://schema.org/price":[{"@value":"45"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-dany-bedar","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/dany-bedar.html"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/9415f32c-aca6-4585-a510-8f780a69374e"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/dany-bedar.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/dany-bedar.html#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/vuhV5hQmqdY?si=On_5XHZ-A6TcaTMA"}],"http://schema.org/description":[{"@language":"fr","@value":"Avec plus de 500 000 albums vendus et une carrière s'étalant sur presque trente années, Dany Bédar nous offre sur scène ses plus grands succès pour ravir ses nombreux fans. À ses succès bien connus tels que Tu m'manques, Faire la paix avec l'amour, Écoute-moi donc et Y'a du monde s'ajoutent des chansons qu'il a écrites pour d'autres artistes et qu'il revisite sur son plus récent album, Le meilleur des autres, lancé au début de 2025. D'autres grands succès radiophoniques tels que Comment j'pourrais te le dire écrite pour Marie-Chantale Toupin, J'te laisse partir (Laurence Jalbert) et Un ange qui passe (Annie Villeneuve). Avec huit albums à son actif en tant qu'artiste solo, Dany Bédar a conquis le public avec son deuxième opus, Écoute-moi donc, lancé en 2004 qui lui a valu le Félix de l'album de l'année/pop-rock en 2005, le prix Juno pour l'album francophone de l'année en 2005 ainsi que le Félix de l'interprète masculin de l'année en 2005 et en 2006. C'est en toute simplicité, armé seulement de ses guitares que Dany Bédar vous convie à une soirée magique afin de partager les grands succès de sa carrière."}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-04T20:00:00-05:00"}],"http://schema.org/name":[{"@language":"fr","@value":"Dany Bédar"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-dany-bedar#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-dany-bedar#Offer_1"}]},{"@id":"http://kg.footlight.io/resource/9415f32c-aca6-4585-a510-8f780a69374e","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Dany Bédar"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Artiste musical"}],"http://schema.org/sameAs":[{"@value":"https://www.facebook.com/danybedar/"},{"@value":"http://www.wikidata.org/entity/Q3015774"}],"http://schema.org/hasOccupation":[{"@value":"Chanteur"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/dany-bedar.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:14:49.765-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/dany-bedar.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-dany-bedar#Offer_0","http://schema.org/price":[{"@value":"40"}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E0510\u0026CodeSiteClient=00000V"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-dany-bedar#Offer_1","http://schema.org/price":[{"@value":"36"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-festival-du-film-de-montagne-de-banff","http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"autres-evenements"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/656dca7a-5549-494c-acbf-8c05662b23bf"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/festival-du-film-de-montagne-de-banff.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Festival du film de montagne de Banff"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-11T13:00:00-05:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/festival-du-film-de-montagne-de-banff.html#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"50e édition! La Tournée mondiale du Festival du film de montagne de Banff est de retour! En janvier et février 2026, la tournée marque 50 ans de pur dépassement. Vibrez devant des images spectaculaires où des hommes et des femmes d’exception poussent souvent leurs exploits à la limite du possible. Une expérience cinématographique incontournable qui célèbre la passion et l’audace. La programmation finale sera révélée en novembre 2025. Avec 23 villes visitées, la tournée au Québec est l’une des plus importantes du World Tour qui se déplace dans plus de 40 pays. Cette année, près de 600 films ont été soumis au jury, un record de tous les temps. Du nombre, environ 80 sont présentés aux festivaliers. Informations pratiques : La soirée dure trois heures au total, entracte et tombola compris. Le programme est le même pour chaque projection. Les films sont présentés en version originale sous-titrée en français afin de préserver leur authenticité. De nombreux prix sont tirés au sort après l'entracte. Une exposition photographique sur un thème différent chaque année accompagne la tournée. www.banffquebec.ca"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/autres-evenements/festival-du-film-de-montagne-de-banff-2026.jpg"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-festival-du-film-de-montagne-de-banff#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-11T19:00:00-05:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-festival-du-film-de-montagne-de-banff#2026-01-11T130000-0500"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-festival-du-film-de-montagne-de-banff#2026-01-11T190000-0500"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/festival-du-film-de-montagne-de-banff.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-10T16:42:20.898-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/festival-du-film-de-montagne-de-banff.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-festival-du-film-de-montagne-de-banff#Offer_0","http://schema.org/price":[{"@value":"29.50"}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E00Y3\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-festival-du-film-de-montagne-de-banff#2026-01-11T130000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Festival du film de montagne de Banff"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-11T13:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-festival-du-film-de-montagne-de-banff#2026-01-11T190000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Festival du film de montagne de Banff"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-11T19:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-pb-rivard","@type":["http://schema.org/Event"],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/name":[{"@language":"fr","@value":"PB Rivard"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/443cb4b4-6463-40d2-a5d9-63e540c3a7c2"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"soirees-cabaret"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/pb-rivard.html"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/2o4_fJvf7QY?si=W9eICyjv2ScJxdn1"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-27T20:00:00-05:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/pb-rivard.html#WebPage"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/soirees-cabaret/pb-rivard.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"PB Rivard présente son nouveau spectacle Chat de Ruelle. Avec près de 2500 spectacles en carrière partout au Québec, au Canada et en Europe, une 50aines d'apparitions dans des galas d’humour à la télévision et récipiendaire de 3 Oliviers comme auteur, PB Rivard combine habilement une plume redoutable à un sourire candide qui cache deux canines bien affûtées… À la télévision, on a pu le voir dans les émissions Contre-Offre, Entrée Principale, Les 5 prochains, Top Dogs et Arrange-toi avec ça. Il a également assuré la première partie du spectacle Déplaire de Laurent Paquin et assure actuellement celles de Olivier Martineau et Mathieu Cyr. * Formule cabaret au foyer de la salle Odyssée / Ambiance intime et conviviale / Bar en fonction / Places limitées"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/dad71250-8ec8-4b41-aa18-47eef056a04c"},{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-pb-rivard#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-pb-rivard#Offer_1"}]},{"@id":"http://kg.footlight.io/resource/443cb4b4-6463-40d2-a5d9-63e540c3a7c2","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"PB Rivard"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}],"http://schema.org/hasOccupation":[{"@language":"fr","@value":"Humoriste"}],"http://schema.org/url":[{"@value":"https://www.pbrivard.com/"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/pb-rivard.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-26T16:27:16.027-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/pb-rivard.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-pb-rivard#Offer_0","http://schema.org/price":[{"@value":"29"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04X7\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-pb-rivard#Offer_1","http://schema.org/price":[{"@value":"25"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mariana-mazza-foie-gras","@type":["http://schema.org/Event"],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/name":[{"@language":"fr","@value":"Mariana Mazza"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-382"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-19T20:00:00-04:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/mariana-mazza-foie-gras.html#WebPage"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/4EZ8q23RTNY?si=dOeRwRh6cjEmmB--"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/mariana-mazza-foie-gras.html"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/mariana-mazza-2025.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"Pour son troisième spectacle, Foie Gras, Mariana Mazza décide de s’exprimer avec sa tête et son cœur… pour faire changement. Dans ce one-woman-show bien personnel, la jeune fille qui a grandi à Montréal-Nord, maintenant rendue une femme embourgeoisée qui vit à Saint-Lambert avec ses caniches et ses toiles invendues, se montre aussi brillante qu’hilarante. Étant habituée à dire des choses uniquement pour provoquer un rire, elle souhaite aller plus loin cette fois et faire un coming out au public : elle est émotive et vulnérable. Le chihuahua sur le Red Bull est officiellement remplacé par le tank au cœur de porcelaine. Comme elle le dit si bien : «C’est moi la meilleure tabarnak!» Oui, Mariana a changé. Mais à quel point? En nomination au Gala Les Olivier 2025: Olivier de l’année «Mariana Mazza signe son spectacle le plus abouti, et aussi le plus révélateur» - Dominic Tardif, La Presse «Mariana c’est la reine… elle est féroce, extraordinaire, drôle, touchante, attachante» - Benoit Dutrizac, QUB radio «Mariana m’a fait rire et pleurer, parfois dans la même minute. J’ai ADORÉ chaque seconde de ce spectacle inoubliable.» - Sophie Durocher, Journal de Montréal"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mariana-mazza-foie-gras#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mariana-mazza-foie-gras#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/mariana-mazza-foie-gras.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:15:17.497-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/mariana-mazza-foie-gras.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mariana-mazza-foie-gras#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04N3\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"52"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mariana-mazza-foie-gras#Offer_1","http://schema.org/price":[{"@value":"48"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-mon-jour-de-chance","http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/u2wGiYfa91k?si=U0PCvNdAZxMBiVvq"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/theatre-hors-serie/mon-jour-de-chance-2026.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/mon-jour-de-chance.html#WebPage"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-07-16T20:00:00-04:00"}],"http://schema.org/name":[{"@language":"fr","@value":"Mon jour de chance"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"theatre-hors-serie"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/mon-jour-de-chance.html"}],"http://schema.org/description":[{"@language":"fr","@value":"Le temps d’une fin de semaine, Sébastien retrouve ses amis d’enfance. Ensemble, ils replongent dans leurs souvenirs, à l’époque où ils prenaient toutes leurs décisions en lançant un dé. Une méthode ludique, anodine… ou lourde de conséquences! Sébastien, lui, n’a jamais oublié ce fameux soir où il a lancé un quatre. Il en est persuadé : un six aurait tout changé et sa vie aurait été meilleure. Et si, des années plus tard, il pouvait relancer le dé et obtenir enfin le fameux six? À sa grande surprise, le résultat tant attendu finit par arriver... mais rien ne se déroule comme prévu. Ses multiples tentatives l’entraînent dans des réalités alternatives où tout dérape ! S’enchaînent alors des retournements de situation inattendus, loufoques et absurdes. Mon jour de chance propose, avec humour et intelligence, une réflexion sur le destin, les hasards de la vie et la possibilité de tout recommencer. Une pièce de Patrick Haudecoeur et Gérald Sibleyras Mise en scène Michel Charette Adaptation François Chénier et Michel Charette Distribution Jeff Boudreault, Anne-Marie Binette, François-Xavier Dufour, Hugues Frenette et Élodie Grenier"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-mon-jour-de-chance#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-mon-jour-de-chance#Offer_1"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-mon-jour-de-chance#Offer_2"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-mon-jour-de-chance#Offer_3"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-07-24T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-mon-jour-de-chance#2026-07-16T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-mon-jour-de-chance#2026-07-17T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-mon-jour-de-chance#2026-07-18T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-mon-jour-de-chance#2026-07-24T200000-0400"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/mon-jour-de-chance.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:33:33.355-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/mon-jour-de-chance.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-mon-jour-de-chance#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"58"}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E01Q2\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-mon-jour-de-chance#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"54"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-mon-jour-de-chance#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"groupes de 4 personnes et plus - pour une durée limitée!"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"40"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-mon-jour-de-chance#Offer_3","http://schema.org/name":[{"@language":"fr","@value":"→ Abonnés théâtre*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"44"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-mon-jour-de-chance#2026-07-16T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Mon jour de chance"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-07-16T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-mon-jour-de-chance#2026-07-17T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Mon jour de chance"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-07-17T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-mon-jour-de-chance#2026-07-18T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Mon jour de chance"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-07-18T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-mon-jour-de-chance#2026-07-24T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Mon jour de chance"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-07-24T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mike-beaudoin","@type":["http://schema.org/Event"],"http://schema.org/description":[{"@language":"fr","@value":"Mike Beaudoin présente son très attendu deuxième one-man show Acharné. Le solide stand-up, qui n’arrête jamais, continue de puiser dans sa vie - pas plate du tout- et se livre sans compromis...avec des détails, beaucoup de détails croustillants et hilarants. Après le succès de sa première tournée VRAI, ses dizaines de milliers d’abonnés qui le suivent assidûment sur les réseaux sociaux et ses passages toujours remarqués à la radio commerciale à heure de grande écoute, Mike séduit de nouveau son public avec un spectacle qui est un mélange explosif d’humour et de vérité, tout en gardant son mordant et son franc parlé qui en font son unicité. Soyez prévenu, vous aurez droit à des péripéties hautes en couleur comme la vie avec son ado qui se confie beaucoup trop, sa peur des psys, son impatience qui teinte plusieurs sphères de sa vie…pour ne nommer que celles-là! Acharné parce qu’il trouve toujours le moyen de surmonter les défis de la vie, tout en nous faisant hurler de rire…lui qui n’est pas reconnu pour avoir la langue dans sa poche!"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/42c3454b-8256-4a57-9b20-b8528e7386b9"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/mike-beaudoin-2026.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/mike-beaudoin.html#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-24T20:00:00-05:00"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/xo3wbcclC30?si=rC8HhhVv4f3KK8r4"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/mike-beaudoin.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Mike Beaudoin"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mike-beaudoin#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mike-beaudoin#Offer_1"}]},{"@id":"http://kg.footlight.io/resource/42c3454b-8256-4a57-9b20-b8528e7386b9","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Mike Beaudoin"}],"http://schema.org/hasOccupation":[{"@value":"Humoriste"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}],"http://schema.org/url":[{"@value":"https://www.mike-beaudoin.com/"}],"http://schema.org/sameAs":[{"@value":"https://www.facebook.com/beaudoinmike"},{"@value":"http://www.wikidata.org/entity/Q111445409"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/mike-beaudoin.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:13:12.363-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/mike-beaudoin.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mike-beaudoin#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E050Z\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/price":[{"@value":"38"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mike-beaudoin#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"34"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-patrice-lecuyer-2","@type":["http://schema.org/Event"],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/3-zn4uLlUls?si=Xg88TFp-6JG5BVTG"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K2-5777"}],"http://schema.org/description":[{"@language":"fr","@value":"Patrice L’Ecuyer présente son tout premier one-man-show intitulé Après seulement 32 ans d’absence sur scène. L’humour a toujours occupé une grande place dans sa carrière, que ce soit dans les Bye Bye (comédien dans quatorze éditions!), L’Ecuyer, Prière de ne pas envoyer de fleurs, les galas Juste pour Rire et ComediHa! ou encore le spectacle Merci beaucoup. Mais aujourd’hui, l’animateur chéri depuis trois décennies s’attaque seul à la scène pour la toute première fois! Au fil de sa carrière, Patrice a toujours été discret sur sa vie, même s’il a vécu des situations hors du commun tant avec ses proches qu’avec de grandes personnalités… mais sur scène, il va tout dévoiler! Flash-back sur sa vie, sa carrière, ses rencontres : à travers un impressionnant flot d’anecdotes remplies d’autodérision, Patrice déploie son immense talent comique. Ancienne star de la LNI (et l’un des joueurs les plus punis!), il a maintenant toute la liberté qu’il faut pour faire rire, surprendre et se mettre à nu sans se sentir coupable. Son talent de conteur, son charisme et ses propos surprenants feront de son spectacle un incontournable de la scène humoristique québécoise. «Pas mal plus drôle que bien des humoristes.» - 98.5 FM «Une série d'anecdotes croustillantes et hilarantes sur le merveilleux monde du showbiz québécois.» - QUB Radio «Très habile dosage d'autodérision et de fierté candide... une soirée franchement captivante et absolument délicieuse.» - Le Devoir «Patrice L'Ecuyer m'a renversé par ses talents de conteur, ses anecdotes improbables, son sens du timing et sa vulnérabilité. J'ai beaucoup ri!» - Noovo info «Un raconteur hors pair... une bête de scène.» - La Presse «Quel excellent conteur, quel excellent spectacle.» - Salut Bonjour «Tellement de sincérité, d'autodérision et de charme. Il maîtrise l'art du timing comme peu savent le faire.» - ICI Première Montréal «Ce one-man-show biographique n'a pas son pareil parmi les spectacles d'humour présentement à l'affiche au Québec.» - Babillart Montréal «Raconter est un art que Patrice L'Ecuyer maîtrise à la perfection!» - Rouge FM «C'est vraiment un conteur hors pair... les rires ont fusé à tout moment.» - ICI Première Québec «De savoureuses histoires... un divertissant survol d'une carrière riche en anecdotes» - Journal de Québec «Le public boit ses paroles, diverti par ses aventures rocambolesques.» - Showbizz.net «Pari réussi... les talents de conteur de L'Ecuyer sont parfaitement mis à contribution.» - Le Soleil «Il s'amuse comme un petit fou, rit de lui-même, et nous entraîne dans un univers où les souvenirs se mêlent à la tendresse et à un humour rassembleur.» - Destination Ville de Québec «Des anecdotes savoureuses! J'ai adoré!» - Le Téléjournal Québec"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/patrice-lecuyer.jpg"}],"http://schema.org/name":[{"@language":"fr","@value":"Patrice L'Ecuyer"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-14T20:00:00-04:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/patrice-lecuyer-2.html#WebPage"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/patrice-lecuyer-2.html"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-patrice-lecuyer-2#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-patrice-lecuyer-2#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/patrice-lecuyer-2.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:14:24.363-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/patrice-lecuyer-2.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-patrice-lecuyer-2#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E054T\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"55"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-patrice-lecuyer-2#Offer_1","http://schema.org/price":[{"@value":"51"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-katrine-sansregret","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/katrine-sansregret.jpg"}],"http://schema.org/name":[{"@language":"fr","@value":"Katrine Sansregret"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/katrine-sansregret.html#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-23T20:00:00-04:00"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/katrine-sansregret.html"}],"http://schema.org/description":[{"@language":"fr","@value":"Finaliste à Star Académie 2025, l’autrice-compositrice-interprète bien connue pour son succès Minable ainsi que sa voix puissante et authentique, propose un spectacle vibrant où se côtoient chansons originales, reprises revisitées et moments de proximité avec le public. Accompagnée de ses musiciens, Katrine amènera le spectateur à vivre des moments empreints d’émotion avec ses chansons comme Minable et Par où partir, et des moments plus énergiques avec sa chanson M’enfuir d’ici. * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/P3WufiTuBhM?si=I2tkNgCdkje0wIQP"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-katrine-sansregret#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-katrine-sansregret#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/katrine-sansregret.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:38:27.325-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/katrine-sansregret.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-katrine-sansregret#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E054Y\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"36"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-katrine-sansregret#Offer_1","http://schema.org/price":[{"@value":"32"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-patrick-groulx","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-186"}],"http://schema.org/name":[{"@language":"fr","@value":"Patrick Groulx"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/patrick-groulx-2026.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-24T20:00:00-04:00"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/patrick-groulx.html"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/patrick-groulx.html#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"Patrick Groulx est enfin de retour sur scène pour présenter son 5e one-man-show: Trop longtemps seul. Dans ce show qui lui ressemble à 100%, il renoue avec un style d’humour qui rappelle parfois le côté décalé et éclaté qu’il avait à l’époque du Groulx luxe. Toujours aussi franc et irrévérencieux, Patrick embrasse maintenant l’idée de vieillir, alors qu’il ne s’est jamais senti aussi bien, aussi jeune et aussi libre que dans la cinquantaine. Trop longtemps seul est un spectacle qui rejoint toutes les générations, qui fait rire aux larmes, réfléchir par moment et a ce petit côté juvénile unique à la Pat Groulx. Pour son 5e show, il regroupe plusieurs facettes comiques de ses précédents spectacles et offre à son public du Groulx pur et dur. Un spectacle aussi le fun à voir seul qu’en gang! Peut-être y croiserez-vous le Curé Poirier et le Brigadier? «Un jour, mon équipe m’a dit: Pat, tu passes trop de temps tout seul… Ça l’air que ça se reflétait dans mes textes! J’ai éclaté de rire car ils avaient raison. J’ai 51 ans, ma blonde est en Outaouais et mes enfants ont leur propre vie maintenant. Pour la première fois depuis longtemps, je retrouve une certaine liberté et je passe plus de temps seul. Ça m’a amené à réfléchir, observer et philosopher sur un paquet de trucs loufoques et inutiles, mais combien inspirants. Bref, tout ça a donné naissance à ce nouveau spectacle que je suis très excité de vous présenter.» - Patrick Groulx"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-patrick-groulx#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-patrick-groulx#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/patrick-groulx.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:39:17.393-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/patrick-groulx.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-patrick-groulx#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E054Z\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/price":[{"@value":"48"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-patrick-groulx#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"44"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-richardson-zephir-punch-creole","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-473"}],"http://schema.org/name":[{"@language":"fr","@value":"Richardson Zéphir"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-24T20:00:00-04:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/richardson-zephir-punch-creole.html"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/richardson-zephir-2025.jpg"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/description":[{"@language":"fr","@value":"Punch créole, le 2e one-man-show de l’humoriste Richardson Zéphir, n’est pas qu’un simple spectacle: c’est une célébration où la rigolade, la musique et l’improvisation se rencontrent dans une ambiance aussi chaleureuse que festive. Avec son charisme naturel et sa personnalité attachante, Richardson se déploie sur scène avec ses mimiques, son jeu physique éclatant et ses changements de voix qui donnent à ses anecdotes savoureuses une couleur unique et hilarante. Ce spectacle vivant et authentique, à l’image de Richardson, charmera autant les amateurs de stand-up que les fans d’improvisation et les téléspectateurs qui l’ont adoré dans LOL : Qui rira le dernier? ou Big Brother Célébrités. Laissez-vous emporter par l’énergie débordante de Richardson Zéphir et ses blagues livrées comme un bon punch créole, qui vous laisse le sourire aux lèvres et le cœur léger. Punch créole: des saveurs d’impro aux arômes de stand-up pur! * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/richardson-zephir-punch-creole.html#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-richardson-zephir-punch-creole#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-richardson-zephir-punch-creole#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/richardson-zephir-punch-creole.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:29:28.210-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/richardson-zephir-punch-creole.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-richardson-zephir-punch-creole#Offer_0","http://schema.org/price":[{"@value":"40"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E0550\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-richardson-zephir-punch-creole#Offer_1","http://schema.org/price":[{"@value":"36"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-dominic-paquet","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/dominic-paquet-2026.jpg"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/dominic-paquet.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Dominic Paquet"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/spec-qc-ca_artiste_dominic-paquet"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-30T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/dominic-paquet.html#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/cgmFKhZDyDo?si=zAJJbgA6bv5qBLVN"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/description":[{"@language":"fr","@value":"Après avoir vendu plus de 250 000 billets de son dernier spectacle, le populaire humoriste Dominic Paquet est de retour avec un tout nouveau one-man-show: J’comprends la game. Le 5e spectacle solo de Dominic se veut un gros clin d'œil à ces fameuses personnes qui, grâce à leur expérience de vie, sont certaines de comprendre des choses qui sont inaccessibles pour le commun des mortels… J’comprends la game est un laboratoire humoristique où la folie prend tout son sens alors que Dominic explore, examine et met en évidence toutes sortes de théories farfelues afin de donner des réponses à des questionnements que personne n’a jamais eus! Encore une fois, Dominic Paquet fait rire aux éclats, surprend et séduit par sa capacité à incarner des personnages uniques et à créer des expressions qui marqueront votre imaginaire! Le public veut des réponses, donc moi je suis là pour nourrir les esprits. Quand le spectacle se termine, je sens que les gens quittent la salle en se disant «maintenant moi aussi j'comprends la game!». - Dominic Paquet Le spectacle est présenté par Alimentation Mon Quartier, qui vous invite à vivre une expérience unique, signée par les artisans et producteurs alimentaires d’ici. En nomination au Gala Les Olivier 2026: Spectacle d’humour / Meilleur vendeur de l’année Olivier de l’année"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-dominic-paquet#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-dominic-paquet#Offer_1"}]},{"@id":"http://kg.footlight.io/resource/spec-qc-ca_artiste_dominic-paquet","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@value":"Dominic Paquet"}],"http://schema.org/image":[{"@value":"https://spec.qc.ca/uploads/artistes/dominic-paquet-2.jpg"}],"http://schema.org/sameAs":[{"@value":"http://kg.artsdata.ca/resource/K12-258"}],"http://schema.org/description":[{"@language":"fr","@value":"Dominic Paquet obtient son diplôme de l'École Nationale de l'humour en 1998. Aux Galas des Olivier 2006 et 2007, il est nominé dans plusieurs catégories : Révélation de l'année, Spectacle d'humour de l'année, Auteur de l'année, Metteur en scène de l'année et Jeu et performance de l'année. En plus de faire de la scène, Dominic Paquet est également connu des milieux de la radio et de la télévision. Il a coanimé plusieurs émissions sur les ondes de CKOI et de Énergie, avec ses collègues Patrick Groulx et Dominic et Martin. À la télévision, il a participé à bon nombre d'émissions de variété qui l'ont fait connaître du grand public. C'est en 2010 qu'il sort sont premier one man show. Cinq ans plus tard, il est de retour avec un troisième spectacle intitulé «Rien qu's'une gosse», pour lequel il vend plus de 40 000 billets en trois mois."}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/dominic-paquet.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:12:45.501-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/dominic-paquet.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-dominic-paquet#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E0551\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"54"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-dominic-paquet#Offer_1","http://schema.org/price":[{"@value":"50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-classe-moyenne","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/classe-moyenne.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Classe Moyenne"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/classe-moyenne.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-19T20:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/classe-moyenne.html#WebPage"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/MJFPq6_4GDE?si=3aCjOLqIqhKkyDXO"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/description":[{"@language":"fr","@value":"Tik Tok les adore, Instagram les suit pas à pas, les plateformes musicales brûlent tant on les écoute : 8,42 millions de clics en quelques mois. Le collectif de musiciens Classe moyenne est la sensation de l’heure, un phénomène comme il s’en produit peu. C’est dans un sous-sol près de La Prairie que le feu prend, là où cinq amis puisent dans leur quotidien pour écrire des chansons qui ressemblent au quotidien des trentenaires, mais que tout le monde aime. Nous autres on s’appelle Classe moyenne, les Colocs c’t’ait déjà pris écrivent-ils, et déjà la séduction opère. Quand ils ajoutent On voulait aller à l’uni, on a tous fini chez Benji, parle-moi d’une école de la vie, on en redemande. Ils s’appellent Classe moyenne, donc, mais aussi Benjamin Nadeau (réalisateur et producteur ayant collaboré avec Marc Dupré, William Cloutier, Ludovick Bourgeois, Dee Holt, Mc12), Gabriel Fredette, Justin Roy, Phil Rxcket et Zach Chico. Des amis qui se sont retrouvés ensemble dans le sous-sol de Benjamin, qui ont écrit une chanson vite devenue virale, qui en ont écrit treize depuis et qui vont continuer de le faire parce qu’ils s’amusent. Et que le monde aime ça, beaucoup, beaucoup. Ne cherchez pas de règles ni de recettes, il n’y en a pas chez Classe moyenne. Il n’y a que la liberté, le plaisir, le positif, le gros party, et pour vous, un bateau qu’il ne faut manquer, car il ne passe pas souvent! Veuillez noter que les artistes pourraient inviter les spectateurs à se lever pendant le spectacle. Nous préférons vous en avertir."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-classe-moyenne#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-classe-moyenne#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/classe-moyenne.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:12:35.525-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/classe-moyenne.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-classe-moyenne#Offer_0","http://schema.org/price":[{"@value":"38"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E0555\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-classe-moyenne#Offer_1","http://schema.org/price":[{"@value":"34"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-dom-babin","@type":["http://schema.org/Event"],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/description":[{"@language":"fr","@value":"Ça m’a pris 2 ans pour formuler ce que j’ai envie de dire depuis 26 ans. Un show d’humour où je joue de la guitare sur les cordes sensibles de notre peuple. C’est un spectacle écrit par vous, interprété par moi et livré par nous. En nomination au Gala Les Olivier 2026: Numéro d’humour de l’année Capsule ou sketch Web humoristique de l’année"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/dom-babin-2027.jpg"}],"http://schema.org/name":[{"@language":"fr","@value":"Dom Babin"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/dom-babin.html"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2027-05-11T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/dom-babin.html#WebPage"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-dom-babin#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-dom-babin#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/dom-babin.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:12:53.842-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/dom-babin.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-dom-babin#Offer_0","http://schema.org/price":[{"@value":"39"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E0559\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-dom-babin#Offer_1","http://schema.org/price":[{"@value":"35"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-elles","@type":["http://schema.org/Event"],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/elles.jpg"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/elles.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Elles"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/elles.html#WebPage"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-03T20:00:00-05:00"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/description":[{"@language":"fr","@value":"S’il y a trois voix que l’on rêve d’entendre à l’unisson, ce sont bien celles de Luce Dufault, Lulu Hughes et Kim Richardson. Ce rêve deviendra réalité puisque les trois interprètes se retrouveront ensemble sur scène dans un nouveau spectacle de Productions Martin Leclerc intitulé ELLES, et qui est en quelque sorte un retour aux sources pour les trois artistes. Leur répertoire? Jazz, soul, rhythm and blues, folk et rock, bien sûr, avec des chansons qui leur permettront de s’éclater. Frissons garantis. Luce, Lulu et Kim sont au sommet de leur art et elles habiteront la scène avec panache, que ce soit en solo, en duo ou en trio, dans une rencontre exceptionnelle où la spontanéité et la complicité ressemblent à l’amitié qui les unit depuis 30 ans. La mise en scène de ELLES a été confiée à Michel Poirier et la direction musicale sera signée Jean Garneau."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-elles#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-elles#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/elles.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:11:36.282-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/elles.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-elles#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E01LQ\u0026CodeSiteClient=00000V"}],"http://schema.org/price":[{"@value":"54"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-elles#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"50"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-eve-cote-cote-eve","@type":["http://schema.org/Event"],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/_E8DxFEv76k?si=MoIr0uJwFDYvTMX6"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/eve-cote-cote-eve.html"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/eve-cote-2025b.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"Après un triomphe cumulant plus de 115 000 billets vendus et des centaines de représentations à travers la province avec les Grandes Crues, Eve Côté présente son premier spectacle solo. Découvrez son univers, sa dégaine gaspésienne et son humour imagé. «Accompagner Eve Côté dans la préparation de son premier one woman show est une chance inouïe. Eve est une humoriste qui raconte comme pas une, l’unicité et la complexité de l’être humain. Après avoir écouté son matériel des dizaines et des dizaines de fois; tout de cette humoriste me fait encore rire aux larmes. Eve a également tous les talents et mon objectif est de vous en faire profiter au maximum. Ce spectacle est à la fois hilarant, tendre, voire touchant. Eve Côté est dans une catégorie à part. Venez la découvrir!» - Joël Legendre «J’aurais aimé ça avoir un band sur scène, des feux d’artifice pis terminer ça avec un solo de claquettes, mais finalement ça sera juste moi; de tous les côtés avec tous mes travers. Mais j’pense que comme moi, vous trouverez que c’est une bonne idée! Je mise sur le meilleur de ma personne, ma Gaspésie natale pis mes jokes!» - Eve Côté «Côté Eve: un spectacle coloré digne de la raconteuse» - Frédérique De Simone, Journal de Montréal «Un débit et une efficacité qui rappellent les meilleures humoristes» - Catherine Brisson, 98,5 FM «Son énergie est infinie. Un premier spectacle solo, mais une humoriste déjà accomplie.» - Christine Manzo, TVA Nouvelles «D'une solidité incroyable! Elle drôle, crue et surtout attachante» - Anaïs Guertin-Lacroix, QUB radio «Le style est si assumé et bien livré qu'on a le goût d'écouter jusqu'au bout.» - Marie-Josée Roy, Showbizz.net «Ses expressions colorées vont vous charmer d'emblée, mais ce n'est que la cerise sur le sundae de ses anectodes exquises sans bon sens!» - Katherine Guillemette, ÉNERGIE «Quelle énergie, quel charisme! Incroyablement divertissante!» - Jérôme Landry, FM93 «Le moins que l'on puisse dire, c'est que Côté Eve est drôlement original!» - Seb Lozon, 107,5 ROUGE «Ses expressions à 1 million de dollars vont vous jeter par terre!» - Sarah Charbonneau, LA CLIQC"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/5d58f420-ddac-4f12-a24c-6e9bbd8c1f32"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-05T20:00:00-05:00"}],"http://schema.org/name":[{"@language":"fr","@value":"Eve Côté"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/eve-cote-cote-eve.html#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-eve-cote-cote-eve#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-eve-cote-cote-eve#Offer_1"}]},{"@id":"http://kg.footlight.io/resource/5d58f420-ddac-4f12-a24c-6e9bbd8c1f32","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Eve Côté"}],"http://schema.org/hasOccupation":[{"@value":"Humoriste"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}],"http://schema.org/url":[{"@value":"https://evecoteofficiel.com/"}],"http://schema.org/alternateName":[{"@language":"fr","@value":"Ève Côté"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/eve-cote-cote-eve.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:22:41.629-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/eve-cote-cote-eve.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-eve-cote-cote-eve#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E01FE\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"47"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-eve-cote-cote-eve#Offer_1","http://schema.org/price":[{"@value":"43"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-patrice-lecuyer","http://schema.org/name":[{"@language":"fr","@value":"Patrice L'Ecuyer"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/patrice-lecuyer.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-10T20:00:00-05:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/patrice-lecuyer.html#WebPage"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/3-zn4uLlUls?si=Xg88TFp-6JG5BVTG"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/patrice-lecuyer.html"}],"http://schema.org/description":[{"@language":"fr","@value":"Patrice L’Ecuyer présente son tout premier one-man-show intitulé Après seulement 32 ans d’absence sur scène. L’humour a toujours occupé une grande place dans sa carrière, que ce soit dans les Bye Bye (comédien dans quatorze éditions!), L’Ecuyer, Prière de ne pas envoyer de fleurs, les galas Juste pour Rire et ComediHa! ou encore le spectacle Merci beaucoup. Mais aujourd’hui, l’animateur chéri depuis trois décennies s’attaque seul à la scène pour la toute première fois! Au fil de sa carrière, Patrice a toujours été discret sur sa vie, même s’il a vécu des situations hors du commun tant avec ses proches qu’avec de grandes personnalités… mais sur scène, il va tout dévoiler! Flash-back sur sa vie, sa carrière, ses rencontres : à travers un impressionnant flot d’anecdotes remplies d’autodérision, Patrice déploie son immense talent comique. Ancienne star de la LNI (et l’un des joueurs les plus punis!), il a maintenant toute la liberté qu’il faut pour faire rire, surprendre et se mettre à nu sans se sentir coupable. Son talent de conteur, son charisme et ses propos surprenants feront de son spectacle un incontournable de la scène humoristique québécoise. «Pas mal plus drôle que bien des humoristes.» - 98.5 FM «Une série d'anecdotes croustillantes et hilarantes sur le merveilleux monde du showbiz québécois.» - QUB Radio «Très habile dosage d'autodérision et de fierté candide... une soirée franchement captivante et absolument délicieuse.» - Le Devoir «Patrice L'Ecuyer m'a renversé par ses talents de conteur, ses anecdotes improbables, son sens du timing et sa vulnérabilité. J'ai beaucoup ri!» - Noovo info «Un raconteur hors pair... une bête de scène.» - La Presse «Quel excellent conteur, quel excellent spectacle.» - Salut Bonjour «Tellement de sincérité, d'autodérision et de charme. Il maîtrise l'art du timing comme peu savent le faire.» - ICI Première Montréal «Ce one-man-show biographique n'a pas son pareil parmi les spectacles d'humour présentement à l'affiche au Québec.» - Babillart Montréal «Raconter est un art que Patrice L'Ecuyer maîtrise à la perfection!» - Rouge FM «C'est vraiment un conteur hors pair... les rires ont fusé à tout moment.» - ICI Première Québec «De savoureuses histoires... un divertissant survol d'une carrière riche en anecdotes» - Journal de Québec «Le public boit ses paroles, diverti par ses aventures rocambolesques.» - Showbizz.net «Pari réussi... les talents de conteur de L'Ecuyer sont parfaitement mis à contribution.» - Le Soleil «Il s'amuse comme un petit fou, rit de lui-même, et nous entraîne dans un univers où les souvenirs se mêlent à la tendresse et à un humour rassembleur.» - Destination Ville de Québec «Des anecdotes savoureuses! J'ai adoré!» - Le Téléjournal Québec"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-patrice-lecuyer#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-patrice-lecuyer#Offer_1"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-04T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-patrice-lecuyer#2026-02-10T200000-0500"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-patrice-lecuyer#2026-04-04T200000-0400"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/patrice-lecuyer.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:27:35.995-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/patrice-lecuyer.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-patrice-lecuyer#Offer_0","http://schema.org/url":[{"@value":"https://www.ovation.ca/00000V/fr/Event/?seriesId=37F73F9E%2D930A%2D4F7B%2D9181%2DA9A800847B58\u0026venueId=0E%2D0E0001"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/price":[{"@value":"53"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-patrice-lecuyer#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"49"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-patrice-lecuyer#2026-02-10T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Patrice L'Ecuyer"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-10T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-patrice-lecuyer#2026-04-04T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Patrice L'Ecuyer"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-04T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-chantal-lamarre","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/chantal-lamarre.html"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/chantal-lamarre.jpg"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/3837fb9b-760f-4769-b390-932d098497c4"}],"http://schema.org/name":[{"@language":"fr","@value":"Chantal Lamarre"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-12-04T20:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/chantal-lamarre.html#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/description":[{"@language":"fr","@value":"Après 40 ans dans le métier, c’est un retour aux sources, une salle, une scène, un spot; Chantal Lamarre convie le public à une soirée comme dans les bons vieux cabarets d’antan où on pousse la chansonnette et on ose la steppette. Observatrice du monde, oiseau moqueur, animatrice et fille de scène qui, jusqu’à 60 ans, n’a jamais pensé la moindre seconde s’y aventurer seule, «elle chante, elle danse, elle joue la comédie», les «oooo» devraient succéder à des «aaa», avec promesse de rires et, peut-être même, l’indispensable petit \"motton\" d’émotion; bref, en toute prétention, une formule gagnante pour un variétés aux codes vintages qui exploite des enjeux bien actuels. Le regard dans le rétroviseur pour envisager l’avenir avec moins de lourdeur; la pipelette qui commente les grands évènements avec son sens critique et sa drôlerie, la fille qui parle de société et de culture avec impétuosité et la femme pitre qui n’a jamais eu peur du ridicule se retrouveront, le temps d’une belle soirée, pour sortir de la grisaille, avoir la banane et l’envie de voir un peu la vie autrement… Mesdames, mesdemoiselles, messieurs, et tout ce qui se situe entre les deux, jeunes et nouveaux vieux ; Steppettes et cornemuses est définitivement à inscrire à votre carnet de sorties! Crédits ★ Textes Chantal Lamarre, avec les collaborations de Catherine Éthier, Guillaume Corbeil, Richard Gohier ★ Script édition et mise en scène François Chénier ★ Musique, orchestration Denis Larochelle et Sylvie Dumontier ★ Chorégraphie Chantal Dauphinais * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-chantal-lamarre#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-chantal-lamarre#Offer_1"}]},{"@id":"http://kg.footlight.io/resource/3837fb9b-760f-4769-b390-932d098497c4","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Chantal Lamarre"}],"http://schema.org/hasOccupation":[{"@language":"fr","@value":"Interprète"}],"http://schema.org/url":[{"@value":"https://chantallamarre.com/"}],"http://schema.org/sameAs":[{"@value":"http://www.wikidata.org/entity/Q2956297"},{"@value":"https://www.facebook.com/chantal.lamarre.steppettes"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/chantal-lamarre.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:19:31.500-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/chantal-lamarre.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-chantal-lamarre#Offer_0","http://schema.org/price":[{"@value":"40"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E0540\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-chantal-lamarre#Offer_1","http://schema.org/price":[{"@value":"36"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mehdi-bousaidan","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-272"}],"http://schema.org/name":[{"@language":"fr","@value":"Mehdi Bousaidan"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/mehdi-bousaidan.html#WebPage"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-30T20:00:00-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/mehdi-bousaidan.html"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/mehdi-bousaidan.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"Dans son tout nouveau spectacle, Mehdi Bousaidan se dévoile comme jamais auparavant. Avec ce troisième one-man show, il choisit de faire une introspection sur sa vie et nous en partage une analyse intime avec l’humour efficace qu’on lui connaît. Ce spectacle est une invitation à jeter un œil à travers la fenêtre de sa vie. C’est un voyage où l’on avance de sa tendre enfance à l’homme qu’il est aujourd’hui en se rapprochant un peu plus de sa réalité. À travers des anecdotes surprenantes, il brouille la frontière entre l’histoire personnelle et l’universel. Il aborde aussi des questions existentielles, comme le mariage, les relations familiales, les peurs et les craintes ainsi que les expériences qui forgent l'âme. C’est un Mehdi qu’on reconnaît, certes, de son sens critique aiguisé, de ses analyses inhabituelles et de ses punchs inattendus, mais c’est aussi un Mehdi qu’on apprend à connaître sous un nouveau visage. Il nous ouvre le livre de sa vie privée pour une toute première fois dans un humour toujours aussi rafraîchissant."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mehdi-bousaidan#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mehdi-bousaidan#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/mehdi-bousaidan.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-28T16:14:10.752-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/mehdi-bousaidan.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mehdi-bousaidan#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04WN\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"43"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mehdi-bousaidan#Offer_1","http://schema.org/price":[{"@value":"39"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-tommy-neron","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/soirees-cabaret/tommy-neron.jpg"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/91004681-064f-4d2a-9803-6acf8fb1e9a6"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/yCGzadr1mkE?si=EP4u8ExMZB6WJd0V"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/tommy-neron.html"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/tommy-neron.html#WebPage"}],"http://schema.org/keywords":[{"@value":"soirees-cabaret"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-13T20:00:00-05:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/dad71250-8ec8-4b41-aa18-47eef056a04c"}],"http://schema.org/name":[{"@language":"fr","@value":"Tommy Néron"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/description":[{"@language":"fr","@value":"Fallait décrire mon show en quelques lignes pis j’ai décidé de ne pas laisser l’assistante du bureau le faire, ce qui explique pourquoi tu n'as pas encore vu les termes Star du web ou encore Vedette sur les réseaux sociaux. Ces termes-là, c’est comme le mot Flamboyant, y’a pas un humoriste qui se décrit lui-même comme flamboyant. Mon spectacle, c’est un gros mélange d'observations et d'anecdotes sur mon passé qui, avec le recul, n'était pas exactement comme tout le monde. Aussi, tu peux amener ton ado, je ne pense pas le traumatiser, mais c’est à tes risques et péril. Pour le show, j'ai la chance d'être entouré de la brillante Korine Côté pour la script-édition et du talentueux Laurent Paquin à la mise en scène. Le titre aurait pu être Flamboyant mais j'ai choisi Les fleurs poussent encore… Tu vas comprendre pourquoi en voyant le spectacle! * Formule cabaret au foyer de la salle Odyssée / Ambiance intime et conviviale / Bar en fonction / Places limitées"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-tommy-neron#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-tommy-neron#Offer_1"}]},{"@id":"http://kg.footlight.io/resource/91004681-064f-4d2a-9803-6acf8fb1e9a6","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Tommy Néron"}],"http://schema.org/hasOccupation":[{"@language":"fr","@value":"Humoriste"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/tommy-neron.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-12T16:26:53.645-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/tommy-neron.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-tommy-neron#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E0506\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"28"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-tommy-neron#Offer_1","http://schema.org/price":[{"@value":"24"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_country-folk-matt-lang","@type":["http://schema.org/Event"],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/country-folk/matt-lang.html#WebPage"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K13-157"}],"http://schema.org/name":[{"@language":"fr","@value":"Matt Lang"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"country-folk"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/QuKTkTbffp4?si=-sfI6F461wS2bufp"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/country-folk/matt-lang.jpg"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/country-folk/matt-lang.html"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-06-18T20:00:00-04:00"}],"http://schema.org/description":[{"@language":"fr","@value":"Après avoir vendu plus de 50.000 billets au cours de sa tournée More, Matt Lang est de retour avec un tout nouveau spectacle pour son album All Night Longer. L’interprète masculin de l’année au dernier Gala Country nous propose un show digne des plus grands Honky Tonk des États-Unis avec une production plus grandiose que jamais. L’artiste new country et ses musiciens continuent d’amener Nashville au Québec avec les pièces à succès de ses premiers albums, ainsi que toutes les chansons de son nouvel opus pour prolonger le party! Ce spectacle réserve de belles surprises pour les fans de country, mais aussi pour les nostalgiques et les adeptes de rock. Avec sa pyrotechnie et ses jeux de lumières, All Night Longer vous transporte sous les néons de Broadway pour un événement unique. Lauréat au Gala de l'ADISQ 2025 dans les catégories : Album de l'année - Anglophone (All Night Longer) Spectacle de l'année - Anglophone (All Night Longer) Veuillez noter que l'artiste pourrait inviter les spectateurs à se lever pendant le spectacle. Nous préférons vous en avertir."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_country-folk-matt-lang#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_country-folk-matt-lang#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/country-folk/matt-lang.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:15:36.070-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/country-folk/matt-lang.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_country-folk-matt-lang#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E014M\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/price":[{"@value":"49"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_country-folk-matt-lang#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"45"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-quebec-montreal","http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/quebec-montreal.html"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-06T20:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/quebec-montreal.html#WebPage"}],"http://schema.org/name":[{"@language":"fr","@value":"Québec-Montréal sur scène"}],"http://schema.org/keywords":[{"@value":"theatre-hors-serie"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/1fwuIls4kcE?si=nvyMlBsmRyBcy3KK"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/theatre-hors-serie/quebec-montreal-2026b.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"Le film culte du cinéma québécois Québec-Montréal fait ses débuts sur les planches! Ne manquez pas cette production théâtrale, dont la mise en scène est assurée par Pierre-François Legendre, qui propose une distribution toute en prestige et en fraîcheur : Charlotte Aubin, Catherine Brunet, Louis Carrière, Patrick Emmanuel Abellard, Pier-Luc Funk, Simon Pigeon, Mickaël Gouin et Antoine Pilon. Sans oublier les auteurs de cette production théâtrale eux aussi bien connus : Ricardo Trogi, Patrice Robitaille et Jean-Philippe Pearson, qui passeront également le flambeau de leurs rôles à la nouvelle génération. Sortie en 2002 et réalisé par Ricardo Trogi, Québec-Montréal est une comédie dramatique qui explore les relations humaines, les non-dits et les quiproquos à travers les histoires croisées de plusieurs personnages en route entre deux villes emblématiques. Synopsis 250 km d'asphalte, sept voyageurs dans la trentaine, une destination. La route devient l'occasion d'échanger des points de vue sur les relations amoureuses et de débattre de questions troublantes sur l'existence. L'autoroute 20 sert de toile de fond aux situations parfois hilarantes, parfois compliquées, entre trois amis qui discutent d'amour idéal, entre deux collègues à la romance ambigüe et entre un jeune couple et leur imminente rupture. Le trajet sera le théâtre de prises de conscience et de réflexions profondes relatives à où la vie nous a menés, et sur les chemins qu'elle nous fera emprunter dans le futur. «J’avais aimé le film à l’époque et voilà que j’ai adoré la pièce qui en est une version vitaminée.» - Patrick Delisle-Crevier, Magazine 7 Jours. «Cette adaptation théâtrale du film classique vient donc surpasser les attentes — pourtant très hautes! — et s’impose comme l’une des pièces les plus drôles et réjouissantes de l’année.» - Bruno Lapointe, Journal de Montréal «Punchée, efficace, assumée, la nouvelle mouture de Québec-Montréal rend justice au film à succès. Chapeau à Pierre-Francois Legendre d’avoir matérialisé l’autoroute 20 sur scène avec une brillante ingéniosité!» - Isabelle Perron, TVA «Il y a des perles d’ingéniosité […] Chaque réplique fonctionne, on embarque dans la proposition.» - Catherine Brisson, 98,5 FM «On sent qu’il y a une certaine passation de Québec-Montréal d’une génération à l’autre, on sent quelque chose des jeunes d’aujourd’hui, même si on reste dans la nostalgie. […] C’est drôle, c’est sans prétention.» - Claudia Hébert, ICI Radio-Canada Première «Les comédiens de la nouvelle mouture de Québec-Montréal insufflent une intensité nouvelle qui fait du bien à l’âme. Sans tomber dans l’imitation, chaque acteur a su réinventer le personnage qu’il incarne avec une authenticité désarmante.» - Marie-Ève Archambault, mattv.ca «L’idée de ressusciter Québec-Montréal avec une nouvelle génération d’acteurs était lumineuse […] Les comédiens semblent s’amuser follement!» - Marie-Josée R. Roy, Hollywood PQ «Sous une apparente légèreté pleine d’humour, le spectacle révèle avec finesse des vérités et des enjeux universels qui nous touchent tous.» - Pamela Beaudry, Meve et cie"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-quebec-montreal#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-quebec-montreal#Offer_1"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-quebec-montreal#Offer_2"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-07T20:00:00-05:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-quebec-montreal#2026-02-06T200000-0500"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-quebec-montreal#2026-02-07T150000-0500"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-quebec-montreal#2026-02-07T200000-0500"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/quebec-montreal.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:17:25.185-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/quebec-montreal.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-quebec-montreal#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"72"}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E01NX\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-quebec-montreal#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"68"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-quebec-montreal#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"→ Abonnés théâtre*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"58"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-quebec-montreal#2026-02-06T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Québec-Montréal sur scène"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-06T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-quebec-montreal#2026-02-07T150000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Québec-Montréal sur scène"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-07T15:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-quebec-montreal#2026-02-07T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Québec-Montréal sur scène"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-07T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-eric-lapointe","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Éric Lapointe"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/eric-lapointe-2026.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-24T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/eric-lapointe.html#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/dvgbkSUL4ro?si=UoWTacKjb_6Aw18U"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/eric-lapointe.html"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/spec-qc-ca_artiste_eric-lapointe"}],"http://schema.org/description":[{"@language":"fr","@value":"Fêtant ses 30 ans de carrière, Éric Lapointe soulignera cet anniversaire avec un réenregistrement des dix chansons de son album OBSESSION (Terre promise, N’importe quoi, L’exquise…) et la parution d’une édition vinyle. Aussi, plusieurs événements spéciaux sont prévus, dont un spectacle-anniversaire, OBSESSION 30 ANS, célébrant sur scène les trois décennies bien remplies depuis la parution de ce premier disque. L’énergie et la fougue bien connues du chanteur seront au rendez-vous! Une soirée de grands succès et de belles surprises vous attend. Veuillez noter que l'artiste pourrait inviter les spectateurs à se lever pendant le spectacle. Nous préférons vous en avertir."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-eric-lapointe#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-eric-lapointe#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/eric-lapointe.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:13:38.801-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/eric-lapointe.html"}]},{"@id":"http://kg.footlight.io/resource/spec-qc-ca_artiste_eric-lapointe","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@value":"Éric Lapointe"}],"http://schema.org/sameAs":[{"@value":"http://kg.artsdata.ca/resource/K13-58"}],"http://schema.org/image":[{"@value":"https://spec.qc.ca/uploads/artistes/eric-lapointe.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"En septembre 1994, une véritable bombe explose dans le monde de la musique québécoise: Éric Lapointe. Découvert en plein été avec Terre promise, une « road song » qui détonne chez cet adolescent que la rumeur veut déjà fêtard, N’importe quoi le consacre instantanément au rang de star. Depuis, ses albums se vendent à des centaines de milliers de copies et ses succès perdurent dans le temps: N’importe quoi, Terre Promise, L’exquise, Je rêve encore, Deux fois la même histoire, D’l’amour, j’en veux pus, Bobépine, Loadé comme un gun, Les Boys, Rocket, Laisse-moi seul, Rien à regretter, On commence à s’quitter, Mon Ange, Ma gueule, Le Boys Blues Band, Qu’est-ce que ça peut ben faire, Un beau grand slow et plus récemment Reste là et La Bartendresse, Coupable, Attends et plus encore. L'Auteur-Compositeur-Interprète à reçu plusieurs prix tel que album de l'année - rock, album de l'année - meilleur vendeur, interprète masculin de l'année, chanson populaire de l'année. Il est aussi le premier artiste québécois (francophone) masculin à franchir le cap du million d'exemplaires vendus au Québec. Il a également fait plusieurs apparitions au cinéma. Il est depuis 2014 coach dans l’émission La Voix."}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-eric-lapointe#Offer_0","http://schema.org/price":[{"@value":"54"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E053N\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-eric-lapointe#Offer_1","http://schema.org/price":[{"@value":"50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-simon-leblanc-present","http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/simon-leblanc.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-21T20:00:00-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/simon-leblanc-present.html"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-248"}],"http://schema.org/name":[{"@language":"fr","@value":"Simon Leblanc"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/simon-leblanc-present.html#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/description":[{"@language":"fr","@value":"Simon Leblanc a plus envie que jamais de sillonner le Québec en quête de paysages et de faces qui rient. Simon repart donc en tournée pour présenter son quatrième spectacle, Présent. Il a hâte de vous voir comme ça s'peut pas! À bientôt tout le monde… Méfiez-vous des billets offerts sur les réseaux sociaux. Plusieurs fraudeurs sont à l’œuvre : ils vendent des billets qui n’existent pas et disparaissent dans la nature une fois l’argent reçu. Seule notre billetterie peut certifier les billets vendus. Toute prétention à l’effet contraire est frauduleuse. L'équipe de la salle Odyssée et le producteur Phaneuf"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-simon-leblanc-present#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-14T20:00:00-05:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-simon-leblanc-present#2026-01-21T200000-0500"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-simon-leblanc-present#2026-02-14T200000-0500"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/simon-leblanc-present.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:16:39.549-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/simon-leblanc-present.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-simon-leblanc-present#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E015Z\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"41"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-simon-leblanc-present#2026-01-21T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Simon Leblanc"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-21T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-simon-leblanc-present#2026-02-14T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Simon Leblanc"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-14T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-michel-barrette","@type":["http://schema.org/Event"],"http://schema.org/description":[{"@language":"fr","@value":"Dans son tout nouveau spectacle Drôle depuis 1957, Michel Barrette revient en force avec un regard hilarant et tendre sur sa génération. Il y célèbre « la revanche des baby-boomers », ces jeunes d’hier devenus les grands sages d’aujourd’hui… mais toujours aussi irrévérencieux. À travers anecdotes savoureuses, souvenirs touchants et observations mordantes, il nous raconte son époque avec une énergie contagieuse. Entre rires et réflexions, Michel prouve qu’on peut vieillir sans jamais cesser d’être drôle. Ce spectacle est une véritable lettre d’amour au passé… et un clin d’œil bien senti au présent."}],"http://schema.org/name":[{"@language":"fr","@value":"Michel Barrette"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-284"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/michel-barrette-2026.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-29T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/michel-barrette.html#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/michel-barrette.html"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-michel-barrette#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-michel-barrette#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/michel-barrette.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:33:25.066-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/michel-barrette.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-michel-barrette#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E053P\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"46"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-michel-barrette#Offer_1","http://schema.org/price":[{"@value":"42"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-qw4rtz-dernier-tour-de-piste","@type":["http://schema.org/Event"],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/qw4rtz-dernier-tour-de-piste.html#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/b0JG269pbn0?si=GBVr3o1HkEH7CR7B"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/name":[{"@language":"fr","@value":"QW4RTZ"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K10-294"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/qw4rtz-2026.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-04T19:00:00-05:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/description":[{"@language":"fr","@value":"Riche d’une quinzaine de saisons professionnelles, QW4RTZ s’élance pour son Dernier tour de piste. La pédale au plancher, les quatre coéquipiers mettent toute la gomme dans un nouveau spectacle de course. Trad, pop, folk, rock, classique, tous les styles s’injectent dans leurs quatre cylindres a cappella. C’est profitant d’une mécanique vocale toujours aussi bien huilée que le groupe négocie chaque virage de ce dernier droit avec panache. Toujours guidés par leur ingénieur en chef Serge Postigo, les quatre amis d’enfance se dépassent encore une fois et réalisent leurs plus grandes aspirations en présentant des numéros de pointe sans jamais déraper… ou presque. Leur formule a cappella permet à Louis, Phil, François et Fa2 de ne jamais freiner leur imagination, et prouve que, même sans guitare, ils en ont sous le capot."}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/qw4rtz-dernier-tour-de-piste.html"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-qw4rtz-dernier-tour-de-piste#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-qw4rtz-dernier-tour-de-piste#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/qw4rtz-dernier-tour-de-piste.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:34:33.295-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/qw4rtz-dernier-tour-de-piste.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-qw4rtz-dernier-tour-de-piste#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E010P\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"47"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-qw4rtz-dernier-tour-de-piste#Offer_1","http://schema.org/price":[{"@value":"43"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-daniel-grenier","@type":["http://schema.org/Event"],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/daniel-grenier.html#WebPage"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/6d976473-3f8c-4739-b9c0-4121377fce74"}],"http://schema.org/name":[{"@language":"fr","@value":"Daniel Grenier"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/soirees-cabaret/daniel-grenier-2026b.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-10T20:00:00-05:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"soirees-cabaret"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/dad71250-8ec8-4b41-aa18-47eef056a04c"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/daniel-grenier.html"}],"http://schema.org/description":[{"@language":"fr","@value":"Garder son CŒUR D'ENFANT, c’est refuser de laisser la vie user notre émerveillement. C’est continuer à s’étonner devant les petites choses un coucher de soleil, un rire sincère, une chanson qu’on aime. C’est savoir s’émerveiller sans cynisme, rêver sans calcul, aimer sans attendre en retour. C’est aussi garder une certaine légèreté, la capacité de rire de soi, de jouer, d’être curieux, d’apprendre sans honte. Ce n’est pas être naïf, mais choisir de rester sensible et ouvert, même quand le monde devient dur ou compliqué. Avoir un CŒUR D'ENFANT, c’est croire qu’il y a encore du beau, même dans le chaos. C’est se souvenir que la joie n’est pas un luxe, mais un instinct qu’on peut nourrir chaque jour. Procurez-vous vos billets pour le nouveau spectacle de Daniel Grenier et laisser votre CŒUR D’ENFANT prendre toute la place. * Formule cabaret au foyer de la salle Odyssée / Ambiance intime et conviviale / Bar en fonction / Places limitées"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-daniel-grenier#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-daniel-grenier#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/daniel-grenier.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:33:43.299-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/daniel-grenier.html"}]},{"@id":"http://kg.footlight.io/resource/6d976473-3f8c-4739-b9c0-4121377fce74","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Daniel Grenier"}],"http://schema.org/hasOccupation":[{"@value":"Humoriste"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}],"http://schema.org/url":[{"@value":"https://danielgrenier.ca/"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-daniel-grenier#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E053W\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"33"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-daniel-grenier#Offer_1","http://schema.org/price":[{"@value":"29"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-brigitte-boisjoli-bring-back-68","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K13-28"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/brigitte-boisjoli-2025.jpg"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/name":[{"@language":"fr","@value":"Brigitte Boisjoli"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/brigitte-boisjoli-bring-back-68.html"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-13T20:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/brigitte-boisjoli-bring-back-68.html#WebPage"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/MTY-yUpzWsE?si=iTJ2EZ3cRuXoOBC2"}],"http://schema.org/description":[{"@language":"fr","@value":"Entourée de 4 musiciens, Brigitte Boisjoli fait revivre la fièvre Elvis Presley! «Elvis a marqué les générations par son talent, sa beauté et sa voix qui semblait sortie tout droit d’un chœur gospel. Un rock’n’roll qui a dérangé, qui s’est déhanché. Et moi, ça m’appelait déjà, étant toute jeune. Cette fougue, cette énergie qui me soulève… Ce besoin urgent de chanter qui je suis, tout comme Elvis. Aujourd’hui, je le sens, je suis prête pour le rock’n’roll d’une vie, celle d’Elvis, mon Elvis!» - Brigitte Boisjoli"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-brigitte-boisjoli-bring-back-68#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-brigitte-boisjoli-bring-back-68#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/brigitte-boisjoli-bring-back-68.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:20:59.362-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/brigitte-boisjoli-bring-back-68.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-brigitte-boisjoli-bring-back-68#Offer_0","http://schema.org/price":[{"@value":"45"}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E00YO\u0026CodeSiteClient=00000V"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-brigitte-boisjoli-bring-back-68#Offer_1","http://schema.org/price":[{"@value":"41"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-virginie-fortin","@type":["http://schema.org/Event"],"http://schema.org/description":[{"@language":"fr","@value":"Virginie Fortin travaille son 3e spectacle d'humour dans une série de rodages. Elle a hâte de vous retrouver! En nomination au Gala Les Olivier 2026: Numéro d’humour de l’année * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/virginie-fortin.html"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/virginie-fortin-2026.jpg"}],"http://schema.org/name":[{"@language":"fr","@value":"Virginie Fortin"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-235"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-12T20:00:00-05:00"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/5lFmUp65sn8?si=fpRTUSs-YUcXw8Ym"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/virginie-fortin.html#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-virginie-fortin#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-virginie-fortin#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/virginie-fortin.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:22:49.960-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/virginie-fortin.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-virginie-fortin#Offer_0","http://schema.org/price":[{"@value":"39"}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E053Y\u0026CodeSiteClient=00000V"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-virginie-fortin#Offer_1","http://schema.org/price":[{"@value":"35"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-tommy-neron","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/tommy-neron.html"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/91004681-064f-4d2a-9803-6acf8fb1e9a6"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/tommy-neron.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-14T20:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/tommy-neron.html#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/yCGzadr1mkE?si=EP4u8ExMZB6WJd0V"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/description":[{"@language":"fr","@value":"Fallait décrire mon show en quelques lignes pis j’ai décidé de ne pas laisser l’assistante du bureau le faire, ce qui explique pourquoi tu n'as pas encore vu les termes Star du web ou encore Vedette sur les réseaux sociaux. Ces termes-là, c’est comme le mot Flamboyant, y’a pas un humoriste qui se décrit lui-même comme flamboyant. Mon spectacle, c’est un gros mélange d'observations et d'anecdotes sur mon passé qui, avec le recul, n'était pas exactement comme tout le monde. Aussi, tu peux amener ton ado, je ne pense pas le traumatiser, mais c’est à tes risques et péril. Pour le show, j'ai la chance d'être entouré de la brillante Korine Côté pour la script-édition et du talentueux Laurent Paquin à la mise en scène. Le titre aurait pu être Flamboyant mais j'ai choisi Les fleurs poussent encore… Tu vas comprendre pourquoi en voyant le spectacle! * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7"}],"http://schema.org/name":[{"@language":"fr","@value":"Tommy Néron"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-tommy-neron#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-tommy-neron#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/tommy-neron.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:25:42.910-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/tommy-neron.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-tommy-neron#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E053Z\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"34"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-tommy-neron#Offer_1","http://schema.org/price":[{"@value":"30"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-zac-bulle","@type":["http://schema.org/Event"],"http://schema.org/description":[{"@language":"fr","@value":"Après des années à créer des vidéos étranges sur internet, le petit Zac Bulle de 8 ans et demi passe du web à la scène avec son tout premier spectacle. Dans cette œuvre éclatée qui mélange stand-up, mises en scènes et personnages extravagants (dont Jacques Bulle, son papa adoré) Zac vous embarque dans son univers décalé et enfantin. Attendez-vous à un humour absurde, bizarre, parfois troublant… mais surtout très con. Pour public averti, 13 ans et plus * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/zac-bulle.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Zac Bulle"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/zac-bulle-2026.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-12-05T20:00:00-05:00"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/sWH-sSf6UZA?si=wLZI-l7rLpu7pmoE"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/zac-bulle.html#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-zac-bulle#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-zac-bulle#Offer_1"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-zac-bulle#Offer_2"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/zac-bulle.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:36:13.064-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/zac-bulle.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-zac-bulle#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E0541\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"33"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-zac-bulle#Offer_1","http://schema.org/price":[{"@value":"29"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-zac-bulle#Offer_2","http://schema.org/price":[{"@value":"25"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"étudiants"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-podcast-couple-ouvert","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/podcast-couple-ouvert.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Podcast Couple Ouvert"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/podcast-couple-ouvert.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-12-11T20:00:00-05:00"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/podcast-couple-ouvert.html#WebPage"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/2DJwFAp6ViY?si=zxh6r0_JligGrc1x"}],"http://schema.org/description":[{"@language":"fr","@value":"Thomas Levac et Stéphanie Vandelac animent le podcast COUPLE OUVERT. Ouvert d’esprit, ouvert à discuter de sujets chauds, mais surtout ouvert à rire de tout. Leur complicité et leur sens de l’autodérision créent une ambiance où rien n’est tabou. Chaque épisode devient une invitation à explorer les relations modernes avec humour et authenticité. Préparez-vous : COUPLE OUVERT devant public, c’est extrêmement érotique! * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-podcast-couple-ouvert#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-podcast-couple-ouvert#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/podcast-couple-ouvert.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:18:04.619-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/podcast-couple-ouvert.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-podcast-couple-ouvert#Offer_0","http://schema.org/price":[{"@value":"35"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E0542\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-podcast-couple-ouvert#Offer_1","http://schema.org/price":[{"@value":"31"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-garou","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/garou.jpg"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/5b1f79c4-32ab-4770-bd5a-88fbdd5c51e4"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/garou.html#WebPage"}],"http://schema.org/name":[{"@language":"fr","@value":"Garou"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-26T20:00:00-04:00"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/bsmF77aegsU?si=u6Ww_kzmtdlvUed2"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/garou.html"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/description":[{"@language":"fr","@value":"Garou a fait paraître Un meilleur lendemain en avril 2025, son tout premier album à titre d’auteur-compositeur. L’artiste a pris la plume parce qu’il avait des choses à dire! Le résultat: dix chansons très intimes qui ont été travaillées avant tout pour la scène et qui marquent un nouveau départ, dévoilent une nouvelle vision musicale. Après des années de recherche sonore en résidence dans son studio, son nouveau spectacle Garou solo est une invitation en chansons dans un univers cinématique et théâtral. Seul en scène, Garou prend la route avec son piano et sa guitare, tout en s’accompagnant au moyen de diverses technologies. Dans une rétrospective autant autobiographique qu’éclectique, il raconte en paroles et en musique cette vie complètement folle, ces expériences extraordinaires, ces moments de solitude en pleine nature, les grandes amitiés et la richesse de l’amour qui l’entourent. Revisitant ainsi plus de 30 ans de carrière, il interprète ses chansons classiques et ses nouvelles pièces dans une mise en scène élégante, soutenue par une scénographie tout en images."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-garou#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-garou#Offer_1"}]},{"@id":"http://kg.footlight.io/resource/5b1f79c4-32ab-4770-bd5a-88fbdd5c51e4","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Garou"}],"http://schema.org/hasOccupation":[{"@value":"Chanteur"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Chanteur"}],"http://schema.org/url":[{"@value":"https://garouofficiel.com/"}],"http://schema.org/sameAs":[{"@value":"http://www.wikidata.org/entity/Q31733"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/garou.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:12:18.193-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/garou.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-garou#Offer_0","http://schema.org/price":[{"@value":"57"}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04XH\u0026CodeSiteClient=00000V"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-garou#Offer_1","http://schema.org/price":[{"@value":"53"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-richardson-zephir","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/richardson-zephir-2025.jpg"}],"http://schema.org/name":[{"@language":"fr","@value":"Richardson Zéphir"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-473"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/richardson-zephir.html"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-24T20:00:00-05:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.artsdata.ca/resource/PerformingArtsEvent"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/richardson-zephir.html#WebPage"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/description":[{"@language":"fr","@value":"Punch créole, le 2e one-man-show de l’humoriste Richardson Zéphir, n’est pas qu’un simple spectacle: c’est une célébration où la rigolade, la musique et l’improvisation se rencontrent dans une ambiance aussi chaleureuse que festive. Avec son charisme naturel et sa personnalité attachante, Richardson se déploie sur scène avec ses mimiques, son jeu physique éclatant et ses changements de voix qui donnent à ses anecdotes savoureuses une couleur unique et hilarante. Ce spectacle vivant et authentique, à l’image de Richardson, charmera autant les amateurs de stand-up que les fans d’improvisation et les téléspectateurs qui l’ont adoré dans LOL : Qui rira le dernier? ou Big Brother Célébrités. Laissez-vous emporter par l’énergie débordante de Richardson Zéphir et ses blagues livrées comme un bon punch créole, qui vous laisse le sourire aux lèvres et le cœur léger. Punch créole: des saveurs d’impro aux arômes de stand-up pur! * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-richardson-zephir#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-richardson-zephir#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/richardson-zephir.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-22T16:28:49.195-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/richardson-zephir.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-richardson-zephir#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E01BE\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"37"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-richardson-zephir#Offer_1","http://schema.org/price":[{"@value":"33"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-alexandre-barrette","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/alexandre-barrette-2026.jpg"}],"http://schema.org/name":[{"@language":"fr","@value":"Alexandre Barrette"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-02T20:00:00-04:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K13-7"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/alexandre-barrette.html#WebPage"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/alexandre-barrette.html"}],"http://schema.org/description":[{"@language":"fr","@value":"Mon nouveau show s’appelle OUVERT. Bien que je sois habituellement pudique sur ma vie privée, je m’ouvre comme jamais dans ce spectacle! Et aussi, quand même important, je crois que vous allez rire beaucoup!"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-alexandre-barrette#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-alexandre-barrette#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/alexandre-barrette.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:20:22.006-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/alexandre-barrette.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-alexandre-barrette#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E0522\u0026CodeSiteClient=00000V"}],"http://schema.org/price":[{"@value":"49"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-alexandre-barrette#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"45"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-ludovick-bourgeois","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/ludovick-bourgeois-2026.jpg"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/ludovick-bourgeois.html"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/name":[{"@language":"fr","@value":"Ludovick Bourgeois"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-277"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/ludovick-bourgeois.html#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-12T20:00:00-05:00"}],"http://schema.org/description":[{"@language":"fr","@value":"Soutenu par un riche univers visuel, dans la lignée de son plus récent spectacle en hommage aux BB, Karaoké 90 se déploiera en trois tableaux: les hits québécois, ceux du groupe Les BB et les mégatubes mondiaux. Du grunge de Nirvana aux power ballades de Céline Dion, en passant par les hymnes pop et rock de U2, Backstreet Boys, Bryan Adams, Bon Jovi, sans oublier les mémorables chansons de nos artistes de chez nous Mario Pelchat, Marie Carmen et plus encore, Ludovick Bourgeois propose une formidable plongée musicale dans cette décennie faste en succès. Karaoké 90: c’est une célébration des années 90 et de la musique qui réunit, un moment de joyeuse nostalgie et de plaisir partagé. Mentionnons qu’à leur arrivée, les spectateurs seront invités à s’inscrire pour participer à un karaoké géant qui verra chaque soir une personne du public monter sur scène pour interpréter sa chanson aux côtés de Ludovick et de ses musiciens, dans une ambiance que l’on promet survoltée!"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-ludovick-bourgeois#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-ludovick-bourgeois#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/ludovick-bourgeois.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:33:52.126-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/ludovick-bourgeois.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-ludovick-bourgeois#Offer_0","http://schema.org/price":[{"@value":"49"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E053X\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-ludovick-bourgeois#Offer_1","http://schema.org/price":[{"@value":"45"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-pascal-cameron-un-spectacle","@type":["http://schema.org/Event"],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/pascal-cameron-un-spectacle.html#WebPage"}],"http://schema.org/name":[{"@language":"fr","@value":"Pascal Cameron"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/e75531ec-57cd-4370-a5a9-d1574b931b99"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/pascal-cameron.jpg"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/08ohoGXSxpM?si=rYmz7ELw8ln25upg"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2027-02-06T20:00:00-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/pascal-cameron-un-spectacle.html"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/description":[{"@language":"fr","@value":"Pascal Cameron présente son premier one man show un spectacle. qui est une représentation humoristique qui veut parler à tout le monde et de tout le monde. Par contre, il est important de noter que c'est un spectacle d'humour, donc les propos de cette représentation seraient inappropriés au Parlement ou à des funérailles mais plus que correct dans un pool party, Noël, un accouchement, la St-Jean ou à crier à travers la fenêtre de ton char quand tu es pogné dans le trafic. De plus, un spectacle. a comme objectif premier de faire rire. Fack, va pas me citer comme preuve quand tu t'obstines avec du monde «Pascal Cameron a dit ça l'autre jour». J'ai aucun diplôme sauf celui du meilleur livreur de journaux de la Montérégie 1999. (Après, j'ai lâché ma job pour me retirer au sommet) Un spectacle. c'est un show de stand up traditionnel avec des blagues fraiches. * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7 «Du génie, à voir absolument!» - Joanie Duquette, CKOI «Cameron est un maître.» - Christian Saint-Pierre, Le Devoir «Il est encore possible, en stand-up, d'aller loin. Mais pour se rendre là, sans trébucher, il faut à la fois que les intentions d'un humoriste soient limpides et que l'exécution soit sans faille. Deux conditions auxquelles Cameron répond.» - Dom Tardif, La Presse «Il se fraye un chemin irrévérencieux grâce à l'auto-dérision, avec un zeste d'Yvon Deschamps.» - Myriam Segal, FM93"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-pascal-cameron-un-spectacle#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-pascal-cameron-un-spectacle#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/pascal-cameron-un-spectacle.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:19:17.255-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/pascal-cameron-un-spectacle.html"}]},{"@id":"http://kg.footlight.io/resource/e75531ec-57cd-4370-a5a9-d1574b931b99","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Pascal Cameron"}],"http://schema.org/hasOccupation":[{"@value":"Humoriste"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}],"http://schema.org/url":[{"@value":"https://pascalcameron.com/"}],"http://schema.org/sameAs":[{"@value":"https://www.facebook.com/pascalcameronhumoriste/"},{"@value":"http://www.wikidata.org/entity/Q111454377"},{"@value":"http://kg.artsdata.ca/resource/K2-2540"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-pascal-cameron-un-spectacle#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"40"}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E0557\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-pascal-cameron-un-spectacle#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"36"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-le-comte-de-monte-cristo","http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-28T19:00:00-04:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"theatre-hors-serie"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/le-comte-de-monte-cristo.html#WebPage"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/le-comte-de-monte-cristo.html"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/description":[{"@language":"fr","@value":"L’une des plus grandes œuvres d’Alexandre Dumas revient sur scène dans une adaptation puissante signée Serge Denoncourt. Le Comte de Monte-Cristo retrace le destin d’Edmond Dantès, jeune marin faussement accusé et emprisonné pendant quatorze ans au Château d’If. Lorsqu’il parvient à s’évader, il découvre un trésor caché, adopte une nouvelle identité et revient pour faire payer ceux qui l’ont trahi. Une fresque haletante sur la trahison, la vengeance et la résilience humaine. Parce qu’aussi longue soit l’attente, la justice finit toujours par triompher. Mise en scène Serge Denoncourt Direction artistique Pierre Bernard Distribution Mikhaïl Ahooja : Edmond Dantès / Le Comte de Monte-Cristo Mélissa Désormeaux-Poulin : Mercédès Herrera / de Morcerf Maxime de Cotret : Fernand Mondego / de Morcerf Maxime Denommée : Gérard de Villefort Kevin Houle : Danglars Benoit Drouin-Germain : Gaspard Caderousse Lou-Pascal Tremblay : André de Villefort / Andréa Cavalcanti Madeleine Sarr : Haydée Luc Bourgeois : Abbé Faria Paul Doucet : Noirtier de Villefort Thomas Derasp-Verge : Albert de Morcerf Sabrina Bégin-Tejeda : Valentine de Villefort Maxime-Olivier Potvin : Bertuccio Durée du spectacle : 3h plus un entracte."}],"http://schema.org/name":[{"@language":"fr","@value":"Le comte de Monte-Cristo"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/theatre-hors-serie/le-comte-de-monte-cristo.jpg"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-le-comte-de-monte-cristo#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-le-comte-de-monte-cristo#Offer_1"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-30T19:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-le-comte-de-monte-cristo#2026-08-28T190000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-le-comte-de-monte-cristo#2026-08-29T140000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-le-comte-de-monte-cristo#2026-08-29T190000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-le-comte-de-monte-cristo#2026-08-30T140000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-le-comte-de-monte-cristo#2026-08-30T190000-0400"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/le-comte-de-monte-cristo.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:36:43.039-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/le-comte-de-monte-cristo.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-le-comte-de-monte-cristo#Offer_0","http://schema.org/price":[{"@value":"94"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E01QT\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-le-comte-de-monte-cristo#Offer_1","http://schema.org/price":[{"@value":"90"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-le-comte-de-monte-cristo#2026-08-28T190000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Le comte de Monte-Cristo"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-28T19:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-le-comte-de-monte-cristo#2026-08-29T140000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Le comte de Monte-Cristo"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-29T14:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-le-comte-de-monte-cristo#2026-08-29T190000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Le comte de Monte-Cristo"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-29T19:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-le-comte-de-monte-cristo#2026-08-30T140000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Le comte de Monte-Cristo"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-30T14:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-le-comte-de-monte-cristo#2026-08-30T190000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Le comte de Monte-Cristo"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-30T19:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-jean-francois-mercier","@type":["http://schema.org/Event"],"http://schema.org/description":[{"@language":"fr","@value":"Jean-François Mercier est de retour sur scène pour présenter son quatrième spectacle en carrière! Avec son regard critique et pertinent, son ton souvent corrosif et parfois émouvant, il ramène son humour singulier sur les planches avec un seul objectif : brasser une fois de plus la cage de l'humour en confrontant nos convictions pour nous faire rire de nos travers, mais aussi des siens. Auteur de grand talent, animateur et comédien, c'est sur scène, nourri par les réactions du public, qu'il trouve sa voix d’humoriste pour nous faire rire et réfléchir."}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-232"}],"http://schema.org/name":[{"@language":"fr","@value":"Jean-François Mercier"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-22T20:00:00-04:00"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/a0TI3dpar_k?si=8sZIwvxIdvsxC16D"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/jean-francois-mercier.html#WebPage"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/jean-francois-mercier.html"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/jean-francois-mercier-2026.jpg"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-jean-francois-mercier#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-jean-francois-mercier#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/jean-francois-mercier.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:37:12.089-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/jean-francois-mercier.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-jean-francois-mercier#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E051Y\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"45"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-jean-francois-mercier#Offer_1","http://schema.org/price":[{"@value":"41"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-jean-francois-pauze","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/jean-francois-pauze.html"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/description":[{"@language":"fr","@value":"JF Pauzé, membre des Cowboys Fringants et auteur-compositeur des chansons du groupe (Félix – Auteur-compositeur de l’année 2024), fera ses débuts en solo avec un premier album à titre d’interprète, composé de 11 nouveaux titres originaux. Une sortie des plus attendues à l’automne 2025. JF remettra les pieds sur scène en 2026, entouré de musiciens, avec un spectacle généreux et festif qui comptera plus de 20 chansons. Un moment important dans le paysage musical québécois pour les saisons à venir! Veuillez noter que l'artiste pourrait inviter les spectateurs à se lever pendant le spectacle. Nous préférons vous en avertir."}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-09T20:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/jean-francois-pauze.html#WebPage"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/FTfHlsjKSy4?si=oTQAOIYX_IxNGRAn"}],"http://schema.org/name":[{"@language":"fr","@value":"JF Pauzé"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/jean-francois-pauze-2026.jpg"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-jean-francois-pauze#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-jean-francois-pauze#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/jean-francois-pauze.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-08T16:23:49.988-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/jean-francois-pauze.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-jean-francois-pauze#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E01OT\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"49"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-jean-francois-pauze#Offer_1","http://schema.org/price":[{"@value":"45"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-pascal-cameron","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Pascal Cameron"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/e75531ec-57cd-4370-a5a9-d1574b931b99"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/08ohoGXSxpM?si=rYmz7ELw8ln25upg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-20T20:00:00-04:00"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/pascal-cameron.html"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/pascal-cameron.jpg"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/description":[{"@language":"fr","@value":"Pascal Cameron présente son premier one man show un spectacle. qui est une représentation humoristique qui veut parler à tout le monde et de tout le monde. Par contre, il est important de noter que c'est un spectacle d'humour, donc les propos de cette représentation seraient inappropriés au Parlement ou à des funérailles mais plus que correct dans un pool party, Noël, un accouchement, la St-Jean ou à crier à travers la fenêtre de ton char quand tu es pogné dans le trafic. De plus, un spectacle. a comme objectif premier de faire rire. Fack, va pas me citer comme preuve quand tu t'obstines avec du monde «Pascal Cameron a dit ça l'autre jour». J'ai aucun diplôme sauf celui du meilleur livreur de journaux de la Montérégie 1999. (Après, j'ai lâché ma job pour me retirer au sommet) Un spectacle. c'est un show de stand up traditionnel avec des blagues fraiches. En nomination au Gala Les Olivier 2026: Capsule ou sketch radio humoristique de l’année * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7 «Du génie, à voir absolument!» - Joanie Duquette, CKOI «Cameron est un maître.» - Christian Saint-Pierre, Le Devoir «Il est encore possible, en stand-up, d'aller loin. Mais pour se rendre là, sans trébucher, il faut à la fois que les intentions d'un humoriste soient limpides et que l'exécution soit sans faille. Deux conditions auxquelles Cameron répond.» - Dom Tardif, La Presse «Il se fraye un chemin irrévérencieux grâce à l'auto-dérision, avec un zeste d'Yvon Deschamps.» - Myriam Segal, FM93"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/pascal-cameron.html#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-pascal-cameron#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-pascal-cameron#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/pascal-cameron.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:15:43.911-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/pascal-cameron.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-pascal-cameron#Offer_0","http://schema.org/price":[{"@value":"37"}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E01NZ\u0026CodeSiteClient=00000V"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-pascal-cameron#Offer_1","http://schema.org/price":[{"@value":"33"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-real-beland","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-242"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/real-beland.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Réal Béland"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/description":[{"@language":"fr","@value":"Comme chez nous, c’est le 6ème show de Réal Béland! Un spectacle qui entre dans l’intimité d’un comique qui comme le dit la rumeur a été conçu dans les coulisses d’une salle de spectacle. Après avoir passé sa vie dans les salles, Réal se sent chez lui lorsqu’il s’arrête en tournée chez vous! Une simplicité désarmante avec ces gags absurdes, son M. Latreille et beaucoup de surprises!!! * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/real-beland.html#WebPage"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2027-02-20T20:00:00-05:00"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/real-beland.jpg"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/p64e7A2d1vQ?si=DynNH8hYV8gqZWXg"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-real-beland#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-real-beland#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/real-beland.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:22:26.342-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/real-beland.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-real-beland#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"46"}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E01OK\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-real-beland#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"42"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-arnaud-soly","http://schema.org/description":[{"@language":"fr","@value":"Arnaud Soly revient sur scène avec son deuxième one-man-show : BON JUS. Il s’agit de son meilleur à ce jour (selon lui!). Pourquoi ça s’appelle BON JUS? C’est bizarre, hein? Ça serait trop compliqué à expliquer. Venez le découvrir en salle. On va rigoler, c’est promis. En nomination au Gala Les Olivier 2025: Capsule ou sketch web humoristique de l'année Texte de l’année : capsule ou sketch web humoristique «Arnaud Soly a trouvé une formule qui lui va comme un gant, entre le stand-up et le théâtre, entre l'absurde et le personnel. Il est drôle, touchant, intelligent et, surtout, très humain.» - La Presse «Avec ce spectacle-là, Arnaud Soly est solide. Il possédait la scène. C'est un spectacle très personnel, où il parvient à faire rire avec finesse, même sur des sujets sensibles.» - 98.5 Montréal «Solide sur les planches, l'humoriste est naturel, il a de l'aplomb, de l'énergie et il a du charisme.» - Journal de Montréal «C'est un super raconteur avec la bonne gestuelle et les mimiques faciales pour accompagner ses histoires. Tout au long de la soirée, il passe par plusieurs sujets comme les étapes de l'enfance, la fertilité, le rap, la cabane à sucre. Vite comme cela, ça a l'air décousu comme spectacle, mais je vous jure que tout fait du sens.» - MAtv"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/arnaud-soly.html"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/spec-qc-ca_artiste_arnaud-soly"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/arnaud-soly-2025.jpg"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/name":[{"@language":"fr","@value":"Arnaud Soly"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-25T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/arnaud-soly.html#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-arnaud-soly#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-arnaud-soly#Offer_1"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-14T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-arnaud-soly#2026-03-25T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-arnaud-soly#2026-10-14T200000-0400"}]},{"@id":"http://kg.footlight.io/resource/spec-qc-ca_artiste_arnaud-soly","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@value":"Arnaud Soly"}],"http://schema.org/image":[{"@value":"https://spec.qc.ca/uploads/artistes/arnaud-soly-2.jpg"}],"http://schema.org/sameAs":[{"@value":"http://kg.artsdata.ca/resource/K12-234"}],"http://schema.org/description":[{"@language":"fr","@value":"Improvisateur d’expérience, flutiste émérite, comédien et humoriste, Arnaud Soly est un véritable artiste multidisciplinaire. Connu par certains pour ses vidéos sur internet, il consacre la plus grande partie de son temps à l’humour scénique. En rodage pour son premier one-man show, on le retrouve sur plusieurs scènes aux 4 coins de la province où il peaufine son humour unique. Un artiste prometteur à découvrir!"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/arnaud-soly.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:22:06.172-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/arnaud-soly.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-arnaud-soly#Offer_0","http://schema.org/price":[{"@value":"42"}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://www.ovation.ca/00000V/fr/Event/?seriesId=8860763e%2Ddaa8%2D4582%2D895a%2D29135ee28594\u0026venueId=0E%2D0E0001"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-arnaud-soly#Offer_1","http://schema.org/price":[{"@value":"38"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-arnaud-soly#2026-03-25T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Arnaud Soly"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-25T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-arnaud-soly#2026-10-14T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Arnaud Soly"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-14T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-jeremy-demay","@type":["http://schema.org/Event"],"http://schema.org/description":[{"@language":"fr","@value":"Jérémy Demay, le plus québécois des humoristes français, résidant au Québec depuis plus de 17 ans, lance son tout nouveau spectacle intitulé Naturel. Avec ce 3e one-man-show, et entre l’écriture de ses livres plusieurs fois bestseller (150 000 copies vendues), Jérémy s’amuse à aller dans des zones plus personnelles et étonnera assurément par un contenu délicieusement audacieux qu’on ne lui soupçonnait pas… Avec une franchise à la fois désarmante mais ô combien hilarante, Jérémy se dévoile comme jamais avec de nombreuses confessions sur lui, sa vie et sa famille en exploitant le côté comique de situations qu’il a vécues. Avec Naturel, on retrouve sur scène un Jérémy toujours aussi énergique, charismatique et vif d’esprit qui tisse dès le départ un lien NATUREL avec le public. Avec son talent unique, Jérémy Demay réussit à la fois à faire rire à gorge déployée et à toucher droit au cœur, tout ça pour le grand plaisir de tous! «Si tu veux rire, va voir ce show! J'ai eu mal aux joues.» - La Semaine «Parfaitement drôle et touchant. À l'image du gaillard.» - Rouge FM «En grande forme, inspiré, très intelligent et d'autres fois léger.» - showbizz.net «Il est vraiment dans une classe à part.» - CKVL.fm «Un Jérémy en feu!» - Les musts à Montréal"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/jeremy-demay.html"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/2f230580-69b1-472e-996a-d965696cb643"}],"http://schema.org/name":[{"@language":"fr","@value":"Jérémy Demay"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-09T20:00:00-04:00"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/jeremy-demay-2026.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/jeremy-demay.html#WebPage"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/GOvDkxVrsZA?si=yoO541Q9SFErEl77"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-jeremy-demay#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-jeremy-demay#Offer_1"}]},{"@id":"http://kg.footlight.io/resource/2f230580-69b1-472e-996a-d965696cb643","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Jérémy Demay"}],"http://schema.org/hasOccupation":[{"@value":"Humoriste"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}],"http://schema.org/url":[{"@value":"https://jeremydemay.ca/"}],"http://schema.org/sameAs":[{"@value":"http://www.wikidata.org/entity/Q16802031"},{"@value":"https://www.facebook.com/JeremyDemayOfficiel/"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/jeremy-demay.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:21:57.922-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/jeremy-demay.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-jeremy-demay#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"48"}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E00WA\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-jeremy-demay#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"44"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-ge-maroc","@type":["http://schema.org/Event"],"http://schema.org/description":[{"@language":"fr","@value":"Une aventure immersive et surprenante sur les routes du Maroc. Parti depuis la Belgique avec la ferme intention de rejoindre Dakhla, la Mecque du kitesurf dans l’extrême sud du pays, Marc déroule les kilomètres avec poésie, sensibilité et une profonde envie de découvrir un Maroc traditionnel. Un voyage aventureux marqué par des rencontres inattendues dans un pays à la croisée des continents européen et africain."}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/656dca7a-5549-494c-acbf-8c05662b23bf"}],"http://schema.org/name":[{"@language":"fr","@value":"Les Grands Explorateurs"}],"http://schema.org/recordedIn":[{"@value":"https://player.vimeo.com/video/1076127156?badge=0\u0026autopause=0\u0026player_id=0\u0026app_id=58479"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/autres-evenements/ge-maroc.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-03T13:30:00-04:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/ge-maroc.html#WebPage"}],"http://schema.org/keywords":[{"@value":"autres-evenements"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K10-427"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/ge-maroc.html"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-ge-maroc#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-ge-maroc#Offer_1"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-ge-maroc#Offer_2"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/ge-maroc.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:40:18.435-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/ge-maroc.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-ge-maroc#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E050D\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"33.92"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-ge-maroc#Offer_1","http://schema.org/price":[{"@value":"31.04"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"60 ans et +"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-ge-maroc#Offer_2","http://schema.org/price":[{"@value":"22.42"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"25 ans et moins"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-p-o-forget","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K5-380"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/name":[{"@language":"fr","@value":"P-O Forget"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/p-o-forget-2026.jpg"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/p-o-forget.html"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-27T20:00:00-04:00"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/cBZkwCrOKUI"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/p-o-forget.html#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/description":[{"@language":"fr","@value":"Après un premier spectacle acclamé pour l'originalité des angles et la précision des punchs, P-O Forget vous convie maintenant au rodage d'une œuvre encore plus éclatée, encore plus incisive et avec votre aide, encore plus hilarante. Venez découvrir ou retrouver cette star montante de l'humour Québécois dans sa version la plus authentique. En nomination au Gala Les Olivier 2026: Numéro d’humour de l’année Capsule ou sketch Web humoristique de l’année Découverte de l’année * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-p-o-forget#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-p-o-forget#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/p-o-forget.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:18:46.541-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/p-o-forget.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-p-o-forget#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04X4\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"35"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-p-o-forget#Offer_1","http://schema.org/price":[{"@value":"31"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-salebarbes","http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/4wHxQ53G8zw?si=JPsViblcfz7kRxfO"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/salebarbes-2027.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"Salebarbes propose un tout nouveau spectacle! Veuillez noter que les artistes pourraient inviter les spectateurs à se lever pendant le spectacle. Nous préférons vous en avertir."}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K10-293"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/salebarbes.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Salebarbes"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2027-03-25T20:00:00-04:00"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/salebarbes.html#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-salebarbes#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2027-03-28T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-salebarbes#2027-03-25T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-salebarbes#2027-03-26T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-salebarbes#2027-03-27T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-salebarbes#2027-03-28T200000-0400"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/salebarbes.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:10:52.433-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/salebarbes.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-salebarbes#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E01AA\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"59"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-salebarbes#2027-03-25T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Salebarbes"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2027-03-25T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-salebarbes#2027-03-26T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Salebarbes"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2027-03-26T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-salebarbes#2027-03-27T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Salebarbes"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2027-03-27T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-salebarbes#2027-03-28T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Salebarbes"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2027-03-28T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-tai-tl","@type":["http://schema.org/Event"],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/tai-tl.html"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-25T20:00:00-04:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/tai-tl.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/tai-tl.html#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-tai-tl#Offer_0"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/tai-tl.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:22:16.296-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/tai-tl.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-tai-tl#Offer_0","http://schema.org/url":[{"@value":"https://www.ovation.qc.ca/pesite.asp?CodeEtablissement=000014\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"0"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-ouellet","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Ouellet"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/dad71250-8ec8-4b41-aa18-47eef056a04c"},{"@id":"http://kg.artsdata.ca/resource/PerformingArtsEvent"}],"http://schema.org/keywords":[{"@value":"soirees-cabaret"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/ouellet.html"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/soirees-cabaret/ouellet-humoriste.jpg"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/MI4mwdLkfKc?si=fWtyjgINZXtTj-c2"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-22T20:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/ouellet.html#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/description":[{"@language":"fr","@value":"OUELLET – Le stand-up mordant du moment! Vous l’avez peut-être vu au ComédiHa! Fest, à Juste pour rire ou en première partie de Jean-Michel Anctil… Mais cette fois, Ouellet débarque avec SON show, 100 % inédit et pleinement assumé. Gaspésien fier et barbu attachant, il incarne cette génération d’humoristes qui allient observations sociales aiguisées et efficacité comique redoutable. Révélation du ComédiHa! Fest 2023, il ne recule devant aucun sujet et n’épargne personne — surtout pas ceux qu’il aime! Ouellet peaufine en ce moment même son prochain one-man-show. Un rodage cru, drôle, mordant et surtout authentique, livré avec toute la fougue d’un gars de chez nous. C’est franc, c’est drôle, et ça frappe juste. Venez voir Ouellet avant que tout le monde en parle. * Formule cabaret au foyer de la salle Odyssée / Ambiance intime et conviviale / Bar en fonction / Places limitées"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-ouellet#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-ouellet#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/ouellet.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-20T16:23:52.634-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/ouellet.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-ouellet#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04OD\u0026CodeSiteClient=00000V"}],"http://schema.org/price":[{"@value":"28"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-ouellet#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"24"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-megan-brouillard","http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/f1c5fdfe-17d2-410b-b10d-5b4a0c9be1e8"}],"http://schema.org/name":[{"@language":"fr","@value":"Mégan Brouillard"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/megan-brouillard.html#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/description":[{"@language":"fr","@value":"Le chiendent, c’est subjectif. La mauvaise herbe, c’est quelque chose qui pousse là où toi, tu ne voulais pas. Ce qui est du chiendent pour l’un ne l’est peut-être pas pour l’autre et ce qui est du chiendent quelque part, ne l’est pas ailleurs… Mon spectacle tourne autour de cette perspective, des différentes perceptions de chacun et chacune et autour de mes propres perceptions. Le premier one-woman-show de Mégan Brouillard est un spectacle mordant et authentique, où Mégan présente elle et les siens, sans filtre et toujours de façon hilarante! Lauréate au Gala Les Olivier 2025: Découverte de l'année Capsule ou sketch web humoristique de l'année En nomination au Gala Les Olivier 2026: Spectacle d'humour de l'année Texte de l’année / Spectacle d’humour de l’année Spectacle d’humour / Meilleur vendeur de l’année Olivier de l’année «Difficile de ne pas tomber sous le charme de la jeune humoriste. La livraison et la tournure de ses blagues sont solides, effiaces et n'ont rien à envier aux humoristes établis.» - Journal de Montréal «J'ai été extrêmement impressionnée, la gueule à terre en bon québécois, un sens de la répartie et du rythme soutenu, des blagues hyper punchées. J'ai eu l'impression d'assister à la naissance d'une grande humoriste.» - 98,5 FM «J'ai tellement ri que j'avais mal et que j'étais épuisée d'avoir trop de plaisir! J'ai eu une superbe soirée, un plaisir fou. Vraiment une belle découverte!» - ICI Première «La dernière fois qu'une humoriste a offert un premier spectacle aussi achevé, à un aussi jeune âge, il s'agissait peut-être de Louis-José Houde. Elle est partie pour la gloire!» - La Presse «Le spectacle le plus hillarant de toutes les tournées d'humour en ce moment. Tous genres confondus!» - Rhytme FM «Mégan Brouillard n'a rien à envier aux vétérans de son domaine.» - Journal de Québec «L'humoriste ratisse large et saura charmer tous les publics.» - Le Soleil «Un premier one-woman-show explosif.» - Showbizz.net"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/megan-brouillard.html"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-13T20:00:00-04:00"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/NlH_SZ-iBzA?si=v4KQZdpjxk5s5QN1"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/megan-brouillard.jpg"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-megan-brouillard#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-megan-brouillard#Offer_1"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-05T20:00:00-05:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-megan-brouillard#2026-05-13T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-megan-brouillard#2026-11-05T200000-0500"}]},{"@id":"http://kg.footlight.io/resource/f1c5fdfe-17d2-410b-b10d-5b4a0c9be1e8","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Mégan Brouillard"}],"http://schema.org/hasOccupation":[{"@value":"Humoriste"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/megan-brouillard.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:11:44.303-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/megan-brouillard.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-megan-brouillard#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E01MJ\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/price":[{"@value":"39"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-megan-brouillard#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"35"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-megan-brouillard#2026-05-13T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Mégan Brouillard"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-13T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-megan-brouillard#2026-11-05T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Mégan Brouillard"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-05T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-william-cloutier","@type":["http://schema.org/Event"],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/name":[{"@language":"fr","@value":"William Cloutier"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/william-cloutier.html#WebPage"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/william-cloutier.html"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K2-2050"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/william-cloutier.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-16T20:00:00-04:00"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/swr2OBfaQZo?si=gy4s1wFDS9CRmEVr"}],"http://schema.org/description":[{"@language":"fr","@value":"Révélé par Star Académie 2021 et acclamé pour son rôle dans Starmania, William Cloutier s’est rapidement imposé comme une figure incontournable de la chanson québécoise. Son premier album On ira cumule plus de six millions d’écoutes et lui a valu une nomination à l’ADISQ. Après une tournée marquante et 361 représentations de Starmania en Europe et au Québec, il revient avec L’amour, un deuxième album qui explore le sentiment sous toutes ses formes. Entouré de collaborateurs de renom, William continue d’éblouir avec sa voix puissante et son charisme naturel. de son album L'amour : «Après avoir brillé dans Starmania en Europe pendant trois ans, William Cloutier retrouve le public québécois avec L’amour, deuxième album très pop dans lequel l’incroyable agilité de sa voix est mise en valeur […] il y déploie sa voix virtuose qui frôle les stratosphères» — Josée Lapointe, La Presse «Un petit bijou pop absolument irrésistible […] avec les rythmes colorés, lumineux et irrésistiblement dansants de son second album, William Cloutier continue de s’imposer comme le nouveau prince de la pop québécoise» — Bruno Lapointe, Le Journal de Montréal «William Cloutier est ce genre d’artiste qui peut faire cohabiter plusieurs styles musicaux sans pour autant qu’on s’y perde» — Mario Boulianne, Le Droit"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-william-cloutier#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-william-cloutier#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/william-cloutier.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:15:07.731-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/william-cloutier.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-william-cloutier#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04XN\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"44"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-william-cloutier#Offer_1","http://schema.org/price":[{"@value":"40"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-osg-oiseau-de-feu","@type":["http://schema.org/Event"],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/osg-oiseau-de-feu.html#WebPage"}],"http://schema.org/name":[{"@language":"fr","@value":"L'Orchestre symphonique de Gatineau"}],"http://schema.org/description":[{"@language":"fr","@value":"Surgissant des cendres, l’oiseau légendaire prend son envol… Sous la direction inspirée du chef d’orchestre, l’Orchestre symphonique de Gatineau vous invite à une soirée où chaque note semble effleurer l’immensité. Découvrez le frisson aérien de Rossini avec l’ouverture de La Pie Voleuse, l’envoûtante symphonie des oiseaux de Respighi, et la danse effervescente du Vol du Bourdon de Rimsky-Korsakov. Mais l’apothéose de cette soirée viendra avec la splendeur ardente de L’Oiseau de Feu de Stravinsky, une œuvre où le feu et la musique s’entrelacent, où les flammes prennent vie et l’âme s’élève. Chaque accord vous propulsera dans un tourbillon sonore, dans un vol effréné vers l’infini. Réservez votre évasion musicale dès aujourd'hui! PROGRAMME Rossini : Ouverture de «La pie voleuse» (La Gaza Ladra) Respighi : Poème symphonique «Les oiseaux» (Gli Uccelli) La colombe Le coq Le rossignol Le coucou Rimski-Korsakov: Le vol du bourdon (Le Conte du Tsar Saltan) Stravinsky: L’Oiseau de feu (Suite 1945) Yves Léveillé, chef Orchestre symphonique de Gatineau (OSG)"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K5-165"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-11T19:30:00-04:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/autres-evenements/osg-loiseau-de-feu.jpg"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"autres-evenements"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/osg-oiseau-de-feu.html"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-osg-oiseau-de-feu#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-osg-oiseau-de-feu#Offer_1"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-osg-oiseau-de-feu#Offer_2"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/osg-oiseau-de-feu.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:12:26.552-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/osg-oiseau-de-feu.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-osg-oiseau-de-feu#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04XW\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"66"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-osg-oiseau-de-feu#Offer_1","http://schema.org/price":[{"@value":"30"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"étudiant"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-osg-oiseau-de-feu#Offer_2","http://schema.org/price":[{"@value":"20"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"12 ans et -"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-jose-gaudet-2","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/b3e28c59-2c93-4210-ab72-47d8555ec99d"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/hic6FeJci6I?si=H4wWUzIvd0XrU8-s"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/jose-gaudet.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-08T20:00:00-04:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/jose-gaudet-2.html"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/jose-gaudet-2.html#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/name":[{"@language":"fr","@value":"José Gaudet"}],"http://schema.org/description":[{"@language":"fr","@value":"Humoriste, José Gaudet nous fait rire depuis déjà très longtemps! Il a à son actif plus de 25 ans d’animation radio, deux tournées de spectacles bien récompensées, animation de galas et 10 ans à la barre de talk-shows, dont Ça finit bien la semaine. José n’arrivera pas sur la pointe des pieds, il prépare un spectacle de stand-up pur et dur. Il abordera sa vision des choses sur la vie d’aujourd’hui et ses réflexions sur la société avec son humour authentique, son franc parlé et son énergie éclatante que nous connaissons bien. À 53 ans, il arrive avec son premier one-man-show. C’est le cas de le dire… Y’était temps! «Il est entré sur scène comme un taureau qui arrivait dans l’arène! J’ai vu toute l’expérience, le gars confiant, le travaillant, le gars qui savait exactement où il allait. J’ai ri du début à la fin. Tellement une belle soirée.» - Marie-Eve Janvier, Rythme 105.7 «Le medley promis de ses personnages les plus mémorables de la glorieuse époque des Grandes Gueules est un pot-pourri jubilatoire. En quelques minutes, l’humoriste rappelle pourquoi il appartient à une catégorie à part.» - Dominic Tardif, La Presse «Ce premier spectacle solo est bien ficelé, rythmé et atteint facilement ses cibles. La bouille de José Gaudet et sa façon très imagée de raconter ses anecdotes captivent et retiennent l’attention du public, qui a ri très fort toute la soirée.» - Frédérique De Simone, Journal de Montréal"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-jose-gaudet-2#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-jose-gaudet-2#Offer_1"}]},{"@id":"http://kg.footlight.io/resource/b3e28c59-2c93-4210-ab72-47d8555ec99d","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"José Gaudet"}],"http://schema.org/hasOccupation":[{"@language":"fr","@value":"Humoriste"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}],"http://schema.org/sameAs":[{"@value":"http://kg.artsdata.ca/resource/K5-1035"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/jose-gaudet-2.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:21:16.783-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/jose-gaudet-2.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-jose-gaudet-2#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://www.ovation.ca/00000V/fr/Event/?seriesId=a0f50cf7%2D1e9c%2D4ea2%2D9211%2D1f054a1d1f38\u0026venueId=0E%2D0E0001"}],"http://schema.org/price":[{"@value":"48"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-jose-gaudet-2#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"44"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_podcast-tout-le-monde-hait","@type":["http://schema.org/Event"],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/podcast/tout-le-monde-hait.html#WebPage"}],"http://schema.org/name":[{"@language":"fr","@value":"Tout le monde s'haït"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/podcast/tout-le-monde-hait.html"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-15T20:00:00-04:00"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/podcast/tout-le-monde-hait.jpg"}],"http://schema.org/keywords":[{"@value":"podcast"}],"http://schema.org/description":[{"@language":"fr","@value":"Marylène Gendron et Sam Cyr, collègues humoristes et surtout meilleurs amis, sont deux bêtes extrêmement complexées. Avec leurs invités, ils se livrent au sujet de toutes ces imperfections qui les obsèdent, ils en rient, ils en pleurent (peut-être pas quand même!). En gros, ils veulent savoir si tout le monde s’haït autant qu’eux. En nomination au Gala Les Olivier 2025: Podcast humoristique sans script de l’année"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_podcast-tout-le-monde-hait#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_podcast-tout-le-monde-hait#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/podcast/tout-le-monde-hait.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:25:11.568-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/podcast/tout-le-monde-hait.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_podcast-tout-le-monde-hait#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"29"}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04XD\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_podcast-tout-le-monde-hait#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"25"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-andre-philippe-gagnon","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K13-15"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"André-Philippe Gagnon"}],"http://schema.org/keywords":[{"@value":"varietes"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/varietes/andre-philippe-gagnon.html"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/varietes/andre-philippe-gagnon.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/varietes/andre-philippe-gagnon.html#WebPage"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-06-08T20:00:00-04:00"}],"http://schema.org/description":[{"@language":"fr","@value":"Dans ce nouveau spectacle, André-Philippe Gagnon continue de nous épater avec ses prouesses vocales, comme il le fait depuis 40 ans. En plus de 70 imitations virtuoses, il nous partage pour la première fois des anecdotes personnelles de son incroyable carrière, livrées avec humour et authenticité, pour nous faire découvrir l’homme derrière les milles voix. Il revisite les plus belles chansons des dernières décennies qui nous accompagnent dans nos vies. Un moment unique, haut en rire, en émotion et en musique! En nomination au Gala Les Olivier 2026: Conception visuelle de l’année «Quarante ans après l'année où il a été révélé au monde entier grâce à son apparition au Tonight Show de Johnny Carson, Gagnon n'a aucune difficulté à meubler une centaine de minutes sans temps mort.» - Ian Bussières, Le Soleil «Tout ce qu'on a envie de voir d'André-Philippe Gagnon, on le voit. C'est un condensé du meilleur d'André-Philippe Gagnon.» - Tanya Beaumont, ICI Première Québec «Sa proposition a [...] grandement plu aux spectateurs. Ces derniers [...] se levant de leurs sièges [...] pour ovationner l'imitateur après les gros numéros comme We Are the World et Imagine de John Lennon.» - Frédérique De Simone, Journal de Montréal «La jeune soixantaine, toujours prêt à épater la galerie. Gagnon imite comme il respire.» - Élie Castiel, Kino Culture Montréal"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-andre-philippe-gagnon#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-andre-philippe-gagnon#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/varietes/andre-philippe-gagnon.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:40:28.058-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/varietes/andre-philippe-gagnon.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-andre-philippe-gagnon#Offer_0","http://schema.org/price":[{"@value":"50"}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04YA\u0026CodeSiteClient=00000V"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-andre-philippe-gagnon#Offer_1","http://schema.org/price":[{"@value":"46"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-dominic-paquet-2","http://schema.org/description":[{"@language":"fr","@value":"Après avoir vendu plus de 250 000 billets de son dernier spectacle, le populaire humoriste Dominic Paquet est de retour avec un tout nouveau one-man-show: J’comprends la game. Le 5e spectacle solo de Dominic se veut un gros clin d'œil à ces fameuses personnes qui, grâce à leur expérience de vie, sont certaines de comprendre des choses qui sont inaccessibles pour le commun des mortels… J’comprends la game est un laboratoire humoristique où la folie prend tout son sens alors que Dominic explore, examine et met en évidence toutes sortes de théories farfelues afin de donner des réponses à des questionnements que personne n’a jamais eus! Encore une fois, Dominic Paquet fait rire aux éclats, surprend et séduit par sa capacité à incarner des personnages uniques et à créer des expressions qui marqueront votre imaginaire! Le public veut des réponses, donc moi je suis là pour nourrir les esprits. Quand le spectacle se termine, je sens que les gens quittent la salle en se disant «maintenant moi aussi j'comprends la game!». - Dominic Paquet Le spectacle est présenté par Alimentation Mon Quartier, qui vous invite à vivre une expérience unique, signée par les artisans et producteurs alimentaires d’ici. En nomination au Gala Les Olivier 2026: Spectacle d’humour / Meilleur vendeur de l’année Olivier de l’année"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/cgmFKhZDyDo?si=zAJJbgA6bv5qBLVN"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/dominic-paquet-2.html#WebPage"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/dominic-paquet-2.html"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/dominic-paquet-2026.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-19T20:00:00-05:00"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/spec-qc-ca_artiste_dominic-paquet"}],"http://schema.org/name":[{"@language":"fr","@value":"Dominic Paquet"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-dominic-paquet-2#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-dominic-paquet-2#Offer_1"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-06-17T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-dominic-paquet-2#2026-02-19T200000-0500"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-dominic-paquet-2#2026-02-20T200000-0500"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-dominic-paquet-2#2026-06-17T200000-0400"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/dominic-paquet-2.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:28:31.871-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/dominic-paquet-2.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-dominic-paquet-2#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E00W2\u0026CodeSiteClient=00000V"}],"http://schema.org/price":[{"@value":"53"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-dominic-paquet-2#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"49"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-dominic-paquet-2#2026-02-19T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Dominic Paquet"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-19T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-dominic-paquet-2#2026-02-20T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Dominic Paquet"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-20T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-dominic-paquet-2#2026-06-17T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Dominic Paquet"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-06-17T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-rue-duplessis","http://schema.org/name":[{"@language":"fr","@value":"Rue Duplessis : ma petite noirceur"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/rendez-vous-theatre/rue-duplessis.html#WebPage"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/rendez-vous-theatre/rue-duplessis.html"}],"http://schema.org/description":[{"@language":"fr","@value":"Qui aurait cru qu’un petit gars de la rue Duplessis à Drummondville, qui a grandi dans la pauvreté, la peur et les préjugés, deviendrait un sociologue estimé et un animateur réputé à Radio-Canada? Jean-Philippe Pleau vient d’une famille bienveillante dans laquelle l’ambition intellectuelle suscitait la méfiance : «L’école, c’est pour ceux qui pètent plus haut que leur trou» y disait-on. Nourri au «macarouni» gratiné au fromage Singles de Kraft, celui qu’on connaît surtout pour ses réflexions savoureuses à C’est fou… (avec Serge Bouchard) et à Réfléchir à voix haute est aujourd’hui étranger au monde d’où il vient, sans vraiment appartenir à celui dans lequel il a abouti. Rue Duplessis, c’est son histoire, celle de sa «migration intérieure» de transfuge de classe; mais c’est aussi un regard critique sur les inégalités socioéconomiques qui dictent encore trop souvent le destin et les aspirations de celles et ceux qui sont «né·es pour un p’tit pain». Un parcours drôle et émouvant, empreint de courage, de nostalgie… et de sociologie. Jean-Philippe Pleau, qui s’est faufilé entre les mailles d’un système qui ne le prédisposait pas à devenir auteur, dit qu’il écrit pour venger les siens». Après l'énorme succès de son roman autobiographique Rue Duplessis: ma petite noirceur, il s’apprête maintenant à franchir une autre étape: monter sur scène pour défendre son récit si personnel devant des centaines de spectateur·rice·s, soir après soir. Notre codirecteur artistique David Laurin, qui en signe l’adaptation, et la metteuse en scène Marie-Ève Milot (Mama, Docteure) en proposent une version théâtrale riche et surprenante au fil de laquelle l’auteur nous racontera sa propre révolution tranquille. Au-delà de l’intime, ce spectacle trouvera assurément une résonance toute particulière chez de nombreux·ses Québécois·es qui, comme Jean-Philippe, naviguent entre deux mondes; ils et elles y reconnaîtront leur propre vécu, leur fierté et leurs contradictions. «Dans cette pièce, l’animateur Jean-Philippe Pleau nous convie à une émission spéciale, de celles qui se transforment en véritable portail. En se livrant devant le public, avec toutes ses déchirures, il mêle sa voix à celles de son milieu d’origine, révélant toute la portée de son livre. Cette adaptation théâtrale s’adresse à celles et ceux qui ont, comme l’auteur, le “cul entre deux chaises”. Un théâtre pour se penser et se rêver.» – Marie-Ève Milot, metteuse en scène Texte Jean-Philippe Pleau Adaptation théâtrale David Laurin Mise en scène Marie-Ève Milot Distribution Steve Laplante, Michel-Maxime Legault et Jean-Philippe Pleau Production Duceppe en collaboration avec la Place des Arts Équipe de création Conseil dramaturgique : Andréane Roy Assistance à la m.e.s. : Josianne Dulong-Savignac Scénographie : Geneviève Lizotte Costumes : Cynthia St-Gelais, assistée de Juliette Dubé-Tyler Éclairages : Paul Chambers Musique : Antoine Berthiaume Accessoires : Camille Walsh Maquillage : Sylvie Rolland-Provost Coach de voix : Luc Bourgeois Répétiteur : Frédérik Fournier Doublure de Jean-Philippe Pleau : Jean-François Poulin «C'est sincère, c’est humain, c’est vrai. Ça raconte un Québec qu’on voit très peu sur les planches et ce Québec-là est plus que généreusement invité dans la salle.» – Claudia Hébert (Tout un matin, ICI Première) «Un témoignage qui résonne dans les méandres de nos âmes. Entre les mains de David Laurin, qui en a élaboré l’adaptation, et de Marie-Ève Milot, qui signe la mise en scène, cette transmutation se révèle franchement probante. Le duo conjugue admirablement finesse et ingéniosité, la sensibilité [imprègne] la production, [au] ton moins âpre, voire plus tendre, que celui du livre.» – Sophie Pouliot, Le Devoir «Steve Laplante est exceptionnel. Michel-Maxime Legault est exceptionnel. Cette adaptation est exceptionnelle et la mise en scène est un tour de force immense.» – Pénélope McQuade, ICI Première «Bouleversant. Une œuvre intime, intelligente et profondément touchante. On rit, on s’émeut, on réfléchit. On en ressort avec plus qu’un souvenir de théâtre: une réflexion profonde sur ce que signifie grandir au Québec, sur ce que l’on hérite et sur ce que l’on choisit de transmettre. Un spectacle à voir absolument. Un vrai coup de cœur.» – Minh-Tu Le, Passion Mtl «Pleau décortique son émancipation socioéconomique, avec un procédé théâtral efficace: il se décuple en trois versions de lui-même (...) Ce trio comique exprime parfaitement le contraste qui l’habite, lui et beaucoup d’autres, et ce sentiment de n’appartenir à aucun de ces mondes au final.» – Amélie Bélanger, Sors-tu.ca"}],"http://schema.org/keywords":[{"@value":"rendez-vous-theatre"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/jJTDziV7GIQ?si=WCeUJ0o5yeldbORV"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-13T20:00:00-04:00"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/rendez-vous-theatre/rue-duplessis.jpg"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-rue-duplessis#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-rue-duplessis#Offer_1"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-rue-duplessis#Offer_2"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-rue-duplessis#Offer_3"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-14T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-rue-duplessis#2026-03-13T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-rue-duplessis#2026-03-14T200000-0400"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/rendez-vous-theatre/rue-duplessis.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:19:47.269-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/rendez-vous-theatre/rue-duplessis.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-rue-duplessis#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E01P3\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/price":[{"@value":"54"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-rue-duplessis#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"50"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-rue-duplessis#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"30 ans et - / 60 ans et +"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"49"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-rue-duplessis#Offer_3","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée 30 ans et - / 60 ans et +"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"45"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-rue-duplessis#2026-03-13T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Rue Duplessis : ma petite noirceur"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-13T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_rendez-vous-theatre-rue-duplessis#2026-03-14T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Rue Duplessis : ma petite noirceur"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-14T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-lou-adriane-cassidy","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K5-1041"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/name":[{"@language":"fr","@value":"Lou-Adriane\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tCassidy"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/lou-adriane-cassidy.jpg"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/ezEwmJwoV2A?si=LV8orH-JTf-DPVuU"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-27T20:00:00-04:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/lou-adriane-cassidy.html"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/lou-adriane-cassidy.html#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"Après avoir marqué la première moitié de 2025 avec Journal d’un loup-garou, dont l’album et le spectacle ont récoltés une pluie d’éloges et un succès populaire sans précédent pour l’artiste, Lou-Adriane Cassidy nous revient avec Triste Animal, spectacle tiré de son quatrième album du même nom. Sur scène comme sur disque, ce nouveau chapitre de son travail nous donne un accès inédit à cette interprète d’exception qui, entourée de cinq musicien.nes, nous touchera au plus profond de l’âme tant par la mature sobriété de son interprétation que par la frénétique énergie qui font sa réputation depuis toujours. Lauréate au Gala de l'ADISQ 2025 dans les catégories : Album de l'année - Choix de la critique / Pop (Journal d'un Loup-Garou) Artiste féminine de l'année Chanson de l'année (Dis-moi, Dis-moi, Dis-moi - Radio Edit) Spectacle de l'année (Journal d'un Loup-Garou) Mise en scène et scénographie de l'année (Journal d'un Loup-Garou) Auteur.es ou compositeur.trices de l'année - Lou-Adriane Cassidy, Alexandre Martel Veuillez noter que l'artiste pourrait inviter les spectateurs à se lever pendant le spectacle. Nous préférons vous en avertir."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-lou-adriane-cassidy#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-lou-adriane-cassidy#Offer_1"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-lou-adriane-cassidy#Offer_2"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/lou-adriane-cassidy.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:30:05.349-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/lou-adriane-cassidy.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-lou-adriane-cassidy#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E050Q\u0026CodeSiteClient=00000V"}],"http://schema.org/price":[{"@value":"40"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-lou-adriane-cassidy#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"36"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-lou-adriane-cassidy#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"étudiants"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"32"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-alexandre-champagne","@type":["http://schema.org/Event"],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/3bab2ea8-4d08-4da2-ab09-a3294f921b33"}],"http://schema.org/name":[{"@language":"fr","@value":"Alexandre Champagne"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/LqkxZAQCcuo?si=mjxGWyMPg6zxfEcy"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/alexandre-champagne.html"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/alexandre-champagne-2025.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-07T20:00:00-05:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.artsdata.ca/resource/PerformingArtsEvent"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/alexandre-champagne.html#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/description":[{"@language":"fr","@value":"Dans La grande désorganisation, Alexandre Champagne, audacieusement éparpillé, revient sur scène après 12 ans d’absence. Il nous propose un accès à sa tête, remplie d’anecdotes, de réflexions et de diagnostics qui cachent parfois de super pouvoirs. C’est sans détour, qu’il raconte ses observations et décortique ses nouveaux rôles : père, entrepreneur, photographe et plus encore. Avec authenticité, fougue, légèreté et autodérision, Alexandre nous entraîne dans les confins de son cerveau. Et on nous glisse à l’oreille qu’il y aurait aussi présence de sa guitare… Alexandre Champagne est le créateur de Contrat d'gars, récipiendaire de deux Olivier, puis cofondateur de Trois fois par jour et de la Fondation Le CIEL. Avec La grande désorganisation, il se permet de revenir à son premier amour : l’humour. * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7 «Ce premier spectacle met en lumière tout le génie comique de l'humoriste à l'humour décalé.» - Frédérique De Simone, Journal de Montréal «Un premier opus simple et intime, qui a aisément su atteindre ses cibles.» - Frédérique De Simone, Journal de Montréal «Mélomane, Alexandre Champagne a également réservé une portion musicale [...] - assurément l'un des numéros les plus efficaces de la soirée.» - Frédérique De Simone, Journal de Montréal"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-alexandre-champagne#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-alexandre-champagne#Offer_1"}]},{"@id":"http://kg.footlight.io/resource/3bab2ea8-4d08-4da2-ab09-a3294f921b33","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Alexandre Champagne"}],"http://schema.org/hasOccupation":[{"@language":"fr","@value":"Humoriste"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}],"http://schema.org/sameAs":[{"@value":"http://www.wikidata.org/entity/Q53852068"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/alexandre-champagne.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:37:20.287-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/alexandre-champagne.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-alexandre-champagne#Offer_0","http://schema.org/price":[{"@value":"35"}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04DR\u0026CodeSiteClient=00000V"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-alexandre-champagne#Offer_1","http://schema.org/price":[{"@value":"31"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mona-de-grenoble-de-la-poudre-aux-yeux","http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/6804e1a6-d2c6-4962-b0ee-02fed55513cc"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/mona-de-grenoble-de-la-poudre-aux-yeux.html#WebPage"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/mona-de-grenoble-de-la-poudre-aux-yeux.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Mona de Grenoble"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/mona-de-grenoble.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-19T20:00:00-04:00"}],"http://schema.org/description":[{"@language":"fr","@value":"J’aurais pu appeler ce show d’humour-là “Étalon en talons” ou “Entre Bâton Rouge et matante” mais après j’ai désaoulé pis je me suis rendue compte que vous allez sûrement vous reconnaître dans mon personnage que certains décrivent comme étant “flamboyant, grivois et solvable depuis Big Brother”. Parce que, dans le fond, on le sait, les paillettes, les robes pis les grosses perruques c’est juste “de la poudre aux yeux”. En nomination au Gala Les Olivier 2026: Spectacle d'humour de l'année Texte de l’année / Spectacle d’humour de l’année Mise en scène de l’année Conception visuelle de l’année"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mona-de-grenoble-de-la-poudre-aux-yeux#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mona-de-grenoble-de-la-poudre-aux-yeux#Offer_1"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-10T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mona-de-grenoble-de-la-poudre-aux-yeux#2026-05-19T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mona-de-grenoble-de-la-poudre-aux-yeux#2026-09-10T200000-0400"}]},{"@id":"http://kg.footlight.io/resource/6804e1a6-d2c6-4962-b0ee-02fed55513cc","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Mona de Grenoble"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}],"http://schema.org/hasOccupation":[{"@value":"Humoriste"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/mona-de-grenoble-de-la-poudre-aux-yeux.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:23:14.992-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/mona-de-grenoble-de-la-poudre-aux-yeux.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mona-de-grenoble-de-la-poudre-aux-yeux#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E01JW\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"43"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mona-de-grenoble-de-la-poudre-aux-yeux#Offer_1","http://schema.org/price":[{"@value":"39"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mona-de-grenoble-de-la-poudre-aux-yeux#2026-05-19T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Mona de Grenoble"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-19T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-mona-de-grenoble-de-la-poudre-aux-yeux#2026-09-10T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Mona de Grenoble"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-10T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-ima","@type":["http://schema.org/Event"],"http://schema.org/description":[{"@language":"fr","@value":"À la veille de ses 25 ans de carrière, Ima prend la route avec ses musiciens pour nous offrir un spectacle rempli de soleil et de renouveau. Dans sa candeur lumineuse, l’autrice-compositrice-interprète nous transporte dans un voyage musical aux couleurs pop, soul, bossa nova avec des notes de jazz. Elle revisite ses plus grands succès (Le temps des fleurs, Drôle de vie... ) et nous surprend avec ses nouvelles chansons issues de son neuvième album qui paraîtra à l’automne 2025. Ce spectacle se veut une ode à la liberté, à la vie avec ses hauts et ses bas. Une Ima en voix, libre, en pleine possession de ses moyens et fébrile de retrouver son public et de connecter avec lui. C’est un rendez-vous tout en authenticité, en nostalgie et en beauté. Un moment hors du temps qui touchera les coeurs et qui fera du bien. * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/name":[{"@language":"fr","@value":"IMA"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/ima.html"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/ima.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-05T20:00:00-05:00"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/8NYRrHliejo?si=dpHPuWTeEQq1nfUE"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/ima.html#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-ima#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-ima#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/ima.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:15:59.833-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/ima.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-ima#Offer_0","http://schema.org/price":[{"@value":"32"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E050J\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-ima#Offer_1","http://schema.org/price":[{"@value":"28"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-jf-pauze","http://schema.org/description":[{"@language":"fr","@value":"JF Pauzé, membre des Cowboys Fringants et auteur-compositeur des chansons du groupe (Félix – Auteur-compositeur de l’année 2024), fera ses débuts en solo avec un premier album à titre d’interprète, composé de 11 nouveaux titres originaux. Une sortie des plus attendues à l’automne 2025. JF remettra les pieds sur scène en 2026, entouré de musiciens, avec un spectacle généreux et festif qui comptera plus de 20 chansons. Un moment important dans le paysage musical québécois pour les saisons à venir! Veuillez noter que l'artiste pourrait inviter les spectateurs à se lever pendant le spectacle. Nous préférons vous en avertir."}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/FTfHlsjKSy4?si=oTQAOIYX_IxNGRAn"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/name":[{"@language":"fr","@value":"JF Pauzé"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/jf-pauze.html#WebPage"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/jf-pauze.html"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/jean-francois-pauze-2026.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-17T20:00:00-04:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-jf-pauze#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-jf-pauze#Offer_1"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2027-01-14T20:00:00-05:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-jf-pauze#2026-09-17T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-jf-pauze#2027-01-14T200000-0500"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/jf-pauze.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:16:23.181-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/jf-pauze.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-jf-pauze#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E01OT\u0026CodeSiteClient=00000V"}],"http://schema.org/price":[{"@value":"55"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-jf-pauze#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"51"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-jf-pauze#2026-09-17T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"JF Pauzé"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-17T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-jf-pauze#2027-01-14T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"JF Pauzé"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2027-01-14T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-billie-du-page","@type":["http://schema.org/Event"],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/7gjeVsxETCI?si=aWOXMQAJDVOJaRdd"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/soirees-cabaret/billie-du-page-2026.jpg"}],"http://schema.org/keywords":[{"@value":"soirees-cabaret"}],"http://schema.org/name":[{"@language":"fr","@value":"Billie du Page"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-14T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/billie-du-page.html#WebPage"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/billie-du-page.html"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/dad71250-8ec8-4b41-aa18-47eef056a04c"},{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/description":[{"@language":"fr","@value":"La pop de Billie du Page, c’est un hymne à la liberté d’être soi-même, sans gêne. Son grain de voix percutant, son sens inné pour les mélodies catchy et la sincérité de ses textes, font d’elle une artiste à part entière. C’est un début remarqué pour la jeune femme de 20 ans, qui a vu son single Fake Friends se hisser numéro 1 du top radio au Québec et au top 10 Canadien! À peine quelques mois après avoir lancé Billie du Page, son premier EP en français (octobre 2024), l'artiste prolifique enchaîne avec Not Your Princess, un nouveau EP en anglais sorti le 7 mars 2025. L’artiste de nationalité canadienne et française nous propose une pop sucrée et féministe, à la fois décomplexée et assumée. En français ou en anglais, Bille sait autant toucher sur nos cordes sensibles grâce à ses ballades mélancoliques que nous faire danser avec ses hits effrontés. Des peines de cœur aux refrains qui collent à la peau. Elle transforme ses déceptions en hymnes pop qui donnent envie de se relever avec fierté. Que ce soit dans un studio ou sur la scène, Billie se sent chez elle. Depuis quelques mois, elle fait ses premiers spectacles, en première partie de Cœur de pirate en Europe, puis avec Aliocha Schneider et Grand Corps Malade au Québec. Grâce à ces dizaines de dates, une chose est sûre Billie est faite pour la scène; elle captive le public tant par sa candeur, sa présence et sa générosité! Lauréate au Gala de l'ADISQ 2025 dans la catégorie : Révélation de l'année * Formule cabaret au foyer de la salle Odyssée / Ambiance intime et conviviale / Bar en fonction / Places limitées"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-billie-du-page#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-billie-du-page#Offer_1"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-billie-du-page#Offer_2"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/billie-du-page.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:25:02.998-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/billie-du-page.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-billie-du-page#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E050M\u0026CodeSiteClient=00000V"}],"http://schema.org/price":[{"@value":"30"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-billie-du-page#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"26"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-billie-du-page#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"étudiants"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"20"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-les-eternels-pigistes","@type":["http://schema.org/Event"],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/dGahOg52oTw?si=jHTW8ba3ojIb5Cez"}],"http://schema.org/description":[{"@language":"fr","@value":"Flambant nue plonge au cœur d’une question universelle: quelle est notre place dans l'univers? Cette interrogation, aussi vieille que l'humanité elle-même, est exacerbée par les récentes avancées scientifiques, notamment la fascinante théorie des univers multiples. Au bout de ce parcours, est-ce que nos personnages trouvent une réponse à cette question? Portée par un chœur de personnages vibrants et attachants, ce texte de Pierre-Michel Tremblay mêle profondeur et humour à travers une série de tableaux qui reflètent nos différentes identités dans des situations qui résonnent avec des enjeux de notre époque. Une rencontre unique sur scène avec quatre virtuoses de l’interprétation : Christian Bégin, Marie Charlebois, Isabelle Vincent et Pier Paquette. Ensemble, ils donnent vie à un spectacle touchant, drôle et captivant qui invite à réfléchir… et à rêver. Texte Pierre-Michel Tremblay Mise en scène Cédrik Lapratte-Roy Distribution Christian Bégin, Marie Charlebois, Isabelle Vincent et Pier Paquette Production Concertium Équipe de création Assistante mise en scène: Amélie Lapratte-Roy Conceptrice de décors: Olivia Pia-Audet Conceptrice costumes: Leilah Dufour-Forget Conceptrice éclairage: Joëlle Leblanc Conceptrice son: Marie-Frédérique Gravel Conceptrice maquillage: Audrey Trottier Concepteur coiffure: Denis Parent Directrice technique création: Catherine Fasquelle Directrice technique tournée: Romane Bocquet «…concept flyé mais tellement drôle à certains moments, tout le monde y passe, les wokes, la réalité virtuelle… et le jeu physique des acteurs. BRAVO! Y a de la comédie là-dedans, c’était très très très bon.» - Ève-Marie Lortie - TVA - Salut bonjour «Je conseille ce spectacle à tous les GenZ […] si jamais vous voulez savoir d'où vous venez.» - Nathalie Petrowski – ICI Première – La journée est encore jeune «Il [Pierre-Michel Tremblay] interroge le sens de l’existence, l’inconvénient d’être né… en désamorçant ses angoisses par la caricature et la comédie» - Luc Boulanger – La Presse «Brillance et franche rigolade. À voir!» - Sylvain Ménard – Fm103 – De bonheur le matin «…une proposition théâtrale audacieuse et touchante[…] Le titre intrigue, et la pièce tient sa promesse: elle nous déshabille, doucement, sans voyeurisme, mais avec une sincérité désarmante.» -Annie Roy – BPArtsMédia «Ce feu roulant de gags et de questions sans réponses, sur des enjeux de notre époque, trouve écho dans la dynamique mise en scène de Cédrik Lapratte-Roy.» - Marc-Yvan Coulombe – babillardmontreal.com"}],"http://schema.org/name":[{"@language":"fr","@value":"Flambant nue"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/les-eternels-pigistes.html"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/les-eternels-pigistes.html#WebPage"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-30T20:00:00-04:00"}],"http://schema.org/keywords":[{"@value":"theatre-hors-serie"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/theatre-hors-serie/les-eternels-pigistes.jpg"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-les-eternels-pigistes#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-les-eternels-pigistes#Offer_1"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-les-eternels-pigistes#Offer_2"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/les-eternels-pigistes.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:24:13.269-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/les-eternels-pigistes.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-les-eternels-pigistes#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04XA\u0026CodeSiteClient=00000V"}],"http://schema.org/price":[{"@value":"54"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-les-eternels-pigistes#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"50"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-les-eternels-pigistes#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"→ Abonnés théâtre*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"40"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-dave-gaudet","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/dave-gaudet.jpg"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/uwQFruCADF0?si=FjabuZs_MpuFUqOK"}],"http://schema.org/description":[{"@language":"fr","@value":"La carrière de l’humoriste à l’énergie débordante et au grand charisme Dave Gaudet est en plein essor depuis la pandémie. Il a vendu avec son dernier spectacle La Légende près de 50 000 billets et sa tournée s’est achevé le 12 avril 2025 à la Place Bell, le positionnant comme premier humoriste québécois à avoir présenté son spectacle dans cet aréna. Au terme de la représentation du 12 avril, il annoncera son nouveau spectacle IA (Intelligence Artificielle), un spectacle inspiré par et créé en partie avec l’intelligence artificielle, un concept bien aligné avec sa créativité et sa folie des grandeurs. Encore une fois, un spectacle unique. La légende ne fait que se concrétiser. Encore."}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/d5cb0feb-55f2-426b-9f00-6d5c46ed7696"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/dave-gaudet.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Dave Gaudet"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2027-02-17T20:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/dave-gaudet.html#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-dave-gaudet#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-dave-gaudet#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/dave-gaudet.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:13:02.603-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/dave-gaudet.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-dave-gaudet#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E0558\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/price":[{"@value":"45"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-dave-gaudet#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"41"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-pierre-luc-pomerleau","@type":["http://schema.org/Event"],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/7d187cec-b726-4f2d-9af4-40a6375a5ba9"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/pierre-luc-pomerleau.html"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/FqWVvobqJLs?si=BCrLOrjsC7RoEhtD"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/pierre-luc-pomerleau.html#WebPage"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-07T20:00:00-05:00"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/pierre-luc-pomerleau.jpg"}],"http://schema.org/name":[{"@language":"fr","@value":"Pierre-Luc Pomerleau"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/description":[{"@language":"fr","@value":"Moqueur polyglotte : Oiseau rare qui se démarque par sa capacité à drôlement bien imiter les voix et les mimiques de ses compères. Pierre-Luc Pomerleau, humoriste à l’efficacité remarquable, est en tournée partout au Québec avec son spectacle Moqueur polyglotte. Sur scène, Pierre-Luc s’amuse à rire de lui-même, notamment de son côté lunatique et maladroit, en mettant de l’avant ses histoires et ses anecdotes personnelles tout en défilant les gags à un rythme impressionnant. L’humoriste à la répartie implacable, qui est aussi l’homme-orchestre derrière les hilarantes et populaires capsules sur le thème de Marketplace (à voir absolument!), aborde des sujets rassembleurs dans lesquels le public se reconnaîtra assurément. Alors que ses anecdotes sont appuyées par une gestuelle démonstrative et une rythmique étonnante, Pierre-Luc sera assurément l’un de vos nouveaux coups de cœur! * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-pierre-luc-pomerleau#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-pierre-luc-pomerleau#Offer_1"}]},{"@id":"http://kg.footlight.io/resource/7d187cec-b726-4f2d-9af4-40a6375a5ba9","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Pierre-Luc Pomerleau"}],"http://schema.org/hasOccupation":[{"@value":"Humoriste"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}],"http://schema.org/url":[{"@value":"https://www.pierrelucpomerleau.com/"}],"http://schema.org/sameAs":[{"@value":"http://www.wikidata.org/entity/Q97366763"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/pierre-luc-pomerleau.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:26:08.804-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/pierre-luc-pomerleau.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-pierre-luc-pomerleau#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"41"}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04Y3\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-pierre-luc-pomerleau#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"37"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-marc-dupre","@type":["http://schema.org/Event"],"http://schema.org/description":[{"@language":"fr","@value":"Après une pause bien méritée, Marc Dupré est de retour avec un tout nouveau spectacle qui s'annonce électrisant ! L’auteur-compositeur-interprète, chéri du public, reprend la route pour vous offrir une soirée inoubliable où ses plus grands succès se mêleront à ses toutes nouvelles chansons issues de son prochain album. Du premier accord au dernier rappel, préparez-vous à une performance vibrante, pleine d’énergie, d’émotion et de moments de complicité avec le public. Marc revisite son impressionnant répertoire, de ses débuts à aujourd’hui, avec la passion et le talent qui font de lui un véritable hitmaker. Venez chanter, danser et vibrer au rythme de ses mélodies entraînantes et de ses textes qui touchent le cœur. Une chose est certaine : ce spectacle marquera son grand retour sur scène en chanson et vous ne voudrez pas le rater! Veuillez noter que l'artiste pourrait inviter les spectateurs à se lever pendant le spectacle. Nous préférons vous en avertir."}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/b98e799e-cc76-4182-bfea-d63e96521408"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/marc-dupre.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Marc Dupré"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/marc-dupre-2025.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-14T20:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/marc-dupre.html#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/d3hRxPG36hA?si=GWigsvyoPen-y0WZ"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-marc-dupre#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-marc-dupre#Offer_1"}]},{"@id":"http://kg.footlight.io/resource/b98e799e-cc76-4182-bfea-d63e96521408","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Marc Dupré"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Artiste musical"}],"http://schema.org/url":[{"@value":"https://www.marcdupre.com/"}],"http://schema.org/hasOccupation":[{"@value":"Artiste musical"}],"http://schema.org/sameAs":[{"@value":"http://www.wikidata.org/entity/Q3287975"},{"@value":"http://kg.artsdata.ca/resource/K12-278"},{"@value":"https://www.facebook.com/DupreMarc/"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/marc-dupre.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:14:40.923-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/marc-dupre.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-marc-dupre#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E0553\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"56"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-marc-dupre#Offer_1","http://schema.org/price":[{"@value":"52"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal","http://schema.org/name":[{"@language":"fr","@value":"La pièce qui tourne mal"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/keywords":[{"@value":"theatre-hors-serie"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/3jZmBEafPZw?si=ki9SGmIdR4tInHkH"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/la-piece-qui-tourne-mal.html#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/theatre-hors-serie/la-piece-qui-tourne-mal.jpg"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/la-piece-qui-tourne-mal.html"}],"http://schema.org/description":[{"@language":"fr","@value":"Monarque présente LA PIÈCE QUI TOURNE MAL / THE PLAY THAT GOES WRONG! Avec cette pièce, André Robitaille et Mario Provencher ont réussi le coup de maitre d’amener sur scène au Québec, et en français, LE SPECTACLE qu’ils souhaitaient produire depuis toujours. Décrocher les droits de cette production maintes fois primée et récompensée relevait du rêve! Synopsis Un meurtre a eu lieu dans un manoir de campagne et un inspecteur est chargé de l'affaire pour trouver le coupable. Cependant, lorsque cette intrigue est confiée aux comédiens maladroits de la Cornley Drama Society, tout ce qui peut mal tourner... tourne mal! Les acteurs et l'équipe se débattent pour se rendre jusqu'au dernier rappel, avec des conséquences hilarantes! Avez-vous déjà découvert qui a assassiné Charles Haversham? Vous devrez venir voir par vous-même! Vivez un chaos total et savourez cette comédie parfaite qui vous fera rire aux éclats! Écrite par Henry Lewis, Jonathan Sayer et Henry Shields Dirigée par André Robitaille Distribution Fabien Cloutier, LeLouis Courchesne, Guillaume Lambert, Pierre-François Legendre, Olivia Palacci, Rémi-Pierre Paquin, Julie Ringuette et Jonathan Roberge Présenté par Monarque Productions en accord avec Mischief Worldwide Ltd."}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-07-31T20:00:00-04:00"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal#Offer_1"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal#Offer_2"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal#Offer_3"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal#Offer_5"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal#Offer_4"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-09T14:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal#2026-07-31T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal#2026-08-01T140000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal#2026-08-01T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal#2026-08-02T140000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal#2026-08-07T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal#2026-08-08T140000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal#2026-08-08T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal#2026-08-09T140000-0400"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/la-piece-qui-tourne-mal.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:17:45.066-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/la-piece-qui-tourne-mal.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E01PX\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"75"}],"http://schema.org/name":[{"@language":"fr","@value":"→ Tarif lève-tôt : 65 $"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal#Offer_1","http://schema.org/price":[{"@value":"65"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée → Tarif lève-tôt : 61 $"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal#Offer_2","http://schema.org/price":[{"@value":"71"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"groupes de 4 personnes et plus"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal#Offer_3","http://schema.org/price":[{"@value":"61"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"→ Abonnés théâtre*"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal#Offer_5","http://schema.org/price":[{"@value":"61"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal#Offer_4","http://schema.org/price":[{"@value":"58"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal#2026-07-31T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"La pièce qui tourne mal"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-07-31T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal#2026-08-01T140000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"La pièce qui tourne mal"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-01T14:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal#2026-08-01T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"La pièce qui tourne mal"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-01T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal#2026-08-02T140000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"La pièce qui tourne mal"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-02T14:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal#2026-08-07T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"La pièce qui tourne mal"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-07T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal#2026-08-08T140000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"La pièce qui tourne mal"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-08T14:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal#2026-08-08T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"La pièce qui tourne mal"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-08T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal#2026-08-09T140000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"La pièce qui tourne mal"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-09T14:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-jf-otis","@type":["http://schema.org/Event"],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/jf-otis.html"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/a0JxB0FAv40?si=aYmwBdtGmRzPSp8l"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/name":[{"@language":"fr","@value":"JF Otis"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/jf-otis.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-16T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/jf-otis.html#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"JF est un gars de Rimouski qui prend d’excellentes mauvaises décisions. Bachelier en administration des affaires devenu humoriste, star du web, il dit que ce fut une décision qui l’a rendu heureux mais qui a rendu ses parents… nerveux. La meilleure décision qu’il a prise selon lui après avoir commencé à faire des vidéos sur internet et s’acheter un lave vaisselle. L'improvisation est un élément important de son stand-up. Chaque représentation devient alors unique, mais toujours aussi efficace. Lorsqu'il est sur scène, on a envie d’écouter et de rire avec ce «gros nounours». L’assistance est placée dans une ambiance festive, comme si l’on était tous autour d’un feu de camp en compagnie d’un vieil ami racontant des anecdotes sans queue ni tête à nous en faire renverser notre verre! En entendant ses histoires vous allez être découragés ou encore vous allez le juger mais une chose est sûre, vous allez rire. * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-jf-otis#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-jf-otis#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/jf-otis.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:34:18.566-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/jf-otis.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-jf-otis#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E053O\u0026CodeSiteClient=00000V"}],"http://schema.org/price":[{"@value":"36"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-jf-otis#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"32"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-pour-une-autre-histoire-dun-soir","http://schema.org/description":[{"@language":"fr","@value":"Trois des plus belles voix du Québec, Joe Bocan, Marie Carmen et Marie Denise Pelletier, se retrouveront de nouveau ensemble sur scène, avec Pour une autre histoire d’un soir, une version revisitée du spectacle qui leur a valu le Félix du Spectacle de l'année Variétés/Réinterprétations en 2022 et plus de 50 000 spectateurs. Dans une mise en scène renouvelée de Michel Poirier, voici un spectacle qui n’aura rien perdu de son charme. Un hommage à l’auteur-compositeur-interprète Germain Gauthier, décédé en août dernier, s’inscrira parmi les nouveautés. Et rassurez-vous: la rumeur veut que L’aigle noir, Repartir à zéro et Tous les cris les S.O.S. soient de nouveau au programme! Pour une autre histoire d’un soir est une occasion pour les fans de la première heure de revivre des moments inoubliables tout en découvrant quelques surprises. Pour les autres, l’occasion de comprendre pourquoi ce spectacle a récolté tant d’éloges de la part du public et des critiques. Pour une autre histoire d’un soir, la porte s’ouvre sur une nouvelle aventure où se reflètent le meilleur du passé et des découvertes de grande envergure."}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/CDpURWNYns0?si=oR3vUwpFj4BCw0jo"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/pour-une-autre-histoire-dun-soir.jpg"}],"http://schema.org/name":[{"@language":"fr","@value":"Pour une autre histoire d'un soir"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-04T15:00:00-04:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/pour-une-autre-histoire-dun-soir.html#WebPage"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/pour-une-autre-histoire-dun-soir.html"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-pour-une-autre-histoire-dun-soir#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-pour-une-autre-histoire-dun-soir#Offer_1"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-04T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-pour-une-autre-histoire-dun-soir#2026-10-04T150000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-pour-une-autre-histoire-dun-soir#2026-10-04T200000-0400"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/pour-une-autre-histoire-dun-soir.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:28:56.053-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/pour-une-autre-histoire-dun-soir.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-pour-une-autre-histoire-dun-soir#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E01QK\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"56"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-pour-une-autre-histoire-dun-soir#Offer_1","http://schema.org/price":[{"@value":"52"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-pour-une-autre-histoire-dun-soir#2026-10-04T150000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Pour une autre histoire d'un soir"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-04T15:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-pour-une-autre-histoire-dun-soir#2026-10-04T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Pour une autre histoire d'un soir"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-04T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-billy-tellier","@type":["http://schema.org/Event"],"http://schema.org/description":[{"@language":"fr","@value":"Fier du succès de sa dernière tournée avec plus de 50 000 billets vendus et près de 200 représentations, Billy Tellier présente son 3e one-man-show Kicker la ruche. Billy, qui a ce talent inouï de toucher la cible avec des sujets rassembleurs, présente cette fois-ci un spectacle encore plus punché et rythmé dans lequel il livre ses états d’âme sur sa peur de faire les mauvais choix... Toute sa vie, Billy a tenté de vivre en bonne petite abeille ouvrière, en faisant ce qu’on attendait de lui et en prenant les décisions les plus sages afin d’impacter le moins possible les gens qui l’entourent. Dans ce nouveau spectacle, il se demande s’il ne serait pas temps de remettre les choses en question, de brasser son quotidien, bref de kicker la ruche. Mais est-ce vraiment une bonne idée pour quelqu'un qui angoisse en choisissant entre crémeuse ou traditionnelle? * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7 «Un rhythme implacable, de l'aplomb à revendre et un sens de l'observation affûté» - Le Devoir «Plus punché que jamais au point de faire rire son public sans arrêt» - NOOVO Moi «Plus que réussi! Billy nous suprend, il est punché et au top de sa game!» - CKOI «Tellement attachant qu'on a envie de l'adopter! Bravo!» - QUB Radio «Un sens du punch aiguisé... un spectacle réussi et efficace!» - 7 Jours «De bonnes anecdotes [...] plusieurs vont se reconnaître! Ça riait beaucoup!» - 98,5 FM «Un show qui vaut le coup!» - Echos Vedettes «Savoureux [...] il a démontré une fois de plsu son charisme fou et son habilité à captiver le public [...] une performance réussie» - BP Arts médias"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/billy-tellier.html"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/billy-tellier-2025.jpg"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/spec-qc-ca_artiste_billy-tellier"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-06T20:00:00-05:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/0PtbNhIwd2U?si=_7tVKMSi0FMC4Hf1"}],"http://schema.org/name":[{"@language":"fr","@value":"Billy Tellier"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/billy-tellier.html#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-billy-tellier#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-billy-tellier#Offer_1"}]},{"@id":"http://kg.footlight.io/resource/spec-qc-ca_artiste_billy-tellier","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@value":"Billy Tellier"}],"http://schema.org/sameAs":[{"@value":"http://kg.artsdata.ca/resource/K12-252"}],"http://schema.org/image":[{"@value":"https://spec.qc.ca/uploads/artistes/billy-tellier-2.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"Révélation Juste pour rire 2004, les succès de Billy Tellier ne font que se multiplier depuis le début de sa carrière, autant sur scène, qu’à la radio et à la télévision. Après des prestations chaleureusement applaudies à Juste pour rire, au ComédiHa (Grand rire de Québec), à Zoofest ou encore aux Parlementeries, Billy Tellier a lancé son premier one-man-show, La loi du plus fort, à l’hiver 2013. Mis en scène par Christian Viau, le spectacle, maintenant disponible sur DVD, a été acclamé tant par la critique que le public. En 2016 et 2017, il a co-animé pour deux années consécutives un Gala ComédiHa aux côtés de Mario Jean. Puis, en 2017 et 2018 dans le cadre du Festival Juste pour rire, il a présenté Le petit monde de Billy Tellier Live à la Place des arts, un spectacle conçu spécialement pour les fans de ses capsules radio et où il a fait salle comble à chacune de ces soirées uniques. Dès l’automne 2019, Billy Tellier sera de retour sur les planches partout au Québec avec son deuxième one-man-show : Hypocrite(s). Avec celui-ci, Billy saura mettre en lumière le petit côté sombre de tous et chacun dans un spectacle servant d’exutoire collectif et qui fera le plus grand bien! À la barre de l’émission matinale du 96,9 CKOI depuis 2012, Billy co-anime présentement Debout les comiques avec Martin Cloutier, Patrice Bélanger et Tammy Verge. Au Gala Les Olivier 2017, Billy est d’ailleurs reparti avec la statuette dans la catégorie Capsule ou sketch radio humoristique de l’année pour ses hilarantes capsules du Petit monde de Billy, diffusées à CKOI. Un CD Best of de ses populaires capsules est disponible sur iTunes. Du côté de la télévision, il a notamment assuré avec brio l’animation des émissions Trouve-moi ça (2014), Des champions du web (2014-2015) et Le poker show (2008), toutes diffusées sur V/TQS. Puis, il a également été comédien dans quelques séries, dont La vie parfaite (Radio-Canada) et Colocs.TV (MusiquePlus), qu’il a scénarisé avec Alexandre Barrette en plus d’y jouer. Finalement, Billy a fait de nombreuses apparitions et chroniques sur des émissions telles Juste pour rire en direct, Brassard en direct d’aujourd’hui, Atomes crochus, Prière de ne pas envoyer de fleurs, Nous avons les images, Vrak la vie, etc. Également prisé comme auteur, il a écrit et script-édité le premier one-man-show d’Alexandre Barrette, a écrit pour plusieurs éditions des galas Artis et Les Olivier, a été script-éditeur sur le spectacle Rechargé de Rachid Badouri et a écrit sur le spectacle de Maxim Martin, Enfin."}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/billy-tellier.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:20:05.977-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/billy-tellier.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-billy-tellier#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E051R\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/price":[{"@value":"40"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-billy-tellier#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"36"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-kain","@type":["http://schema.org/Event"],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/kain.html#WebPage"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/kain-26.jpg"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/name":[{"@language":"fr","@value":"Kaïn"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K5-432"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/kain.html"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-15T20:00:00-05:00"}],"http://schema.org/description":[{"@language":"fr","@value":"L’infatigable groupe Kaïn se paie la traite pour ses 25 ans d’existence et s’offre une place dans la cour des grands, le temps d’un album. Un hommage à ses idoles Québécois de Desjardins à Mandeville, en passant par Plume, Pag, Piché, Séguin et d’autres influences importantes du band. Plus festif et vivant que jamais, le spectacle promet la fête et la grande soif! Kaïn, un incontournable party Québécois."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-kain#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-kain#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/kain.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-14T16:25:23.318-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/kain.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-kain#Offer_0","http://schema.org/price":[{"@value":"44"}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04YI\u0026CodeSiteClient=00000V"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-kain#Offer_1","http://schema.org/price":[{"@value":"40"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-ge-ouganda","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/ge-ouganda.html"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K10-427"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/autres-evenements/ge-ouganda.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-01T13:30:00-05:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/ge-ouganda.html#WebPage"}],"http://schema.org/recordedIn":[{"@value":"https://player.vimeo.com/video/1076125689?badge=0\u0026autopause=0\u0026player_id=0\u0026app_id=58479"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/656dca7a-5549-494c-acbf-8c05662b23bf"}],"http://schema.org/description":[{"@language":"fr","@value":"L’Ouganda est une terre à la végétation luxuriante et à la faune abondante, baignée de rires et de lumière, où il fait bon vivre. Partez à la découverte de ce trésor sauvage et méconnu que l’on surnomme la «Perle de l’Afrique». Ce film vous entraîne dans un voyage au cœur des sources du Nil Blanc, au gré des chuchotements des personnages qui les ont explorées, entre les rives du lac Victoria et les pentes des Montagnes de la Lune."}],"http://schema.org/name":[{"@language":"fr","@value":"Les Grands Explorateurs"}],"http://schema.org/keywords":[{"@value":"autres-evenements"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-ge-ouganda#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-ge-ouganda#Offer_1"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-ge-ouganda#Offer_2"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/ge-ouganda.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:27:43.983-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/ge-ouganda.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-ge-ouganda#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"33.92"}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E050B\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-ge-ouganda#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"60 ans et +"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"31.04"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-ge-ouganda#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"25 ans et moins"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"22.42"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-yves-roy-desmarais","http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-490"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Pierre-Yves Roy-Desmarais"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-26T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/pierre-yves-roy-desmarais.html#WebPage"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/pierre-yves-roy-desmarais.html"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/pierre-yves-roy-desmarais.jpg"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/description":[{"@language":"fr","@value":"Après avoir remporté les Olivier du spectacle de l’année et d’auteur de l’année, ainsi que le Félix du spectacle d’humour de l’année, Pierre-Yves Roy-Desmarais élève son art à un niveau supérieur avec son deuxième spectacle. Un soir, alors qu’il ne s’attend à rien, Pierre-Yves Roy-Desmarais vit un événement qui changera le cours de son existence. Il tente tant bien que mal de garder le contrôle de sa vie, mais le temps devient flou, les souvenirs se mélangent et les unités de mesure ne veulent plus rien dire. Avec tout ce qui lui arrive, il a l’impression de vivre l’équivalent d’une année complète, en seulement un an. Une année en un an est un spectacle alliant stand-up, chansons et fromage, d’une durée de 75 centimètres. En nomination au Gala Les Olivier 2026: Numéro d’humour de l’année Olivier de l’année «La bête de scène a un univers inimitable.» - Christian Saint-Pierre, Le Devoir «C'est un génie comique ce Pierre-Yves Roy-Desmarais» - Nathalie Petrowski, La journée (est encore jeune) «Son imaginaire débridé et sa créativité sont et resteront toujours sa plus grande force.» - Josée Lapointe, La Presse «Spectacle punché, réfléchi, éclaté.» - Catherine Brisson, 98,5 FM «Il prouve, encore une fois, qu'il offre un univers unique et rafraîchissant.» - Léa Harvey, Le Soleil «Pierre-Yves Roy-Desmarais maîtrise l'art du clownesque comme personne.» - Pascale Robitaille, TVA «[...] Pierre-Yves Roy-Desmarais demeure donc dans une classe à part.» - Cédric Bélanger, Journal de Québec «Rythmé et dynamique: la mise en scène du spectacle est probablement la plus unique et novatrice au Québec. Un show que tout fan d'humour se doit de voir.» - Alexandre Tétreault, FM93"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-yves-roy-desmarais#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-yves-roy-desmarais#Offer_1"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-01T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-yves-roy-desmarais#2026-03-26T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-yves-roy-desmarais#2026-03-27T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-yves-roy-desmarais#2026-05-21T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-yves-roy-desmarais#2026-05-22T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-yves-roy-desmarais#2026-09-30T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-yves-roy-desmarais#2026-10-01T200000-0400"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/pierre-yves-roy-desmarais.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:22:18.768-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/pierre-yves-roy-desmarais.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-yves-roy-desmarais#Offer_0","http://schema.org/price":[{"@value":"46"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E017V\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-yves-roy-desmarais#Offer_1","http://schema.org/price":[{"@value":"42"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-yves-roy-desmarais#2026-03-26T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Pierre-Yves Roy-Desmarais"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-26T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-yves-roy-desmarais#2026-03-27T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Pierre-Yves Roy-Desmarais"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-27T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-yves-roy-desmarais#2026-05-21T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Pierre-Yves Roy-Desmarais"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-21T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-yves-roy-desmarais#2026-05-22T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Pierre-Yves Roy-Desmarais"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-22T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-yves-roy-desmarais#2026-09-30T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Pierre-Yves Roy-Desmarais"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-30T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-pierre-yves-roy-desmarais#2026-10-01T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Pierre-Yves Roy-Desmarais"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-01T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-francois-bellefeuille-sauvage","http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/francois-bellefeuille-sauvage.html"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/francois-bellefeuille-sauvage.html#WebPage"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-285"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-04T20:00:00-05:00"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/0GClKbjoyNo?si=liHE5aan41yf1HK5"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/name":[{"@language":"fr","@value":"François Bellefeuille"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/francois-bellefeuille-sauvage.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"François Bellefeuille est de retour sur scène avec son troisième spectacle: Sauvage. Hilarant, incisif et imprévisible, il livre une performance de stand-up à la fois intelligente et éclatée, repoussant les limites de son style inimitable. L’humoriste, dont la maîtrise du ridicule est à son apogée, porte un regard tout aussi mordant sur le monde, et vous entraîne dans un univers où la réalité côtoie l’absurde. Préparez-vous à avoir mal aux abdos; François Bellefeuille est au sommet de son art! Jusqu’au prochain spectacle… on l’espère! «Il nous a achevé en partant! Il est vraiment au sommet de son art.» - Sabrina Cournoyer, Salut Bonjour «François Bellefeuille est toujours aussi drôle et pertinent.» - Catherine Beauchamp, 98.5 FM «François Bellefeuille revient en force [...] L'un des humoristes les plus solides et originaux.» - Raphaël Gendron-Martin, Journal de Montréal «Un spectacle tassé serré, livré avec aplomb.» - Jean Siag, La Presse «J'entendais des hurlements de rire autour de moi.» - Claudia Hébert, Tout un matin, ICI Première «Sauvage, c'est du François Bellefeuille pur jus [...] Délirant, flyé, sagement décapant et jamais bien méchant.» - Marie-Josée R. Roy, Hollywood PQ"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-francois-bellefeuille-sauvage#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-francois-bellefeuille-sauvage#Offer_1"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-09T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-francois-bellefeuille-sauvage#2026-02-04T200000-0500"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-francois-bellefeuille-sauvage#2026-03-31T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-francois-bellefeuille-sauvage#2026-09-09T200000-0400"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/francois-bellefeuille-sauvage.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:23:31.086-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/francois-bellefeuille-sauvage.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-francois-bellefeuille-sauvage#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"49"}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E00YI\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-francois-bellefeuille-sauvage#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"45"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-francois-bellefeuille-sauvage#2026-02-04T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"François Bellefeuille"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-04T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-francois-bellefeuille-sauvage#2026-03-31T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"François Bellefeuille"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-31T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-francois-bellefeuille-sauvage#2026-09-09T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"François Bellefeuille"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-09T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-phil-g-smith","@type":["http://schema.org/Event"],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/phil-g-smith.html#WebPage"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/soirees-cabaret/phil-g-smith-2026b.jpg"}],"http://schema.org/keywords":[{"@value":"soirees-cabaret"}],"http://schema.org/name":[{"@language":"fr","@value":"Phil G. Smith"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/dad71250-8ec8-4b41-aa18-47eef056a04c"},{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-12-02T20:00:00-05:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/2i9qQOJH7rw?si=8UArZTi1GzzIaY5V"}],"http://schema.org/description":[{"@language":"fr","@value":"Suite au succès de ses deux albums On fait du country et On est encore là (prix Étoiles Stingray au Festival Western de Saint-Tite, nominations au Gala Country, nominations au Gala de l'ADISQ), Phil G. Smith reprend la tournée avec son tout nouveau spectacle La prochaine sortie. Avec des prestations remarquées sur les grandes scènes du Québec, dont LASSO et le Festival Western de St-Tite, Phil G. Smith poursuit son ascension avec ses chansons aux sonorités de country-rock américain francophone qui pavent la route à un spectacle qui fera voyager, fenêtres baissées, entre Saguenay et Nashville! * Formule cabaret au foyer de la salle Odyssée / Ambiance intime et conviviale / Bar en fonction / Places limitées"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/phil-g-smith.html"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-phil-g-smith#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-phil-g-smith#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/phil-g-smith.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:11:18.015-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/phil-g-smith.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-phil-g-smith#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04ZT\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/price":[{"@value":"35"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-phil-g-smith#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"31"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-bleu-jeans-bleu","http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/a9m3mORP_EY?si=Zgnx6YPadL2Wb_9K"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/9d969f98-150f-4bc1-856f-32655c6ff0ca"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Bleu Jeans Bleu"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/bleu-jeans-bleu-2026.jpg"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/bleu-jeans-bleu.html"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-21T20:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/bleu-jeans-bleu.html#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"Les gars de Bleu Jeans Bleu font du bien. Ils l’ont toujours fait, et ils poursuivent sur leur lancée avec un tout nouveau spectacle, la tournée Record NºV. L’élégance d’un parfum haut de gamme, le réconfort d’un macaroni à la viande. L’énergie d’un Monster Truck, la finesse d’une Mercedes vintage. Ce nouveau spectacle allie irrésistiblement leur musique efficace à leur humour décalé, dans une parfaite complicité avec le public — une signature qui fait de BJB un des groupes chouchous de la scène québécoise. Ambiance feutrée, décor italo-romantique… laissez-vous envoûter par la fragrance du bonheur pur! Veuillez noter que les artistes pourraient inviter les spectateurs à se lever pendant le spectacle. Nous préférons vous en avertir."}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-bleu-jeans-bleu#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-bleu-jeans-bleu#Offer_1"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2027-03-13T20:00:00-05:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-bleu-jeans-bleu#2026-02-21T200000-0500"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-bleu-jeans-bleu#2027-03-13T200000-0500"}]},{"@id":"http://kg.footlight.io/resource/9d969f98-150f-4bc1-856f-32655c6ff0ca","@type":["http://schema.org/Organization"],"http://schema.org/name":[{"@language":"fr","@value":"Bleu Jeans Bleu"}],"http://schema.org/sameAs":[{"@value":"http://www.wikidata.org/entity/Q96216977"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/bleu-jeans-bleu.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:24:04.052-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/bleu-jeans-bleu.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-bleu-jeans-bleu#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04WV\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/price":[{"@value":"44"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-bleu-jeans-bleu#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"40"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-bleu-jeans-bleu#2026-02-21T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Bleu Jeans Bleu"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-21T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-bleu-jeans-bleu#2027-03-13T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Bleu Jeans Bleu"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2027-03-13T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_sorties-famille-le-livre-magique","http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/sorties-famille/le-livre-magique.html#WebPage"}],"http://schema.org/name":[{"@language":"fr","@value":"Le livre\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tmagique"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/fbb87285-64ba-4bae-9bec-ae259276fca6"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/PKXCCoFKcAc?si=hKNggfeO6NJ3WV1K"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/sorties-famille/le-livre-magique.html"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-18T13:00:00-05:00"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/sorties-famille/le-livre-magique-2026.jpg"}],"http://schema.org/keywords":[{"@value":"sorties-famille"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/description":[{"@language":"fr","@value":"Conçu par des créateurs québécois, LE LIVRE MAGIQUE est un spectacle musical qui fera voyager petits et grands dans l’univers des contes fantastiques comme Aladin et la lampe merveilleuse, La Belle et la Bête, Cendrillon et La Petite Sirène. C'est à travers une histoire originale et un décor enchanteur que les six personnages magiques vous feront rêver sur des airs connus, tels que : Sous l’océan, Hakuna Matata, Libérée Délivrée et bien plus encore. Les artistes aux voix et aux talents indéniables vous surprendront dans une mise en scène féérique aux chorégraphies dynamiques. Laissez-vous transporter et émerveiller par la magie de vos contes favoris. À la fin du spectacle, rencontrez vos personnages préférés afin de créer ce souvenir dont vos enfants se souviendront encore longtemps; un rêve de proximité devenu réalité!"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_sorties-famille-le-livre-magique#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_sorties-famille-le-livre-magique#Offer_1"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_sorties-famille-le-livre-magique#Offer_2"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-18T16:00:00-05:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_sorties-famille-le-livre-magique#2026-01-18T130000-0500"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_sorties-famille-le-livre-magique#2026-01-18T160000-0500"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/sorties-famille/le-livre-magique.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-16T16:26:01.324-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/sorties-famille/le-livre-magique.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_sorties-famille-le-livre-magique#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"32"}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E01OU\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_sorties-famille-le-livre-magique#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"28"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_sorties-famille-le-livre-magique#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"étudiants"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"27"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_sorties-famille-le-livre-magique#2026-01-18T130000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Le livre\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tmagique"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-18T13:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_sorties-famille-le-livre-magique#2026-01-18T160000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Le livre\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tmagique"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-18T16:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-simon-gouache","http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/simon-gouache.html#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/simon-gouache-2026.jpg"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/cd2aff1e-3955-4619-a5f6-26e833e92a75"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Simon Gouache"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-24T20:00:00-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/simon-gouache.html"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/description":[{"@language":"fr","@value":"«Je vais faire ça, pour l’instant…» C’est ce que Simon Gouache s'est dit quand il s'est lancé en stand-up, il y a maintenant plus de vingt ans. Visiblement, cet instant dure toujours. De ce qui devait être une courte aventure est née une grande passion, qui est au premier plan de ce quatrième spectacle. «Il appartient à l'élite du stand-up. 9/10» - Dominic Tardif, La Presse «Quel bon rythme [...] il utilise très très bien le silence» - Sabrina Cournoyer, Salut Bonjour «On voudrait que ça ne se termine jamais [...] nos muscles intercostaux demandent grâce.» - Christian St-Pierre, Le Devoir «Ses textes sont travaillés, il ne prend rien pour acquis» - Catherine Brisson, 98.5 FM"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-simon-gouache#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-simon-gouache#Offer_1"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-28T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-simon-gouache#2026-02-24T200000-0500"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-simon-gouache#2026-03-07T200000-0500"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-simon-gouache#2026-05-28T200000-0400"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/simon-gouache.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:27:27.363-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/simon-gouache.html"}]},{"@id":"http://kg.footlight.io/resource/cd2aff1e-3955-4619-a5f6-26e833e92a75","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Simon Gouache"}],"http://schema.org/hasOccupation":[{"@value":"Humoriste"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}],"http://schema.org/sameAs":[{"@value":"http://kg.artsdata.ca/resource/K12-233"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-simon-gouache#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E010S\u0026CodeSiteClient=00000V"}],"http://schema.org/price":[{"@value":"42"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-simon-gouache#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"38"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-simon-gouache#2026-02-24T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Simon Gouache"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-24T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-simon-gouache#2026-03-07T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Simon Gouache"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-07T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-simon-gouache#2026-05-28T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Simon Gouache"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-28T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-messmer","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-251"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/varietes/messmer.html#WebPage"}],"http://schema.org/keywords":[{"@value":"varietes"}],"http://schema.org/name":[{"@language":"fr","@value":"Messmer"}],"http://schema.org/additionalType":[{"@id":"http://kg.artsdata.ca/resource/PerformingArtsEvent"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/0DXowjSNqVw?si=EBV8RgVWMYm4-Jh1"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/varietes/messmer-13hz.jpg"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/varietes/messmer.html"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-20T20:00:00-04:00"}],"http://schema.org/description":[{"@language":"fr","@value":"Après de multiples tournées à succès et plus de 2 millions de billets vendus, Messmer, connu et reconnu comme le Maître Mondial de l’Hypnose revient avec un tout nouveau spectacle. Dans 13Hz, Messmer vous invite à entrer dans son mystérieux et hilarant univers où la frontière entre la réalité et l’illusion s’efface, pour diriger vos pensées vers des territoires inconnus. Avec sa présence charismatique inégalée et son talent exceptionnel, le recordman en hypnose collective avec 1066 personnes hypnotisées en moins de 5 minutes, vous plonge au cœur de vos pensées les plus profondes avant de vous guider à travers un jeu subtil d’ondes cérébrales à 13Hz. Le fascinateur vous entraîne vers un état de conscience unique où la volonté et le contrôle de nos vies prennent une nouvelle dimension. Osez découvrir l’expérience Messmer, où la maîtrise de soi et la fascination se rencontrent. «Quel spectacle fascinant! C’est très drôle de voir ce que Messmer arrive à faire de nous! J’ai dansé comme une folle à Ibiza! Que d’émotions!» - Isabelle Maréchal, QUB radio «Grâce à sa maîtrise de l’hypnose, le célèbre fascinateur québécois nous ‘’fait vivre des aventures que nous ne pourrions faire autrement’’ [...] Messmer est bienveillant [...] La vérité est qu’on rit, parfois même beaucoup. Et quand on y pense bien, il n’existe aucune forme de divertissement qui se rapproche de ce qu’il fait.» - Jean Siag, La Presse «I n’y avait que les cascades répétées de rires, les visages ahuris, les yeux ronds, obnubilés [...] par les pitreries de Messmer et ses invités d’un jour. [...] impossible de nier que les gens adorent le personnage.» - Marie-Josée R. Roy, Hollywood PQ «Un pur divertissement du début à la fin.» - Marie-Claude Doyle, Échos Vedettes «Je ne m’attendais pas à rire autant ! Le lien entre Messmer et son public est fascinant.» - Isabelle Perron, LCN «Stupéfiant et magistral! À voir absolument!» - Maryse Phaneuf, Éklectik Média «Les spectateurs les plus dubitatifs face à l’hypnose reconnaîtront [...] tout le talent de [Messmer]. L’artiste d’expérience captive évidemment, malgré tout, grâce à son talent d’hypnotiseur. Un métier tout à fait fascinant.» - Léa Harvey, Le Soleil «C'est le meilleur show que j'ai vu de ma vie. C'est une expérience qu'il faut avoir vue dans sa vie! Je n'ai jamais ri de même, [...] je braillais de rire.» - Pascale Picard, FM93"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-messmer#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-messmer#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/varietes/messmer.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:12:02.456-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/varietes/messmer.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-messmer#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E00WQ\u0026CodeSiteClient=00000V"}],"http://schema.org/price":[{"@value":"53"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_varietes-messmer#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"49"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-fallait-etre-la","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/fallait-etre-la-2026.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/fallait-etre-la.html#WebPage"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/fallait-etre-la.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Fallait être là - Le\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tjeu!"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-19T20:00:00-04:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/description":[{"@language":"fr","@value":"Fallait être là est un nouveau concept de spectacle humoristique et interactif qui redéfinit les codes de l’impro et de l’humour de scène traditionnel afin de créer des moments déjantés. Philippe Laprise, maître de cérémonie, accueille quatre invités vedettes : Joanie Guérin, LeLouis Courchesne, Frederic Barbusci ainsi que Neev, qui participent à des défis humoristiques décalés, avec comme objectif de déclencher les plus grands fous rires. Fallait être là est un spectacle événementiel audacieux, hilarant et surprenant, dans lequel le public sera amené à participer à plusieurs niveaux. Assistez à une expérience inédite de laquelle vous sortirez en vous disant : «Fallait être là!». * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-fallait-etre-la#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-fallait-etre-la#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/fallait-etre-la.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:39:51.725-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/fallait-etre-la.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-fallait-etre-la#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E0516\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/price":[{"@value":"40"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-fallait-etre-la#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"36"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-sylvain-cossette","http://schema.org/name":[{"@language":"fr","@value":"Sylvain Cossette"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/a3006456-14f1-4417-aa25-e6c92081a1f9"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/sylvain-cossette.html"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/sylvain-cossette-2025.jpg"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/description":[{"@language":"fr","@value":"Sylvain Cossette souligne les 25 ans de RENDEZ-VOUS, projet marquant de sa carrière musicale. C’est au début des années 2000 que le populaire chanteur a eu l’idée de reprendre sur disque et sur scène des succès tels que Pas besoin de frapper, Si Dieu Existe, Harmonium, Say it ain’t so Joe, The long and winding road, Love of my life, La maison sous les arbres, et plusieurs autres. Aujourd’hui, il invite le public à revivre cette expérience musicale dans une nouvelle mise en scène autobiographique touchante et feutrée. L’auteur-compositeur-interprète qui a vendu 1 million d’albums en carrière en profitera aussi pour revisiter les nombreux succès de sa discographie (Que je t’aime, Je pense encore à toi, Et pourtant, Blanc, Reste si tu veux, Tu reviendras, Si j’me rappelle, Reviens-moi) en plus de ses reprises à succès 70s, 80s. Un rendez-vous musical à ne pas manquer!"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/sylvain-cossette.html#WebPage"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-06-26T20:00:00-04:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/CRAhIuS8z78?si=SXnGM3UyVtlgTeE9"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-sylvain-cossette#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-sylvain-cossette#Offer_1"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-26T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-sylvain-cossette#2026-06-26T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-sylvain-cossette#2026-06-27T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-sylvain-cossette#2026-09-26T200000-0400"}]},{"@id":"http://kg.footlight.io/resource/a3006456-14f1-4417-aa25-e6c92081a1f9","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Sylvain Cossette"}],"http://schema.org/hasOccupation":[{"@value":"Chanteur"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Chanteur"}],"http://schema.org/sameAs":[{"@value":"http://kg.artsdata.ca/resource/K12-471"},{"@value":"http://www.wikidata.org/entity/Q3506961"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/sylvain-cossette.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:21:49.946-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/sylvain-cossette.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-sylvain-cossette#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"55"}],"http://schema.org/url":[{"@value":"https://www.ovation.ca/00000V/fr/Event/?seriesId=F4BE0957%2DAE40%2D452A%2D9CDF%2D2EFECE45CE85\u0026venueId=0E%2D0E0001"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-sylvain-cossette#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"51"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-sylvain-cossette#2026-06-26T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Sylvain Cossette"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-06-26T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-sylvain-cossette#2026-06-27T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Sylvain Cossette"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-06-27T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-sylvain-cossette#2026-09-26T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Sylvain Cossette"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-26T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-guy-nantel","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/guy-nantel-2026.jpg"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/guy-nantel.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Guy Nantel"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/spec-qc-ca_artiste_guy-nantel"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-11T20:00:00-05:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/guy-nantel.html#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/description":[{"@language":"fr","@value":"Alors que l’actualité regorge de sujets explosifs, d’opinions tranchées et de contradictions bien de chez nous, l’humoriste Guy Nantel revient avec un tout nouveau spectacle au titre pour le moins intrigant : Bienveillant. Oui, vous avez bien lu. BIENVEILLANT. Le mot qu’on associe le moins spontanément à un humoriste reconnu pour son franc-parler, son regard critique et son humour sociopolitique sans compromis. Disons que le titre donne le ton! Depuis toujours, Nantel est cette voix différente qui ose dire tout haut ce que tout le monde pense tout bas. Avec sa lucidité mordante et son ironie bien placée, il met le doigt sur nos travers collectifs, nos incohérences et nos grands débats d’époque, tout ça au grand plaisir du public. Avec ce 7e spectacle en carrière, Guy Nantel continue de faire ce qu’il fait le mieux : nous dérouter, nous désarmer et surtout nous faire rire à gorge déployée!"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-guy-nantel#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-guy-nantel#Offer_1"}]},{"@id":"http://kg.footlight.io/resource/spec-qc-ca_artiste_guy-nantel","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@value":"Guy Nantel"}],"http://schema.org/image":[{"@value":"https://spec.qc.ca/uploads/artistes/guy-nantel-2.jpg"}],"http://schema.org/sameAs":[{"@value":"http://kg.artsdata.ca/resource/K12-194"}],"http://schema.org/description":[{"@language":"fr","@value":"Guy Nantel s'impose comme un humoriste unique et original. Avec ses prises de position, Nantel fait réfléchir, dénonce et met en évidence les contradictions de notre société en s'attaquant aux travers humains. Seul dans son créneau, son côté caustique et sarcastique donne un style singulier à cet humoriste populaire. Avec son nouveau spectacle, Corrompu, Guy Nantel analyse notre société et ses enjeux avec esprit et cynisme, fidèle à ses habitudes. Irrévérencieux, sans filtre et sans tabou, Nantel n’a pas son pareil! Scandales politiques, financiers, conspiration, collusion, corruption; il dénonce, propose et s'interroge. Dans ce quatrième one-man-show, il nous présente toute la vérité, rien que la vérité sur la société québécoise, mais également sur lui-même en se posant de multiples questions : Ferait-il un bon politicien? Succomberait-il à la corruption? Salué comme l’humoriste politique et social de sa génération, il se révèle de façon inattendue avec Corrompu. Fort du succès de son précédent spectacle, pour lequel il a vendu plus de 100 000 billets, Guy Nantel nous livre encore une fois un contenu à la hauteur des attentes, un contenu intelligent, engagé et hilarant !"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/guy-nantel.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:29:05.163-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/guy-nantel.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-guy-nantel#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E052F\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"50"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-guy-nantel#Offer_1","http://schema.org/price":[{"@value":"46"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-christian-marc-gendron","http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/christian-marc-gendron-2026.jpg"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/christian-marc-gendron.html"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-17T20:00:00-04:00"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-196"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/name":[{"@language":"fr","@value":"Christian Marc Gendron"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/christian-marc-gendron.html#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"Après le succès phénoménal de la série de concerts Piano Man, qui a rassemblé plus de 150 000 spectateurs à travers le Québec au cours des dernières années, Christian Marc Gendron revient sur scène avec un tout nouveau spectacle: Trilogie. Ce concept unique réunira les meilleurs numéros de ses trois dernières tournées, en plus de proposer de nouvelles créations inspirées du répertoire des grands Pianoman tels que Jerry Lee Lewis, Ray Charles, Stevie Wonder, Elton John et, bien sûr, Billy Joel. Le spectacle inclura également un hommage à Gino Vannelli ainsi que plusieurs moments d’humour comme seul Christian Marc Gendron sait les livrer. Avec Trilogie, Christian Marc Gendron vous invite dans son univers musical et scénique incomparable, toujours accompagné de son équipe de 9 musiciens sous la direction musicale de Simon Godin et de la participation exceptionnelle de Manon Séguin. Veuillez noter que l'artiste pourrait inviter les spectateurs à se lever pendant le spectacle. Nous préférons vous en avertir."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-christian-marc-gendron#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-christian-marc-gendron#Offer_1"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-18T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-christian-marc-gendron#2026-05-17T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-christian-marc-gendron#2026-05-18T200000-0400"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/christian-marc-gendron.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:23:06.491-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/christian-marc-gendron.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-christian-marc-gendron#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E01HS\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"54"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-christian-marc-gendron#Offer_1","http://schema.org/price":[{"@value":"50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-christian-marc-gendron#2026-05-17T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Christian Marc Gendron"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-17T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-christian-marc-gendron#2026-05-18T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Christian Marc Gendron"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-18T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-osg-du-songe-aux-noces","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K5-165"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"autres-evenements"}],"http://schema.org/name":[{"@language":"fr","@value":"L'Orchestre symphonique de Gatineau"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-28T19:30:00-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/osg-du-songe-aux-noces.html"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/osg-du-songe-aux-noces.html#WebPage"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/autres-evenements/osg-du-songe-aux-noces.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"*NOUVEAUTÉ : soirée spéciale Apéro-Concert aux abonnés Quand le rêve rencontre la fête, la nuit devient inoubliable… De la malice éclatante des Noces de Figaro de Mozart à la poésie enchantée du Songe d’une nuit d’été de Mendelssohn, laissez-vous emporter dans un voyage musical suspendu entre fantaisie, tendresse et émerveillement. Pour cette soirée unique, l’Orchestre symphonique de Gatineau (OSG) accueillera les sopranos québécoises Marianne Lambert et Hélène Brunet, deux voix d’exception qui se distinguent sur la scène internationale et canadienne. Nous aurons aussi le plaisir d’ajouter la voix de l’artiste gatinois, Wassim Aboutanos, qui par sa narration ajoutera sensibilité et poésie touchant le cœur du public. Une fascinante rencontre avec des artistes de haut calibre, pour une nuit musicale d’une rare intensité. De l’éphémère à l’éternel, entrez dans l’instant : une parenthèse magique où l'actualité s'efface devant la splendeur du rêve et de la musique. Réservez votre évasion musicale dès aujourd'hui! PROGRAMME Mozart: Les Noces de Figaro (Extraits) Mendelssohn: Le Songe d’une nuit d’été, Op. 61 Hélène Brunet, soliste Marianne Lambert, soliste Wassim Aboutanos narrateur Chœur de l’OSG, direction Antonio Llaca Chœur de filles du Centre d’Excellence Artistique De La Salle, direction Maxime Bégin Yves Léveillé, chef Orchestre symphonique de Gatineau (OSG) *Apéro-concert – Une rencontre en toute convivialité Un nouveau rendez-vous chaleureux où le Maestro et le directeur général vous accueillent dans un salon aménagé au foyer de la Salle Odyssée. Autour d’un verre, ils se prêtent au jeu des questions-réponses et partagent avec vous les coulisses de leur parcours et de leur travail. Un moment privilégié d’échange et de proximité. Réservé aux abonnés"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-osg-du-songe-aux-noces#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-osg-du-songe-aux-noces#Offer_1"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-osg-du-songe-aux-noces#Offer_2"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/osg-du-songe-aux-noces.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:31:11.787-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/osg-du-songe-aux-noces.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-osg-du-songe-aux-noces#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04XV\u0026CodeSiteClient=00000V"}],"http://schema.org/price":[{"@value":"66"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-osg-du-songe-aux-noces#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"étudiant"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"30"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-osg-du-songe-aux-noces#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"12 ans et -"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"20"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-benny-jones","@type":["http://schema.org/Event"],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/benny-jones.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Benny Jones"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/39c7cd56-43b8-4ca4-ab13-ac8a7d201684"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/soirees-cabaret/benny-jones.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-12-01T20:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/benny-jones.html#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/AzIZMOZ8S9I?si=thZbq8DNFm3iOAnp"}],"http://schema.org/keywords":[{"@value":"soirees-cabaret"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/dad71250-8ec8-4b41-aa18-47eef056a04c"}],"http://schema.org/description":[{"@language":"fr","@value":"«J'ai grandi avec le bois dans le dos, la mer dans la face... Mais surtout, j'ai eu la chance de grandir entouré de monde... du vrai monde! Du monde qui m'ont raconté leurs histoires. Beau temps, mauvais temps... Et toutes ces histoires-là, au fil du temps, j'ai décidé d’en faire des chansons... Parce que, des chansons, ça aide à se rappeler du passé, comme à s'inventer un futur... Mais surtout, ça sert à mettre un peu de magie dans le présent.» Gagnant du prix Étoile Stingray en 2019, l’auteur-compositeur-interprète Benny Jones débarque dans l’univers musical francophone en 2020 avec son premier album Parce qu’il m’arrive. Gaspésien d’origine, devenu Madelinot d’adoption, Benny Jones parcourt la province à la rencontre du public, pour chanter ses histoires et raconter ses chansons. Fort de son expérience scénique, dont les 1ères parties de 2Frères et tournées ROSEQ, il est notamment lauréat des prix Réseau des Grands Espaces et RADARTS lors des Rencontres d’Automne du ROSEQ 2023. Sous la réalisation de Martin Hogan, Benny présente son nouvel opus Petit album d’automne… d’hiver, dont les premiers extraits Dehors et Best of de toi se hissent au somment des ventes francophones iTunes à leur sortie, ainsi que sur les ondes radiophoniques. * Formule cabaret au foyer de la salle Odyssée / Ambiance intime et conviviale / Bar en fonction / Places limitées"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-benny-jones#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-benny-jones#Offer_1"}]},{"@id":"http://kg.footlight.io/resource/39c7cd56-43b8-4ca4-ab13-ac8a7d201684","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"en","@value":"Benny Jones"}],"http://schema.org/hasOccupation":[{"@value":"Musical artist"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Musical artist"}],"http://schema.org/url":[{"@value":"https://www.bennyjonesmusique.com/"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/benny-jones.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:27:52.557-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/benny-jones.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-benny-jones#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E052G\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"30"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-benny-jones#Offer_1","http://schema.org/price":[{"@value":"26"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-annie-villeneuve","@type":["http://schema.org/Event"],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/annie-villeneuve.html#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/name":[{"@language":"fr","@value":"Annie Villeneuve"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/annie-villeneuve.html"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/annie-villeneuve.jpg"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/j-aIGp2fnNo?si=fpY5w0eU1X4vbW2V"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-10T20:00:00-05:00"}],"http://schema.org/description":[{"@language":"fr","@value":"Après plus de sept ans, Annie Villeneuve est enfin de retour avec un tout nouveau spectacle afin de présenter son 6e album en carrière. Tout à son image, ce concert sera porté par sa dextérité vocale, des anecdotes attachantes et des moments de grande complicité avec son public. Avec ses grands classiques ainsi que ses nouveaux morceaux, vous allez profiter d’une soirée mémorable, chaleureuse et nostalgique à la fois!"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-annie-villeneuve#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-annie-villeneuve#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/annie-villeneuve.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-08T16:33:41.523-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/annie-villeneuve.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-annie-villeneuve#Offer_0","http://schema.org/price":[{"@value":"42"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04R5\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-annie-villeneuve#Offer_1","http://schema.org/price":[{"@value":"38"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-coco-belliveau","@type":["http://schema.org/Event"],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/coco-belliveau.html#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/coco-belliveau.html"}],"http://schema.org/description":[{"@language":"fr","@value":"En 1692 à Salem, des procès ont été mis en place afin de mettre fin à la sorcellerie. Des centaines d'années plus tard, Coco Belliveau entame son prochain spectacle. Coïncidence ? Nous ne croyons pas…. Joignez-vous à cette charmante acadienne neurodivergente (et totalement brûlée) pour une soirée de paroles envoûtantes. Vite, avant qu'elle ne soit clouée au pilori pour ses calembredaines et sa Cocologie aiguisée! * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/coco-belliveau.jpg"}],"http://schema.org/name":[{"@language":"fr","@value":"Coco Belliveau"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-06T20:00:00-05:00"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-coco-belliveau#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-coco-belliveau#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/coco-belliveau.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:34:41.254-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/coco-belliveau.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-coco-belliveau#Offer_0","http://schema.org/price":[{"@value":"35"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04Y1\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-coco-belliveau#Offer_1","http://schema.org/price":[{"@value":"31"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-p-a-methot","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-127"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/p-a-methot.html"}],"http://schema.org/name":[{"@language":"fr","@value":"P-A Méthot"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/p-a-methot-2026.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/p-a-methot.html#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/jsF1E5pN_zg?si=Y9hSDcPIO31ep4R7"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-27T20:00:00-05:00"}],"http://schema.org/description":[{"@language":"fr","@value":"Le seul et unique P-A Méthot est enfin de retour sur scène avec un nouveau spectacle intitulé Pardon?!. Raconteur authentique et rassembleur, P-A sait autant toucher le public que le faire rire. Après plus de 500 000 billets vendus pour ses deux premiers spectacles, l’humoriste chouchou revient avec son humour ancré dans le vrai monde et ses histoires savoureuses, transformant chaque situation du quotidien en un moment comique où tout le monde se reconnaît. Dans ce nouveau one-man-show, notre habile conteur répond aux grands questionnements de la vie avec une seule et unique question : «PARDON?!». Un troisième spectacle vrai, authentique et surtout hilarant."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-p-a-methot#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-p-a-methot#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/p-a-methot.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:38:50.955-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/p-a-methot.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-p-a-methot#Offer_0","http://schema.org/price":[{"@value":"52"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E054B\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-p-a-methot#Offer_1","http://schema.org/price":[{"@value":"48"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-roch-voisine","@type":["http://schema.org/Event"],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/roch-voisine.html#WebPage"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/QhmHQbHJd_w?si=Xt7YOIR8sgZRWnKb"}],"http://schema.org/description":[{"@language":"fr","@value":"Pour fêter les 35 ans de son album Hélène, certifié diamant grâce à plus d’un million de copies vendues, un disque qui a propulsé la «Rochmania» à des sommets inégalés, Roch Voisine nous revient avec un spectacle exceptionnel et fort en émotions. Avec ses hits qui ont traversé les décennies, comme les immenses succès Hélène, Là-bas dans l’ombre et Avant de partir, L’idole a marqué toutes les générations. Vivez une immersion dans l’univers de l’une des voix les plus emblématiques de la scène musicale francophone. Vous serez transportés dans le temps, revivant chaque émotion, chaque mélodie qui a fait de ses chansons des souvenirs intemporels. Une expérience unique qui ravira les fans de tous âges!"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/roch-voisine.jpg"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K2-5121"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-01T20:00:00-04:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Roch Voisine"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/roch-voisine.html"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-roch-voisine#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-roch-voisine#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/roch-voisine.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:27:04.752-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/roch-voisine.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-roch-voisine#Offer_0","http://schema.org/price":[{"@value":"94"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E0517\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-roch-voisine#Offer_1","http://schema.org/price":[{"@value":"90"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-louis-t","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/louis-t.html"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/louis-t.html#WebPage"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/f89e2c38-48f0-4129-bde3-df4bd123cc0b"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/name":[{"@language":"fr","@value":"Louis T"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-21T20:00:00-04:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/jcACMVsVQQo?si=_CwSZj5hPmh-Njp-"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/description":[{"@language":"fr","@value":"Louis T revient avec un 3e spectacle, à la fois plus mature et tannant. Avec humour, le nouveau quarantenaire nous partage ses réflexions sur la masculinité; ces hommes qui deviennent père à 80 ans, l’insécurité dans le lit, les séparations difficiles ou encore la place qu’occupe les minorités dans l’espace public. Avec un regard bienveillant, quoi que critique, sur la société et les hommes. Louis T arrive à la conclusion que malgré bien des défauts, les hommes modernes ne sont vraiment pas si pires que ça, quoi que... Un spectacle drôle, efficace et brillant! «Son approche fait son effet. Il parvient à glisser de bonnes blagues et à passer ses commentaires sociaux sans jamais laisser son public oublier qu’il est surtout là pour rire.» — La Presse, Marissa Groguhé «Un spectacle qui, sous son gros enrobage homérique, parfois même très niaiseux, se cache un propos engagé et engageant, bien dosé.» — Journal de Montréal, Frédérique de Simone «On rit, on est content et c’est un bon show, 8.5/10, c’est de l’humour intelligent!» — ICI Première, Nathalie Petrowski «Il a su charmer […] on a vraiment ri beaucoup!» — QUB Radio, Isabelle Perron"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/louis-t.jpg"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-louis-t#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-louis-t#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/louis-t.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:17:07.536-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/louis-t.html"}]},{"@id":"http://kg.footlight.io/resource/f89e2c38-48f0-4129-bde3-df4bd123cc0b","@type":["http://schema.org/Person"],"http://schema.org/alternateName":[{"@language":"fr","@value":"Louis T."}],"http://schema.org/sameAs":[{"@value":"http://www.wikidata.org/entity/Q16661350"},{"@value":"https://www.facebook.com/LouisTfanpage"}],"http://schema.org/name":[{"@language":"fr","@value":"Louis T"}],"http://schema.org/hasOccupation":[{"@language":"fr","@value":"Humoriste"}],"http://schema.org/url":[{"@value":"https://louist.ca/"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-louis-t#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E0512\u0026CodeSiteClient=00000V"}],"http://schema.org/price":[{"@value":"42"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-louis-t#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"38"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-kain-derniere-chance","@type":["http://schema.org/Event"],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/name":[{"@language":"fr","@value":"Kaïn"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/kain-derniere-chance.html#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"L’infatigable groupe Kaïn se paie la traite pour ses 25 ans d’existence et s’offre une place dans la cour des grands, le temps d’un album. Un hommage à ses idoles Québécois de Desjardins à Mandeville, en passant par Plume, Pag, Piché, Séguin et d’autres influences importantes du band. Plus festif et vivant que jamais, le spectacle promet la fête et la grande soif! Kaïn, un incontournable party Québécois."}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/kain-derniere-chance.html"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K5-432"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-23T20:00:00-04:00"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/kain-26.jpg"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-kain-derniere-chance#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-kain-derniere-chance#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/kain-derniere-chance.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:13:21.142-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/kain-derniere-chance.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-kain-derniere-chance#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"45"}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E0515\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-kain-derniere-chance#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"41"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-fred-dionne","@type":["http://schema.org/Event"],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/fred-dionne.html#WebPage"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/dad71250-8ec8-4b41-aa18-47eef056a04c"},{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/mhL-SpEKcxo?si=I1yLvTUUuG0OEFDu"}],"http://schema.org/keywords":[{"@value":"soirees-cabaret"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/7176231d-0218-41b4-88c0-c5ec76886f56"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/fred-dionne.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Fred Dionne"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/soirees-cabaret/fred-dionne-2026b.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-20T20:00:00-04:00"}],"http://schema.org/description":[{"@language":"fr","@value":"Depuis la sortie de son premier album Highways en 2022, Fred Dionne s’impose avec son son country-folk-bohémien en français. Avec son charisme et ses mélodies accrocheuses, il parcourt le Québec aux côtés de Matt Lang et de 2Frères, en plus de briller dans des festivals comme LASSO Montréal et les Francos. Ses succès Hangover et Highways se hissent au sommet des palmarès, lui valant le prix Révélation WKND et une nomination au Gala Country. À l’automne 2024, il remporte un premier Willie et obtient une nomination à l’ADISQ avant de dévoiler Eldorado, un album inspiré de ses voyages aux États-Unis. Mature et assumé, cet opus explore les rêves, les rencontres et la quête de liberté. Depuis janvier 2025, il reprend la route avec un spectacle festif et rassembleur qui promet d’enflammer les scènes du Québec! En nomination au Gala de l'ADISQ 2025 dans les catégories : Album de l'année - Country (Eldorado) Ma première nomination * Formule cabaret au foyer de la salle Odyssée / Ambiance intime et conviviale / Bar en fonction / Places limitées"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-fred-dionne#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-fred-dionne#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/fred-dionne.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:23:39.806-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/fred-dionne.html"}]},{"@id":"http://kg.footlight.io/resource/7176231d-0218-41b4-88c0-c5ec76886f56","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Fred Dionne"}],"http://schema.org/hasOccupation":[{"@language":"fr","@value":"Chanteur"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Chanteur"}],"http://schema.org/url":[{"@value":"https://www.freddionne.com/"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-fred-dionne#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E0514\u0026CodeSiteClient=00000V"}],"http://schema.org/price":[{"@value":"30"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-fred-dionne#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"26"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-pierre-verville","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/pierre-verville.html"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/pierre-verville.html#WebPage"}],"http://schema.org/name":[{"@language":"fr","@value":"Pierre Verville"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-17T20:00:00-04:00"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/pierre-verville-2026.jpg"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/description":[{"@language":"fr","@value":"Plongez dans l’univers unique de Pierre Verville avec Pour une dernière voix, un spectacle solo mêlant humour et imitations, et oscillant entre fiction et réalité. Car Pierre, dans ses magnifiques délires insomniaques, s’imagine sur scène en animateur de radio qui raconte sa propre vie, alors que son auditoire est là, dans la salle, juste devant lui… À travers ses évocations, Pierre revisite ses souvenirs d’enfance et sa carrière haute en péripéties, en incarnant avec brio des personnages phares de la culture québécoise. Il s’agit de son au revoir à la scène en solo. Coécrit avec Pierre-Michel Tremblay, Pour une dernière voix propose un voyage des années 1960 à aujourd’hui, où se croisent François Legault, Michel Tremblay, Luc Plamondon, Robert Charlebois et bien d’autres. Un spectacle tendre et humoristique, où le parcours personnel de Pierre Verville se conjugue avec notre histoire collective. Il est accompagné sur scène de la musicienne Nadine Turbide. Présent sur la scène culturelle depuis plus de 40 ans - il a commencé aux Lundis des ha ha! en 1983 -, l’humoriste et comédien a tout un bagage et il compte bien y piger allègrement pour la création de ce dernier spectacle. Pierre Verville a été l’une des étoiles de l’émission radiophonique À la semaine prochaine, qui vient de tirer sa révérence après 17 saisons sur les ondes de Radio-Canada. Il prête aussi ses traits à Marc Duchesne dans la série Ravages, diffusée sur Illico, et il a été chaleureusement acclamé cet été au gala de Véronique Claveau au festival Juste pour rire Québec. * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-pierre-verville#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-pierre-verville#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/pierre-verville.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:28:40.805-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/pierre-verville.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-pierre-verville#Offer_0","http://schema.org/price":[{"@value":"42"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E051Q\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-pierre-verville#Offer_1","http://schema.org/price":[{"@value":"38"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-patrick-groulx","@type":["http://schema.org/Event"],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/patrick-groulx.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"Avec toute la folie qu’on lui connait, l’humoriste Patrick Groulx revient sur scène avec un 5e one-man-show en rodage. Celui qui charme le public depuis plus de 25 ans vous invite à être aux premières loges de la création de son tout nouveau spectacle. * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/patrick-groulx.html#WebPage"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-186"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Patrick Groulx"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-28T20:00:00-04:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/patrick-groulx.html"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-patrick-groulx#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-patrick-groulx#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/patrick-groulx.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:25:27.461-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/patrick-groulx.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-patrick-groulx#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E051S\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/price":[{"@value":"40"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-patrick-groulx#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"36"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-vincent-vallieres","http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-1001"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/vincent-vallieres.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Vincent Vallières"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/_6NXn4Udt7k?si=VW2GYOmrYcCKsq4J"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/vincent-vallieres.html#WebPage"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/vincent-vallieres.jpg"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-15T20:00:00-04:00"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/description":[{"@language":"fr","@value":"Vincent Vallières revient sur scène entouré de ses musiciens pour un tout nouveau spectacle, où son imposant répertoire s'unit aux pièces de son nouvel album Les saisons, les secondes. Un spectacle à l’image de cet incontournable de la chanson québécoise: rassembleur, vivant et authentique."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-vincent-vallieres#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-vincent-vallieres#Offer_1"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2027-02-25T20:00:00-05:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-vincent-vallieres#2026-05-15T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-vincent-vallieres#2027-02-25T200000-0500"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/vincent-vallieres.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:21:25.183-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/vincent-vallieres.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-vincent-vallieres#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04XM\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"47"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-vincent-vallieres#Offer_1","http://schema.org/price":[{"@value":"43"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-vincent-vallieres#2026-05-15T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Vincent Vallières"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-15T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-vincent-vallieres#2027-02-25T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Vincent Vallières"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2027-02-25T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-jay-scott","@type":["http://schema.org/Event"],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/29wHKMPqNYk?si=cJERltZn4p4RQR8r"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/jay-scott-2026.jpg"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/c9c71cb1-dfda-4cad-9b13-eac3743b9306"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/jay-scott.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Jay Scøtt"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-06T20:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/jay-scott.html#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"Après le succès retentissant de Sa Plus Grande Première Tournée en 2021-2022, Jay Scøtt le Conquérant s’est lancé dans une tournée mondiale à grand déploiement à travers la province dans le cadre de son très garni Québec World Tour 2023-2024 pour ensuite annoncer son aventureuse tournée Chasse \u0026 Pêche de 5 festivals à l’été 2024. Place maintenant à la tournée officielle de son 2e album en carrière: Toutes les rues sont silencieuses. Tout au long du spectacle, Jay Scøtt vous invite, armé de sa guitare, de son ukulélé, de son clavier et de ses séquences, dans son univers mélancolique aux rimes et mélodies vocales accrocheuses. C’est dans un décor intime, chaleureux et une mise en scène imprévisible, frôlant la folie, que vous vivrez une expérience unique et authentique, teintée d’humour. Soyez prêts à chanter et entendre la simple histoire d'un autre humain à la poursuite du bonheur. À bonne franquette! Veuillez noter que l'artiste pourrait inviter les spectateurs à se lever pendant le spectacle. Nous préférons vous en avertir. Première partie : Or Bleu En 2023, le duo Or Bleu officialise sa signature avec l’étiquette Disques 7ième Ciel. Composé des producteurs musicaux Charles Cozy et Fruits, il s’agit du premier projet de ce genre à rejoindre les rangs de la maison de disques. Un album dont la musique est entièrement composée par le duo et où les paroles sont assurées par une multitude de featurings. Découvrez l’histoire et le projet Or Bleu via cette capsule réalisée par leur ami de longue date, le réputé Phil Chagnon. * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-jay-scott#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-jay-scott#Offer_1"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-jay-scott#Offer_2"}]},{"@id":"http://kg.footlight.io/resource/c9c71cb1-dfda-4cad-9b13-eac3743b9306","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Jay Scøtt"}],"http://schema.org/hasOccupation":[{"@value":"auteur-compositeur-interprète"},{"@language":"fr","@value":"auteur-compositeur-interprète"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"auteur-compositeur-interprète"}],"http://schema.org/url":[{"@value":"https://www.117records.ca/jay-scott/"}],"http://schema.org/sameAs":[{"@value":"http://www.wikidata.org/entity/Q115804773"}],"http://schema.org/alternateName":[{"@language":"fr","@value":"Jay Scott"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/jay-scott.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:28:16.231-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/jay-scott.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-jay-scott#Offer_0","http://schema.org/price":[{"@value":"40"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E0508\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-jay-scott#Offer_1","http://schema.org/price":[{"@value":"36"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-jay-scott#Offer_2","http://schema.org/price":[{"@value":"32"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"étudiants"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-les-39-marches","http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/les-39-marches.html#WebPage"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/x1MrcBBmsYU?si=YY8ndX8NfRs1n4ls"}],"http://schema.org/name":[{"@language":"fr","@value":"Les 39 marches"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/theatre-hors-serie/les-39-marches.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-04T20:00:00-04:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"theatre-hors-serie"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/les-39-marches.html"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/description":[{"@language":"fr","@value":"Les 39 marches, la comédie policière des plus primées à travers le monde et qui connait un succès fracassant depuis son lancement au Québec, prend la route avec en vedette Benoît Brière, Martin Drainville, Luc Guérin et Évelyne Rompré. Adaptée à l’origine par Patrick Barlow et tirée du roman de John Buchan et du classique film à suspense The 39 Steps d’Alfred Hitchcock, cette satire hilarante met en scène des dizaines de personnages, permettant au génie comique et au jeu physique des quatre acteurs de se déployer sous nos yeux. Cette délirante intrigue d’espionnage raconte l’histoire de Richard Hannay, un gentleman faussement accusé du meurtre d’une femme, forcé de partir dans une incroyable cavale à travers l’Angleterre. Pendant cette chasse à l’homme aussi captivante que désopilante, Hannay devra aussi tenter de décoder un mystérieux secret pour sauver son pays. Il s’agit d’un spectacle pour un public de tous âges, alors que cette histoire aussi riche en rebondissements qu’en divertissement tient en haleine jusqu’à la toute fin, en plus de faire rire et d’éblouir par les prouesses scéniques permettant de créer autant de lieux et de personnages. Dans un savant mélange de mystère, d’action et d’humour, Les 39 marches est la pièce humoristique que vous souhaitez absolument voir! Avec Les 39 marches, le meurtre n’a jamais été aussi drôle! Présenté en accord avec Fiery Angel Limited Adapté par Patrick Barlow Tiré du roman de John Buchan Tiré du film de Alfred Hitchcock Licencié par ITV Global Entertainment Limited Un concept original de Simon Corble et Nobby Dimon Production Entourage Mise en scène Benoît Brière, Martin Drainville, Luc Guérin Adaptation Benoît Brière, Martin Drainville, Luc Guérin Les 39 marches est présenté avec entente spéciale accordée par Concord Theatricals Ce spectacle contient des effets lumineux incluant des effets stroboscopiques susceptibles d’incommoder certains spectateurs. «Une des pièces de théâtre les plus drôles de l'ère moderne» - Journal de Montréal «Dans l'habile scénographie de Normand Blais, tous les moyens sont bons pour contruire le récit.» - Le Devoir «Un véritable tour de force du trio Brière, Drainville, Guérin» - 7 Jours «Une interprétation impeccable, les changements de costumes [et les] décors sont un feu roulant.» - CIME FM «Un hommage éclaté à Hitchcock et un triomphe pour ses comédiens» - ATUVU.CA «C'est un humour très burlesque... les gens riaient fort autour de moi!» - ICI première"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-les-39-marches#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-les-39-marches#Offer_1"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-les-39-marches#Offer_2"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-05T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-les-39-marches#2026-05-04T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-les-39-marches#2026-05-05T200000-0400"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/les-39-marches.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:17:15.584-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/les-39-marches.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-les-39-marches#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E01NI\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/price":[{"@value":"59"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-les-39-marches#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"55"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-les-39-marches#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"→ Abonnés théâtre*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"45"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-les-39-marches#2026-05-04T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Les 39 marches"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-04T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-les-39-marches#2026-05-05T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Les 39 marches"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-05T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-koros","http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/koros.html"}],"http://schema.org/name":[{"@language":"fr","@value":"KOROS"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/autres-evenements/koros.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/koros.html#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"autres-evenements"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/jObKG11cDdk?si=Umlmf2qZS8CYZZPX"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-18T05:00:00-05:00"}],"http://schema.org/description":[{"@language":"fr","@value":"La danse en réalité virtuelle Un casque, des écouteurs, et vous voilà sur scène, au centre d’un spectacle si vrai qu’on peut presque le toucher. Avec KOROS, vivez une expérience immersive de danse grâce à la réalité virtuelle! Des chorégraphes québécoises de renom vous invitent au cœur de leur univers puissant, faisant de vous le point central d'extraits revisités de pièces enivrantes. Jeux de lumières, performance 360, effets spéciaux interactifs: cet événement chorégraphique signé par l’Agora de la danse vous transporte sans que vous quittiez votre chaise. Pour plus d’information, consultez le www.koros.ca Réservation obligatoire au koros.ca/gatineau Une expérience à vivre au centre d'exposition Art-image, à la Maison de la culture de Gatineau. Les places sont limitées, réservation obligatoire. Remerciement très spécial à Hélène Blackburn et la compagnie Cas Public pour leur précieuse collaboration au projet. L’Agora de la danse remercie pour son soutien financier le Conseil des arts et des lettres du Québec Crédits à télécharger ici"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-koros#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-23T05:00:00-05:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-koros#2026-02-18T050000-0500"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-koros#2026-02-19T050000-0500"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-koros#2026-02-20T050000-0500"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-koros#2026-02-21T050000-0500"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-koros#2026-02-22T050000-0500"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-koros#2026-02-23T050000-0500"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/koros.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:37:50.373-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/koros.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-koros#Offer_0","http://schema.org/url":[{"@value":"https://www.koros.ca/gatineau"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"0"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-koros#2026-02-18T050000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"KOROS"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-18T05:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-koros#2026-02-19T050000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"KOROS"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-19T05:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-koros#2026-02-20T050000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"KOROS"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-20T05:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-koros#2026-02-21T050000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"KOROS"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-21T05:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-koros#2026-02-22T050000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"KOROS"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-22T05:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-koros#2026-02-23T050000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"KOROS"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-23T05:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-noel-une-tradition-en-chanson","@type":["http://schema.org/Event"],"http://schema.org/description":[{"@language":"fr","@value":"C’est un rendez-vous dès 2026 pour la 9e édition de Noël une tradition en chanson. Pour plusieurs, ce rendez-vous festif donne le coup d’envoi à la période des festivités de fin d’année. Au fil des ans, ils ont été plus de 100 000 spectateurs à venir entendre les plus belles voix du Québec interpréter des classiques incontournables. Noël une tradition en chanson fait vibrer la corde sensible des souvenirs d’enfance en misant sur la nostalgie et le plaisir de célébrer ensemble. Une mise en scène de Joël Legendre, avec Jacques Roy à la direction musicale."}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/noel-une-tradition-en-chanson.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Noël une tradition en\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tchanson"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-12-06T20:00:00-05:00"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/noel-une-tradition-en-chanson.html#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/noel-une-tradition-en-chanson-2026.jpg"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-noel-une-tradition-en-chanson#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-noel-une-tradition-en-chanson#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/noel-une-tradition-en-chanson.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:17:35.174-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/noel-une-tradition-en-chanson.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-noel-une-tradition-en-chanson#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E0556\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"61"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-noel-une-tradition-en-chanson#Offer_1","http://schema.org/price":[{"@value":"57"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-jey-fournier","@type":["http://schema.org/Event"],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/dad71250-8ec8-4b41-aa18-47eef056a04c"}],"http://schema.org/description":[{"@language":"fr","@value":"Un jour, on se réveille et on réalise qu’on a 30 ans (ou presque). On voit les autres avancer, on essaie de ne pas trop se comparer, mais on a quand même l’impression d’être en retard sur la vie. Entre la séparation, les remises en question et la quête du bonheur, j’ai choisi d’en rire. Tant qu’à être en retard, autant en faire un spectacle. Première partie : Rolly Assal Humoriste d’origine égypto-syrienne, Rolly Assal est un pois chiche pure laine à la tronche sympathique et à l’humour réfléchi. Authentique et armé d’une énergie contagieuse, il sait charmer son public autant sur scène que sur le web. * Formule cabaret au foyer de la salle Odyssée / Ambiance intime et conviviale / Bar en fonction / Places limitées"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"soirees-cabaret"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/bRLRznhaQhw?si=FDoPwfXXng30R2uN"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/9c098b46-cc97-4c46-8b83-ea55bea0be56"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/jey-fournier.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Jey Fournier"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/soirees-cabaret/jey-fournier.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/jey-fournier.html#WebPage"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-26T20:00:00-05:00"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-jey-fournier#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-jey-fournier#Offer_1"}]},{"@id":"http://kg.footlight.io/resource/9c098b46-cc97-4c46-8b83-ea55bea0be56","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Jey Fournier"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/jey-fournier.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:31:28.532-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/jey-fournier.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-jey-fournier#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E052E\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"34"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-jey-fournier#Offer_1","http://schema.org/price":[{"@value":"30"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal-2","http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/la-piece-qui-tourne-mal-2.html#WebPage"}],"http://schema.org/keywords":[{"@value":"theatre-hors-serie"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/3jZmBEafPZw?si=ki9SGmIdR4tInHkH"}],"http://schema.org/name":[{"@language":"fr","@value":"La pièce qui tourne mal"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/theatre-hors-serie/la-piece-qui-tourne-mal.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/la-piece-qui-tourne-mal-2.html"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2025-09-01"}],"http://schema.org/description":[{"@language":"fr","@value":"Monarque présente LA PIÈCE QUI TOURNE MAL / THE PLAY THAT GOES WRONG! Avec cette pièce, André Robitaille et Mario Provencher ont réussi le coup de maitre d’amener sur scène au Québec, et en français, LE SPECTACLE qu’ils souhaitaient produire depuis toujours. Décrocher les droits de cette production maintes fois primée et récompensée relevait du rêve! Synopsis Un meurtre a eu lieu dans un manoir de campagne et un inspecteur est chargé de l'affaire pour trouver le coupable. Cependant, lorsque cette intrigue est confiée aux comédiens maladroits de la Cornley Drama Society, tout ce qui peut mal tourner... tourne mal! Les acteurs et l'équipe se débattent pour se rendre jusqu'au dernier rappel, avec des conséquences hilarantes! Avez-vous déjà découvert qui a assassiné Charles Haversham? Vous devrez venir voir par vous-même! Vivez un chaos total et savourez cette comédie parfaite qui vous fera rire aux éclats! Écrite par Henry Lewis, Jonathan Sayer et Henry Shields Dirigée par André Robitaille Distribution Fabien Cloutier, LeLouis Courchesne, Guillaume Lambert, Pierre-François Legendre, Olivia Palacci, Rémi-Pierre Paquin, Julie Ringuette et Jonathan Roberge Présenté par Monarque Productions en accord avec Mischief Worldwide Ltd."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal-2#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal-2#Offer_1"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal-2#Offer_2"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal-2#Offer_3"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal-2#Offer_4"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2025-09-01"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal-2#2025-09-01"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal-2#2026-08-08T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal-2#2026-08-09T140000-0400"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/la-piece-qui-tourne-mal-2.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:16:48.554-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/la-piece-qui-tourne-mal-2.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal-2#Offer_0","http://schema.org/price":[{"@value":"75"}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E01PX\u0026CodeSiteClient=00000V"}],"http://schema.org/name":[{"@language":"fr","@value":"→ Tarif lève-tôt* : 65 $"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal-2#Offer_1","http://schema.org/price":[{"@value":"65"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée → Tarif lève-tôt* : 61 $"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal-2#Offer_2","http://schema.org/price":[{"@value":"71"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"groupes de 4 personnes et plus - pour une durée limitée!"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal-2#Offer_3","http://schema.org/price":[{"@value":"61"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal-2#Offer_4","http://schema.org/price":[{"@value":"58"}],"@type":["http://schema.org/Offer"]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal-2#2025-09-01","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"La pièce qui tourne mal"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#date","@value":"2025-09-01"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal-2#2026-08-08T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"La pièce qui tourne mal"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-08T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-la-piece-qui-tourne-mal-2#2026-08-09T140000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"La pièce qui tourne mal"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-09T14:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-les-boys","http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"theatre-hors-serie"}],"http://schema.org/description":[{"@language":"fr","@value":"LES BOYS : LA PLUS GRANDE FRANCHISE CINÉMATOGRAPHIQUE QUÉBECOISE ADAPTÉE À LA SCÈNE Propriétaire d’une brasserie et entraîneur d’une équipe de hockey très, très amateure, Stan perd une somme d’argent colossale en jouant au poker contre Méo, un Shylock qu’il considère comme son ami. Pour que Stan puisse s’acquitter de sa dette, Méo a une idée : ils vont jouer ça quitte ou double sur la glace, dans le cadre d’une partie de hockey. Si l’équipe de Stan gagne, on efface la dette. Mais si elle perd… Méo devient le propriétaire de sa précieuse brasserie. Tandis que Méo réunit une bande d’armoires à glace et de repris de justice, Stan ne peut compter que sur ses boys. Son équipe est composée d’hommes provenant de différents horizons, que rien n’unit à part peut-être leur amour du hockey et la bière qu’ils boivent ensemble après les games… Pour aller au secours de celui qui est plus que leur coach, ils devront mettre de côté leurs différends et apprendre le véritable sens de l’amitié. Plus de 25 ans après sa sortie, Guillaume Corbeil et Marc St-Martin revisitent le film de Louis Saia, basé sur une idée originale de Richard Goudreau et d’un scénario de Christian Fournier, en le portant à la scène. C’est l’occasion de retrouver l’équipe de hockey la plus célèbre du Québec (OK, peut-être la deuxième plus célèbre…) et de se rappeler l’importance de la dureté du mental. Distribution Stan : Emmanuel Bilodeau Méo : Louis Champagne Bob : David Savard Ti-Guy : Benoit Drouin-Germain Mario : Mikhail Ahooja Léopold : Thomas Derasp-Verge Fannie, Lisette, Brigitte, Karine et Marcel : Lauren Hartley, en remplacement de Marie-Pier Labrecque Fern : Denis Houle Boisvert : Didier Lucien Sonia : Marilou Morin Julien : Nicolas Pinson Jean-Charles : Gabriel Sabourin «Un spectacle inventif, porté par une solide équipe d'interprètes [...] Ces \"Boys\" vont cartonner partout au Québec!» -Luc Boulanger, La Presse «UNE VRAIE BELLE SURPRISE! La pièce Les Boys est un pur plaisir du début à la fin. C'est drôle, bien rythmé et bourré de clins d'oeil qu'on adore! On rit fort! ALLEZ-Y, VOUS NE LE REGRETTEREZ PAS! » -Hadi Hassin, TVA/LCN «C'est dynamique, inventif et très efficace! MISSION ACCOMPLIE! » -Pascal Morrissette, Rythme FM «Les Boys, le spectacle : ce retour modernisé dans la nostalgie du film nous apporte rires, candeur et bien du plaisir!» - Josée Laberge, BP ARTS MÉDIA «Dans mon livre à moi, cette adaptation est sa-vou-reuse. La mise en scène est particulièrement efficace.» - Isabelle Perron, QUB RADIO"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/7Ir3o77UCvg?si=9sFY97f8lYGVA19f"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/les-boys.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Les Boys"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/theatre-hors-serie/les-boys-2026.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-29T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/les-boys.html#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-les-boys#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-les-boys#Offer_1"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-les-boys#Offer_2"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-31T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-les-boys#2026-05-29T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-les-boys#2026-05-30T150000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-les-boys#2026-05-30T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-les-boys#2026-05-31T150000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-les-boys#2026-05-31T200000-0400"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/les-boys.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:40:08.250-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/les-boys.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-les-boys#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E01NF\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/price":[{"@value":"75"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-les-boys#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"71"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-les-boys#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"→ Abonnés théâtre*"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"61"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-les-boys#2026-05-29T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Les Boys"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-29T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-les-boys#2026-05-30T150000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Les Boys"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-30T15:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-les-boys#2026-05-30T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Les Boys"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-30T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-les-boys#2026-05-31T150000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Les Boys"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-31T15:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-les-boys#2026-05-31T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Les Boys"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-31T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-philippe-laprise","http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-128"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Philippe Laprise"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/philippe-laprise-2026.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"Cette année est bien remplie pour Philippe Laprise! Il lance son 5e one-man-show en 25 ans de carrière tout en soulignant ses 50 ans! Si on ajoute à ça le fait qu’il est père de 3 enfants et d’un chien, co-propriétaire d’une équipe de baseball, en couple depuis près de 25 ans et qu’il a presque gagné (mais pas tant que ça) Sortez-moi d’ici et LOL : Qui rira le dernier?, Philippe a vraiment dépassé les attentes de ses proches… et les siennes! Dans ce spectacle solo, Philippe se questionne sur ce que c’est réellement être au sommet à notre époque où tout évolue rapidement. Maintenant rendu à un âge «presque» vénérable, Philippe s’assume et n’a plus rien à perdre. Il nous présente donc Au sommet, un spectacle sans filtre mais toujours avec une bonne dose d’autodérision. La route vers le sommet est longue, surtout quand l’intérieur des cuisses frotte ensemble, alors partons tous maintenant et suivons Philippe au sommet!"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/philippe-laprise.html#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/Ix6ZIg-7y_0?si=qfC5nhgYQewzWESL"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/philippe-laprise.html"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-06-16T20:00:00-04:00"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-philippe-laprise#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-philippe-laprise#Offer_1"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-18T20:00:00-05:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-philippe-laprise#2026-06-16T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-philippe-laprise#2026-11-18T200000-0500"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/philippe-laprise.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:26:24.748-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/philippe-laprise.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-philippe-laprise#Offer_0","http://schema.org/price":[{"@value":"50"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E010N\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-philippe-laprise#Offer_1","http://schema.org/price":[{"@value":"46"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-philippe-laprise#2026-06-16T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Philippe Laprise"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-06-16T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-philippe-laprise#2026-11-18T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Philippe Laprise"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-18T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-michel-fugain","@type":["http://schema.org/Event"],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/michel-fugain.jpg"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/michel-fugain.html#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"Depuis plus de 50 ans, Michel Fugain nous fait voyager dans le monde de demain, celui du Big Bazar et celui de tous ses grands classiques d’hier et d’aujourd’hui. Il nous chante La vie, l’amour, etc… Attention mesdames et messieurs, préparez-vous pour Une belle histoire. C’est la fête avec le retour de Michel Fugain au Québec!"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/qchLmWDgwNw?si=hrjRQZoSCSlbNqwL"}],"http://schema.org/name":[{"@language":"fr","@value":"Michel Fugain"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-16T20:00:00-04:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/michel-fugain.html"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-michel-fugain#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-michel-fugain#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/michel-fugain.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:16:31.446-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/michel-fugain.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-michel-fugain#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04XE\u0026CodeSiteClient=00000V"}],"http://schema.org/price":[{"@value":"94"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-michel-fugain#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"90"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-pervers-par-accident","http://schema.org/name":[{"@language":"fr","@value":"Pervers par\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\taccident"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/124177b3-a135-4261-aa4c-42ca95309f48"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/eLOUKJQ_R5c?si=5DAub3VFkl4pOfWy"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/pervers-par-accident.html"}],"http://schema.org/keywords":[{"@value":"theatre-hors-serie"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/pervers-par-accident.html#WebPage"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/theatre-hors-serie/pervers-par-accident.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-07-03T20:00:00-04:00"}],"http://schema.org/description":[{"@language":"fr","@value":"Adaptation québécoise du succès new-yorkais d’Andrew Goffman (7 500 représentations), Pervers par accident raconte l’histoire d’un garçon devenu homme après la découverte inattendue, dans le placard de son père, d’une collection de vieilles cassettes… disons, éducatives à sa façon. Ce premier contact avec le “plaisir défendu” déclenche une fascination qui, avec les années, se transforme en véritable dépendance. Entre fantasme et réalité, humour et gêne, le personnage se livre avec une franchise désarmante sur ses obsessions, ses échecs amoureux et sa quête d’équilibre entre désir et affection. Porté par Tommy Joubert et mis en scène par Tammy Verge, le spectacle alterne les éclats de rire et les moments de tendresse. On y retrouve le ton cru mais sincère d’une confession drôle et humaine, où le ridicule devient un miroir touchant de nos contradictions les plus intimes. Un one-man-show théâtral pour adultes avertis, irrésistiblement drôle, un brin coquin, et d’une étonnante sensibilité."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-pervers-par-accident#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-pervers-par-accident#Offer_1"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-pervers-par-accident#Offer_2"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-07-04T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-pervers-par-accident#2026-07-03T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-pervers-par-accident#2026-07-04T200000-0400"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/pervers-par-accident.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:38:06.787-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/theatre-hors-serie/pervers-par-accident.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-pervers-par-accident#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E01QI\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"50"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-pervers-par-accident#Offer_1","http://schema.org/price":[{"@value":"46"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-pervers-par-accident#Offer_2","http://schema.org/price":[{"@value":"36"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"→ Abonnés théâtre*"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-pervers-par-accident#2026-07-03T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Pervers par\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\taccident"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-07-03T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_theatre-hors-serie-pervers-par-accident#2026-07-04T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Pervers par\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\taccident"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-07-04T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-sinem-kara","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/soirees-cabaret/sinem-kara.jpg"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/sinem-kara.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Sinem Kara"}],"http://schema.org/description":[{"@language":"fr","@value":"Sinem Kara monte sur scène avec une énergie contagieuse. D'origine turque et née au Québec, elle porte un regard à la fois tendre et piquant sur nos habitudes, nos contradictions et nos petites manies — un regard qui mélange deux cultures avec la finesse d'un trait d'humour bien placé. Attendez-vous à des anecdotes du quotidien revisitée à la manière Sinem, la famille, la langue, les repas qui durent des heures, les malentendus et ces petites situations qui prennent une dimension universelle quand elle les raconte. Son ton est chaleureux, souvent autodérisoire toujours proche du public. Venez pour rire, mais aussi pour voir l'œuvre naître, vos réactions comptent, vos rires aident à sculpter le spectacle. C'est une expérience intime et généreuse, parfaite pour celles et ceux qui aiment l'humour en devenir et le regard neuf d'une artiste qui observe le monde et nous le rend plus drôle. En nomination au Gala Les Olivier 2025: Découverte de l’année * Formule cabaret au foyer de la salle Odyssée / Ambiance intime et conviviale / Bar en fonction / Places limitées"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-06-15T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/sinem-kara.html#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"soirees-cabaret"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/2KtjhH6bJ60?si=_bAqoqla7YFiDyb2"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/dad71250-8ec8-4b41-aa18-47eef056a04c"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-sinem-kara#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-sinem-kara#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/sinem-kara.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:33:07.815-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/sinem-kara.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-sinem-kara#Offer_0","http://schema.org/price":[{"@value":"34"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E053D\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-sinem-kara#Offer_1","http://schema.org/price":[{"@value":"30"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-jerry-tremblay","@type":["http://schema.org/Event"],"http://schema.org/description":[{"@language":"fr","@value":"Grand gagnant de l'édition 2025 de l'émission Quel talent Cultivateur de malaise expérimenté mais involontaire, Jerry rayonne par son manque d’habileté sociale et son désir profond d’être aimé. Le spectacle, un cocktail explosif de cirque et de malaises croustillants, offre une plongée à la fois hilarante et touchante dans les réalités parfois absurdes de la quête amoureuse des cœurs désespérés. Pour le meilleur et pour le pire, Jerry Tremblay vous garantit une soirée inoubliable remplie de surprises. Description en une phrase Cultivateur de malaises et de bonheur. Description en trois mots Cirque - Malaises - Humour «Un expert du timing réservé, un roi des occasions manquées, un empereur au jeu des attentes déçues, un géant en tant qu'artiste du vélo.» - Leipziger-Volkszeitung, Leipzig «Sans dire un mot, avec ses mimiques et ses acrobaties, il a rapidement impressionné et fait s’esclaffer un public» - Le Soleil, Québec «Bonnement bluffant et réussit sans peine à faire rire la salle» - BPartsmedia, Montreal"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/jerry-tremblay.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Jerry Tremblay"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/jerry-tremblay.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-09T20:00:00-05:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/jerry-tremblay.html#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/recordedIn":[{"@value":"https://player.vimeo.com/video/1012408844?h=6943fdf890"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-jerry-tremblay#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-jerry-tremblay#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/jerry-tremblay.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:32:00.683-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/jerry-tremblay.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-jerry-tremblay#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E052J\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"30"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-jerry-tremblay#Offer_1","http://schema.org/price":[{"@value":"26"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-la-route-du-rire","http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/la-route-du-rire.html"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/dad71250-8ec8-4b41-aa18-47eef056a04c"},{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/soirees-cabaret/la-route-du-rire-25-26.jpg"}],"http://schema.org/name":[{"@language":"fr","@value":"La route du rire"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/la-route-du-rire.html#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"soirees-cabaret"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-16T20:00:00-05:00"}],"http://schema.org/description":[{"@language":"fr","@value":"La route du rire c’est sans aucun doute le chemin à prendre pour passer une belle soirée et rire de bon cœur grâce aux meilleurs humoristes émergents du moment! Que ce soit des visages connus ou des artistes de la relève, vous ne regretterez pas le détour! * Formule cabaret au foyer de la salle Odyssée / Ambiance intime et conviviale / Bar en fonction / Places limitées"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-la-route-du-rire#Offer_0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-13T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-la-route-du-rire#2026-02-16T200000-0500"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-la-route-du-rire#2026-03-16T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-la-route-du-rire#2026-04-13T200000-0400"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/la-route-du-rire.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:19:57.297-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/la-route-du-rire.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-la-route-du-rire#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E01GB\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"20"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-la-route-du-rire#2026-02-16T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"La route du rire"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-16T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-la-route-du-rire#2026-03-16T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"La route du rire"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-16T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-la-route-du-rire#2026-04-13T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"La route du rire"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-13T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-alexandre-forest","@type":["http://schema.org/Event"],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-02T20:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/alexandre-forest.html#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"Alexandre Forest entame le rodage de son premier one man show. C’est l'occasion parfaite de le découvrir et de vous laisser emporter dans son univers. Grâce à un alliage subtil entre autodérision et finesse d'esprit, l'humoriste réussit à capturer avec brio l’essence des petites et grandes incongruences de la vie quotidienne. * Formule cabaret au foyer de la salle Odyssée / Ambiance intime et conviviale / Bar en fonction / Places limitées"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"soirees-cabaret"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/soirees-cabaret/alexandre-forest.jpg"}],"http://schema.org/name":[{"@language":"fr","@value":"Alexandre Forest"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/alexandre-forest.html"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/4leFnm5PGJ0?si=flWIfDeDe2XMp1Ux"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/dad71250-8ec8-4b41-aa18-47eef056a04c"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-alexandre-forest#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-alexandre-forest#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/alexandre-forest.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:32:09.000-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/alexandre-forest.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-alexandre-forest#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E052K\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"29"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-alexandre-forest#Offer_1","http://schema.org/price":[{"@value":"25"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-kevin-parent","@type":["http://schema.org/Event"],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/kevin-parent.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Kevin Parent"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/kevin-parent.jpg"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/bddebad1-6735-4d46-ba98-5957e76b0350"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-06T20:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/kevin-parent.html#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/FtXM05PPB2c?si=4NMZX8CnMfGQh-Jz"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/description":[{"@language":"fr","@value":"L’auteur-compositeur-interprète lance en 1995 un premier disque, Pigeon d’argile. Cet album le propulsera dans les sommets des chartes de vente au Québec. Pigeon d'argile se vend à lui seul à plus de 375 000 exemplaires. Il aura aussi vendu près d’un million d’albums au Québec en carrière, faisant de lui l’un des artistes francophones les plus populaires. Kevin Parent remonte sur scène afin de célébrer les 30 ans de Pigeon d’argile. Reconnu pour son timbre de voix grave ainsi que pour son jeu unique à la guitare acoustique, il revisitera les pièces de cet album sans oublier les chansons les plus connues de son répertoire. Présenté dans une atmosphère conviviale et chaleureuse, le spectacle est tantôt doux et intime, tantôt festif et rythmé, mais toujours empreint d’humour et d’amour!"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-kevin-parent#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-kevin-parent#Offer_1"}]},{"@id":"http://kg.footlight.io/resource/bddebad1-6735-4d46-ba98-5957e76b0350","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Kevin Parent"}],"http://schema.org/hasOccupation":[{"@value":"Musicien"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Musicien"}],"http://schema.org/sameAs":[{"@value":"http://www.wikidata.org/entity/Q3195784"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/kevin-parent.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:34:01.707-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/kevin-parent.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-kevin-parent#Offer_0","http://schema.org/price":[{"@value":"49"}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E052L\u0026CodeSiteClient=00000V"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-kevin-parent#Offer_1","http://schema.org/price":[{"@value":"45"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-the-franklin-electric","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"The Franklin Electric"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/the-franklin-electric.html"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/the-franklin-electric.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"Un nouveau chapitre s’ouvre pour The Franklin Electric avec la sortie de l’album Victory Songs. Ce tournant se reflète aussi sur scène, avec un spectacle qui marque une étape importante dans l’évolution du projet. Plus intime, cette performance tisse un lien plus profond avec le public, comme une seconde vague. L’honnêteté et l’authenticité de Jon Matte y occupent toute la place. Il y partage les thèmes qui nourrissent ses chansons et reconnaît que chacun suit son propre chemin, en faisant de son mieux — en évoluant, en changeant, en guérissant. Le spectacle adopte un son plus acoustique et folk, avec des moments dépouillés autour d’un seul micro, portés par un violon, entrecoupés de passages plus intenses et chargés d’énergie. Le spectacle met à l’honneur l’album Victory Songs, tout en revisitant des classiques de The Franklin Electric."}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/e7fd9303-0a5c-46fe-84a0-4dade7012a97"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-03T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/the-franklin-electric.html#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/Zekhga3DwKQ?si=Px60NZIKyjSPSpBA"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-the-franklin-electric#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-the-franklin-electric#Offer_1"}]},{"@id":"http://kg.footlight.io/resource/e7fd9303-0a5c-46fe-84a0-4dade7012a97","@type":["http://schema.org/Organization"],"http://schema.org/name":[{"@language":"en","@value":"The Franklin Electric"}],"http://schema.org/url":[{"@value":"https://www.thefranklinelectric.com/"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Musical group"}],"http://schema.org/sameAs":[{"@value":"https://www.facebook.com/THEFRANKLINELECTRIC/"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/the-franklin-electric.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:32:25.268-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/the-franklin-electric.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-the-franklin-electric#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E0532\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"44"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-the-franklin-electric#Offer_1","http://schema.org/price":[{"@value":"40"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-noir-silence","@type":["http://schema.org/Event"],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Noir Silence"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/noir-silence.jpg"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/X_EX4pha67I?si=xg8gQxclTgBSbzfv"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/noir-silence.html#WebPage"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/noir-silence.html"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-21T20:00:00-04:00"}],"http://schema.org/description":[{"@language":"fr","@value":"En novembre 1996, Noir Silence marque l'histoire en remportant trois Félix, propulsé par le succès monumental On jase de toi, aujourd'hui classé parmi les grands classiques de la SOCAN. Ce titre emblématique a lancé une carrière ponctuée de succès radios inoubliables : Malade, Made in USA, Le vent a tourné, Méo Penché et T'es tellement belle, pour n'en nommer que quelques-uns. Toujours aussi unis et portés par leur formation originale, les cinq membres de Noir Silence reviennent sur scène avec toute l’énergie et la passion qui ont fait leur renommée. Ce spectacle, à la fois rétrospectif et vibrant, célèbre une carrière riche et marquante, tout en rassemblant des générations autour de leur rock authentique et intemporel. Première partie : Tim Brink And The Rising Tim Brink n’est pas juste un chanteur — c’est une voix cinématographique, brute et habitée, qui évoque Peaky Blinders, Sons of Anarchy ou Yellowstone. Finaliste marquant de La Voix IV, approché par Stone Temple Pilots, il revient avec Tim Brink And The Rising, un projet musical entre Tarantino, Johnny Cash et Rage Against the Machine. Une proposition urgente, sensible, viscérale. Tim Brink ne chante pas: il incarne."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-noir-silence#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-noir-silence#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/noir-silence.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:18:37.580-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/noir-silence.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-noir-silence#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04O2\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/price":[{"@value":"46"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-noir-silence#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"42"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_country-folk-chris-klafford","@type":["http://schema.org/Event"],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"country-folk"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/Xe--Py4A0CY?si=kBUYyB71NuSuPJbv"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/country-folk/chris-klafford.html#WebPage"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Chris Kläfford"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/country-folk/chris-klafford.html"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/country-folk/chris-klafford.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-15T20:00:00-04:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/a9a70dca-dffc-430f-8afa-7b69674e5a1c"}],"http://schema.org/description":[{"@language":"fr","@value":"**Spectacle présenté en anglais** Chris Kläfford, né en 1989, est un auteur-compositeur-interprète originaire de Lindesberg, en Suède. Il est considéré comme l'une des voix musicales les plus fortes à avoir émergé de son pays d'origine depuis longtemps. Après avoir payé son dû en tant que musicien itinérant pendant plus de dix ans, il a fait une percée bien méritée en remportant l'émission de télévision Swedish Idol en 2017. Au fil des ans, Chris a construit et maintenu une solide carrière. En 2019, il a reçu les éloges des juges et du public dans l'émission America's Got Talent, où il s'est qualifié pour les demi-finales. Grâce à son talent, à sa productivité et à sa popularité croissante sur les médias sociaux, il a réussi à gagner une base de fans dans le monde entier, plusieurs singles platine et une exposition record avec un numéro 1 sur les ondes radio. Veuillez noter que l'artiste pourrait inviter les spectateurs à se lever pendant le spectacle. Nous préférons vous en avertir."}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_country-folk-chris-klafford#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_country-folk-chris-klafford#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/country-folk/chris-klafford.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:14:15.961-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/country-folk/chris-klafford.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_country-folk-chris-klafford#Offer_0","http://schema.org/price":[{"@value":"45"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E051X\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_country-folk-chris-klafford#Offer_1","http://schema.org/price":[{"@value":"41"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-la-grande-veillee-du-2-pierrots","@type":["http://schema.org/Event"],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/la-grande-veillee-du-2-pierrots.html#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"Le 2 Pierrots est de retour pour faire revivre au public des moments inoubliables avec son spectacle La grande veillée du 2 Pierrots. Fidèle à ses habitudes, la légendaire boîte à chanson a pour but de faire danser et chanter sur les airs des chansons québécoises et francophones les plus connues et aimées. De Paul Piché aux Cowboys Fringants en passant par Les Colocs, le spectacle met en lumière la riche histoire musicale du Québec de façon festive comme le 2 Pierrots avait l’habitude de le faire chaque soirée dans son établissement de l’époque à Montréal. Avec un groupe de 4 musiciens sur scène qui accompagnent Carl Cadorette (longtemps chansonnier au 2 Pierrots) et de la chanteuse Raffy, La grande veillée du 2 Pierrots offre une occasion aux nostalgiques de renouer avec l’endroit et à ceux qui n’ont jamais eu la chance d’y aller d’expérimenter un party comme seule cette boîte à chanson peut offrir. Veuillez noter que les artistes pourraient inviter les spectateurs à se lever pendant le spectacle. Nous préférons vous en avertir."}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/la-grande-veillee-du-2-pierrots.jpg"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/la-grande-veillee-du-2-pierrots.html"}],"http://schema.org/name":[{"@language":"fr","@value":"La grande veillée du 2 Pierrots"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-24T20:00:00-04:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/xC2fbAWLgVA?si=rtQwIqvxV0pCXv-y"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-la-grande-veillee-du-2-pierrots#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-la-grande-veillee-du-2-pierrots#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/la-grande-veillee-du-2-pierrots.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:32:34.026-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/la-grande-veillee-du-2-pierrots.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-la-grande-veillee-du-2-pierrots#Offer_0","http://schema.org/name":[{"@language":"fr","@value":""}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E0534\u0026CodeSiteClient=00000V"}],"http://schema.org/price":[{"@value":"40"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-la-grande-veillee-du-2-pierrots#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"36"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-liliane-blanco-binette","@type":["http://schema.org/Event"],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/9H_aaXwx4t0?si=m_CUzRRHCbipCG-F"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/6a167be3-2bfe-4d4b-ae89-af38c5524ab8"}],"http://schema.org/name":[{"@language":"fr","@value":"Liliane Blanco-Binette"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/liliane-blanco-binette.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-07T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/liliane-blanco-binette.html#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/liliane-blanco-binette.html"}],"http://schema.org/description":[{"@language":"fr","@value":"Avec une lucidité désarmante et une autodérision qui frappe juste, Liliane propose un premier one woman show où l’humour se mêle à l’intime. Ce n’est pas un simple spectacle, c’est une rencontre. Elle transforme ses vulnérabilités en matière comique, révélant une façon décalée d’observer le monde et d’assumer ses contradictions. Entre confidences crues et complicité partagée, elle entraîne le public dans un moment à la fois fragile et nécessaire, où l’on se reconnaît dans ce qui nous échappe et nous dépasse. En nomination au Gala Les Olivier 2025: Découverte de l’année"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-liliane-blanco-binette#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-liliane-blanco-binette#Offer_1"}]},{"@id":"http://kg.footlight.io/resource/6a167be3-2bfe-4d4b-ae89-af38c5524ab8","@type":["http://schema.org/Person"],"http://schema.org/hasOccupation":[{"@language":"fr","@value":"Humoriste"}],"http://schema.org/url":[{"@value":"https://lilianeblancobinette.com/"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}],"http://schema.org/name":[{"@language":"fr","@value":"Liliane Blanco-Binette"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/liliane-blanco-binette.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:32:42.598-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/liliane-blanco-binette.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-liliane-blanco-binette#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E0535\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"39"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-liliane-blanco-binette#Offer_1","http://schema.org/price":[{"@value":"35"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-harmonies-dun-soir","@type":["http://schema.org/Event"],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/harmonies-dun-soir.html"}],"http://schema.org/description":[{"@language":"fr","@value":"Après tant d’années à marier passionnément nos voix et nos instruments pour faire vibrer à l’unisson nos cœurs et les vôtres, l’Ensemble d’En direct de l’univers a imaginé un spectacle original construit autour des coups de cœur de chacun des membres de la formation. Le spectacle Harmonies d’un soir, sera en tournée dans plusieurs villes du Québec. Vous êtes conviés à une fête où chaque note, chaque parole résonneront comme un souvenir heureux. Sous la direction musicale de Jean- Benoît Lasanté. Accompagné des musiciens, Paul Brochu (batterie), Jean-François Groulx (piano), Peter Kisilenko (basse) et Philippe Drolet (clavier). Et bien sûr, les cinq excellents choristes qui prendront le devant de la scène; Virginie Cummins, Jason McNally, Roxane Filion, Émilie Janvier et Valérie Boulianne-Lefrançois"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/P9FH2rKNbrI?si=udSnPDmj-i1mdgFO"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/harmonies-dun-soir.jpg"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-24T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/harmonies-dun-soir.html#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/name":[{"@language":"fr","@value":"Harmonies d'un soir"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-harmonies-dun-soir#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-harmonies-dun-soir#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/harmonies-dun-soir.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:32:50.398-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/harmonies-dun-soir.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-harmonies-dun-soir#Offer_0","http://schema.org/price":[{"@value":"58"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E01L5\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-harmonies-dun-soir#Offer_1","http://schema.org/price":[{"@value":"54"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-show-du-gite","@type":["http://schema.org/Event"],"http://schema.org/description":[{"@language":"fr","@value":"Vivez une soirée exceptionnelle avec Le Show du Gîte, le spectacle-bénéfice incontournable de variétés musicales! Cette année, laissez-vous séduire par la voix envoûtante d’Annie Villeneuve, la magie du Choeur qui bat, et l’énergie communicative de notre animateur et chanteur Wassim Aboutanos. Découvrez également deux étoiles montantes de Star Académie, Romie Lacasse et Mirani Coelho, qui illumineront la scène de leurs voix magnifiques. Enfin, préparez-vous à être émerveillé par le talent du danseur Zachary Bastille. Ne manquez pas cette expérience unique, où émotions et divertissement seront au rendez-vous pour tous les publics! Billets disponibles: VIP: 125 $ (incluant un don de 50 $ au GITE AMI). Pour recevoir un reçu d’impôts (au montant de 50 $), veuillez contacter le GITE AMI en utilisant ce courriel : dea@legiteami.org Réguliers: 45 $ Note : Vous voulez soutenir Le Gîte-Ami! Faites un don ici : Faire un don | Gîte-Ami | Réinsertion sociale en Outaouais. Don de 20$ pour obtenir reçu d’impôts."}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/show-du-gite.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Le Show du Gîte"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/autres-evenements/show-du-gite-2026.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-25T20:00:00-04:00"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/show-du-gite.html#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"autres-evenements"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-show-du-gite#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-show-du-gite#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/show-du-gite.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:20:50.260-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/autres-evenements/show-du-gite.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-show-du-gite#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E0549\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"45"}],"http://schema.org/name":[{"@language":"fr","@value":"régulier"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_autres-evenements-show-du-gite#Offer_1","http://schema.org/price":[{"@value":"125"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"VIP"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_country-folk-guylaine-tanguay","@type":["http://schema.org/Event"],"http://schema.org/description":[{"@language":"fr","@value":"Avec I LOVE ROCK’N’ ROLL, Guylaine Tanguay nous ramène là où tout a commencé: au bar Le Clairon de Normandin, où elle chantait, pendant ses études, les grands succès rock des années 80. De Joan Jett, Pat Benatar, Heart, AC/DC, Queen, Bon Jovi, The Police, Kiss et Tina Turner, jusqu’à Paul Daraîche, elle revisite avec fougue et nostalgie les chansons qui ont marqué son parcours. Un spectacle festif, vibrant et plein d’énergie! Veuillez noter que l'artiste pourrait inviter les spectateurs à se lever pendant le spectacle. Nous préférons vous en avertir."}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/country-folk/guylaine-tanguay.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Guylaine Tanguay"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K13-82"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-08-21T20:00:00-04:00"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/country-folk/guylaine-tanguay.html#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"country-folk"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/country-folk/guylaine-tanguay.jpg"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_country-folk-guylaine-tanguay#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_country-folk-guylaine-tanguay#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/country-folk/guylaine-tanguay.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:33:15.824-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/country-folk/guylaine-tanguay.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_country-folk-guylaine-tanguay#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E053J\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"49"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_country-folk-guylaine-tanguay#Offer_1","http://schema.org/price":[{"@value":"45"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-boucar-diouf","http://schema.org/description":[{"@language":"fr","@value":"Nos bungalows sont le théâtre d'une cohabitation singulière entre trois prédateurs, réunis autour d'un frigidaire. Si l'humain et le chien cherchent naturellement la compagnie des autres, le chat tend à être plus solitaire. Comme l'a si bien illustré Ira Lewis: «Le chien pense: ils me nourrissent, ils me protègent, ils doivent être des dieux. Le chat, lui, pense: ils me nourrissent, ils me protègent, je dois être un dieu.» Ce spectacle propose un mélange habile de biologie, d'histoire et de culture afin de raconter, avec humour et sensibilité, la présence du chien et du chat auprès de l'humain, une histoire intimement liée à notre humanité. Un spectacle drôle, instructif et touchant."}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/boucar-diouf.html"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-226"}],"http://schema.org/name":[{"@language":"fr","@value":"Boucar Diouf"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/boucar-diouf.html#WebPage"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/boucar-diouf-2026.jpg"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-13T15:00:00-04:00"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-boucar-diouf#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-boucar-diouf#Offer_1"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2027-03-07T20:00:00-05:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-boucar-diouf#2026-09-13T150000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-boucar-diouf#2026-09-13T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-boucar-diouf#2027-03-07T200000-0500"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/boucar-diouf.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:14:32.596-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/boucar-diouf.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-boucar-diouf#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E00XX\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"55"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-boucar-diouf#Offer_1","http://schema.org/price":[{"@value":"51"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-boucar-diouf#2026-09-13T150000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Boucar Diouf"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-13T15:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-boucar-diouf#2026-09-13T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Boucar Diouf"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-13T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-boucar-diouf#2027-03-07T200000-0500","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Boucar Diouf"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2027-03-07T20:00:00-05:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-jey-fournier","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/jey-fournier.html"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/9c098b46-cc97-4c46-8b83-ea55bea0be56"}],"http://schema.org/name":[{"@language":"fr","@value":"Jey Fournier"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/jey-fournier.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"Un jour, on se réveille et on réalise qu’on a 30 ans (ou presque). On voit les autres avancer, on essaie de ne pas trop se comparer, mais on a quand même l’impression d’être en retard sur la vie. Entre la séparation, les remises en question et la quête du bonheur, j’ai choisi d’en rire. Tant qu’à être en retard, autant en faire un spectacle. Première partie : Rolly Assal Humoriste d’origine égypto-syrienne, Rolly Assal est un pois chiche pure laine à la tronche sympathique et à l’humour réfléchi. Authentique et armé d’une énergie contagieuse, il sait charmer son public autant sur scène que sur le web. * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-12-18T20:00:00-05:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/jey-fournier.html#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/bRLRznhaQhw?si=FDoPwfXXng30R2uN"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-jey-fournier#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-jey-fournier#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/jey-fournier.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:24:39.769-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/jey-fournier.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-jey-fournier#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E055C\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"38"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-jey-fournier#Offer_1","http://schema.org/price":[{"@value":"34"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-andre-sauve","http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-286"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/andre-sauve.html#WebPage"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/andre-sauve.html"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/andre-sauve.jpg"}],"http://schema.org/description":[{"@language":"fr","@value":"André Sauvé fait un retour sur scène avec une création audacieuse où son regard unique sur le monde puise son inspiration dans la nature et ses mystères. Il nous livre ses observations dont chaque détail prend une dimension surprenante, ne voyant dans cette nature qui l'entoure qu’une métaphore de l’esprit humain. À travers ce voyage, il tisse un lien entre humour et poésie, explorant l’âme humaine à travers le prisme de son environnement. Humour et réflexion s’unissent dans cette expérience fidèle à la touche et au talent inimitable d’André Sauvé. Mise en scène : Louis-Karl Tremblay Direction artistique : Pierre Bernard"}],"http://schema.org/name":[{"@language":"fr","@value":"André Sauvé"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-10T20:00:00-04:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-andre-sauve#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-andre-sauve#Offer_1"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-21T20:00:00-04:00"}],"@type":["http://schema.org/EventSeries"],"http://schema.org/subEvent":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-andre-sauve#2026-03-10T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-andre-sauve#2026-03-11T200000-0400"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-andre-sauve#2026-09-21T200000-0400"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/andre-sauve.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:26:32.616-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/andre-sauve.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-andre-sauve#Offer_0","http://schema.org/price":[{"@value":"56"}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E00ZP\u0026CodeSiteClient=00000V"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-andre-sauve#Offer_1","http://schema.org/price":[{"@value":"52"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-andre-sauve#2026-03-10T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"André Sauvé"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-10T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-andre-sauve#2026-03-11T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"André Sauvé"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-11T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-andre-sauve#2026-09-21T200000-0400","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"André Sauvé"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-21T20:00:00-04:00"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-un-piano-et-des-mots","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/un-piano-et-des-mots.jpg"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/bLipa5a9jVw?si=rJEdoNwt_OzQvjCW"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/un-piano-et-des-mots.html"}],"http://schema.org/name":[{"@language":"fr","@value":"Un piano et des mots"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/un-piano-et-des-mots.html#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-07T20:00:00-04:00"}],"http://schema.org/description":[{"@language":"fr","@value":"Plongez au cœur d'une expérience musicale inoubliable avec Un piano et des mots, un spectacle à la fois intimiste et vibrant. Marc-André Fortin et Audrey-Louise Beauséjour, accompagnés de trois musiciens talentueux, vous convient à un voyage à travers le temps, mettant en lumière les joyaux du répertoire musical francophone. Des paroles évocatrices, des mélodies envoûtantes, Un piano et des mots offre une ode à la nostalgie et à l'authenticité. Ces chansons, qui résonnent comme des marqueurs temporels dans nos vies, seront interprétées avec passion et virtuosité, créant ainsi un moment privilégié où chaque note et chaque mot résonnent avec émotion. On a qu’à penser à certaines paroles telles que : «C’est dans la pénombre que la lumière est belle» «T’es la plus belle saison de ma vie» «Pour l’amour qu’il nous reste» «S’il fallait» Rejoignez-nous pour une soirée unique où la puissance des mots et la magie du piano se rencontrent! Équipe Création : Productions Grand V. (Valérie Boulianne et Janik V. Dufour) Textes : Janik V. Dufour (en collaboration avec Louis-Charles Sylvestre) Mise en scène : Janik V. Dufour Direction musicale et piano: Gabriel Bertrand Gagnon Musiciens : Maxime Reed (batterie) et Maxime Alarie (basse) Éclairages : Mathieu Paradis Son : Steve Branchaud"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-un-piano-et-des-mots#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-un-piano-et-des-mots#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/un-piano-et-des-mots.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:21:41.779-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/un-piano-et-des-mots.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-un-piano-et-des-mots#Offer_0","http://schema.org/price":[{"@value":"49"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E0518\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-un-piano-et-des-mots#Offer_1","http://schema.org/price":[{"@value":"45"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-val-belzil","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Val Belzil"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/soirees-cabaret/val-belzil.jpg"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/val-belzil.html"}],"http://schema.org/keywords":[{"@value":"soirees-cabaret"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/Kk-v97RkkyA?si=3LKturwGbJbXG-Lw"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/dad71250-8ec8-4b41-aa18-47eef056a04c"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/val-belzil.html#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/12ca4d70-2308-48a7-9554-13725e436405"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-05T20:00:00-05:00"}],"http://schema.org/description":[{"@language":"fr","@value":"Val Belzil présente son tout premier spectacle solo. Avec un stand-up authentique, assumé et débordant d’autodérision, elle propose un moment d’humour à la fois original et inspirant. Accompagnée de son personnage, Denise, fière Saguenéenne et icône du DIY, elle mêle confidences et réflexions percutantes. Originaire de Québec, Val a osé monter sur scène à 43 ans et n’a jamais regardé en arrière. Finaliste remarquée de l’émission Le prochain stand-up, on a pu la voir en première partie de grandes figures de l’humour comme Lise Dion et Daniel Lemire. Avec DIY, Fais-le toi-même, Val prouve qu’il n’est jamais trop tard pour se réinventer et suivre ses passions — même si cela implique, comme elle le dit, de faire des «choix de vie discutables». * Formule cabaret au foyer de la salle Odyssée / Ambiance intime et conviviale / Bar en fonction / Places limitées"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-val-belzil#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-val-belzil#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/val-belzil.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:31:52.824-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/soirees-cabaret/val-belzil.html"}]},{"@id":"http://kg.footlight.io/resource/12ca4d70-2308-48a7-9554-13725e436405","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Val Belzil"}],"http://schema.org/hasOccupation":[{"@language":"fr","@value":"Humoriste"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}],"http://schema.org/url":[{"@value":"https://valbelzil.com/"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-val-belzil#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E0538\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"34"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_soirees-cabaret-val-belzil#Offer_1","http://schema.org/price":[{"@value":"30"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-emile-proulx-cloutier","@type":["http://schema.org/Event"],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/name":[{"@language":"fr","@value":"Émile Proulx-Cloutier"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/emile-proulx-cloutier.html#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"chanson"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/6U-GCApla_g?si=pNIhMYelDNWpbQne"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/chanson/emile-proulx-cloutier-2025.jpg"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/spec-qc-ca_artiste_emile-proulx-cloutier"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/emile-proulx-cloutier.html"}],"http://schema.org/description":[{"@language":"fr","@value":"Après trois albums aux textes éloquents, d’une richesse musicale saluée par le public et la critique, ÉMILE PROULX-CLOUTIER nous éblouit une fois de plus avec sa voix percutante, assumée et émouvante, confirmant qu’il a su définir le son d’un univers musical bien à lui. Dans ce spectacle, l’auteur-compositeur-interprète nous livre les nouvelles chansons de son album Ma main au feu, ainsi que plusieurs autres pièces poétiques et vives de son répertoire. «𝐴𝑣𝑒𝑐 𝑀𝑎 𝑚𝑎𝑖𝑛 𝑎𝑢 𝑓𝑒𝑢 – 𝐴𝑐𝑡𝑒𝑠 𝐼 – 𝐼𝐼 – 𝐼𝐼𝐼, Émile Proulx-Cloutier 𝑛𝑜𝑢𝑠 𝑜𝑓𝑓𝑟𝑒 𝑢𝑛 𝑠𝑝𝑒𝑐𝑡𝑎𝑐𝑙𝑒 𝑠𝑎𝑖𝑠𝑖𝑠𝑠𝑎𝑛𝑡, 𝑎̀ 𝑙𝑎 𝑓𝑜𝑖𝑠 𝑚𝑢𝑠𝑖𝑐𝑎𝑙, 𝑡𝘩𝑒́𝑎̂𝑡𝑟𝑎𝑙 𝑒𝑡 𝑝𝑜𝑒́𝑡𝑖𝑞𝑢𝑒. 𝑆𝑢𝑟 𝑠𝑐𝑒̀𝑛𝑒, 𝑖𝑙 𝑖𝑛𝑐𝑎𝑟𝑛𝑒 𝑐𝘩𝑎𝑞𝑢𝑒 𝑚𝑜𝑡, 𝑐𝘩𝑎𝑞𝑢𝑒 𝑛𝑜𝑡𝑒, 𝑎𝑣𝑒𝑐 𝑢𝑛𝑒 𝑖𝑛𝑡𝑒𝑛𝑠𝑖𝑡𝑒́ 𝑟𝑎𝑟𝑒. 𝐼𝑙 𝑦 𝑖𝑛𝑡𝑒𝑟𝑝𝑟𝑒̀𝑡𝑒 𝑙𝑒𝑠 𝑐𝘩𝑎𝑛𝑠𝑜𝑛𝑠 𝑑𝑒 𝑠𝑜𝑛 𝑝𝑙𝑢𝑠 𝑟𝑒́𝑐𝑒𝑛𝑡 𝑎𝑙𝑏𝑢𝑚, 𝑀𝑎 𝑚𝑎𝑖𝑛 𝑎𝑢 𝑓𝑒𝑢, 𝑡𝑜𝑢𝑡 𝑒𝑛 𝑟𝑒𝑣𝑖𝑠𝑖𝑡𝑎𝑛𝑡 𝑐𝑒𝑟𝑡𝑎𝑖𝑛𝑒𝑠 𝑝𝑖𝑒̀𝑐𝑒𝑠 𝑚𝑎𝑟𝑞𝑢𝑎𝑛𝑡𝑒𝑠 𝑑𝑒 𝑠𝑜𝑛 𝑟𝑒́𝑝𝑒𝑟𝑡𝑜𝑖𝑟𝑒. 𝐿𝑒 𝑟𝑒́𝑠𝑢𝑙𝑡𝑎𝑡.ᐣ 𝑈𝑛 𝑣𝑜𝑦𝑎𝑔𝑒 𝑒𝑛 𝑡𝑟𝑜𝑖𝑠 𝑡𝑒𝑚𝑝𝑠 𝑜𝑢̀ 𝑙𝑎 𝑝𝑎𝑟𝑜𝑙𝑒 𝑑𝑒𝑣𝑖𝑒𝑛𝑡 𝑠𝑜𝑢𝑓𝑓𝑙𝑒, 𝑙𝑎 𝑚𝑢𝑠𝑖𝑞𝑢𝑒 𝑑𝑒𝑣𝑖𝑒𝑛𝑡 𝑐𝑟𝑖, 𝑒𝑡 𝑙𝑎 𝑓𝑟𝑎𝑔𝑖𝑙𝑖𝑡𝑒́, 𝑢𝑛𝑒 𝑓𝑜𝑟𝑚𝑒 𝑑𝑒 𝑓𝑜𝑟𝑐𝑒.» - Blogue Meve et cie Ce qu'ils ont dit à propos de son album Ma main au feu : «Bien plus qu’un album, un chef d’oeuvre en soi. Tout y est de ce qu’Émile laissait deviner de son génie depuis des années. Un sommet.» - Monique Giroux, ICI Musique «Un opus qui frappe et caresse. Entre fougue et douceur, Émile nous brasse et nous rassure. On ressort de cette écoute, comblé.e.s par la musique, les brass, le piano, envoûté.e.s par la parole libre et vulnérable.» - Pénélope, ICI Première «Émile Proulx-Cloutier ressent et traduit le monde d’aujourd’hui avec une rare acuité.» - Josée Lapointe, La Presse «Fresque chansonnière et musicale [...] le grand oeuvre d’Émile Proulx-Cloutier. La rage de vivre l’emporte.» - Sylvain Cormier, Le Devoir. «On se surprend à vouloir tenir plusieurs chansons dans nos mains, imprimées sur du papier. Pour mieux les annoter, pour surligner quelques passages et plier le coin de certaines pages.» - Léa Harvey, Le Soleil"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-06-09T20:00:00-04:00"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-emile-proulx-cloutier#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-emile-proulx-cloutier#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/emile-proulx-cloutier.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:32:58.574-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/chanson/emile-proulx-cloutier.html"}]},{"@id":"http://kg.footlight.io/resource/spec-qc-ca_artiste_emile-proulx-cloutier","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@value":"Émile Proulx-Cloutier"}],"http://schema.org/description":[{"@language":"fr","@value":"Les chansons d’Émile Proulx-Cloutier se vivent comme des contes, des petits films, des vols express au cœur de personnages plus grands que nature (des enfants, des vieux, des femmes, des fous). À travers eux, l’acteur-chanteur-raconteur se révèle comme jamais auparavant. Porté par ses mots et le groove de musiciens multi-instrumentistes, on voyage du slam à la berceuse, de la tragédie à la franche rigolade. Amoureux de la langue, il rêve de mots qui seraient comme des diamants dans la bouche: ça fait mal mais ça brille! Attention: fragile et explosif. Après avoir raflé sept prix au Festival en chanson de Petite-Vallée en 2011 (Prix du public, Prix Pauline-Julien, Prix ROSEQ, Prix UDA, Prix Artisti, Prix Chanteaufête, Prix Francouvertes), Émile a présenté plus de 40 spectacles partout à travers le Québec, pour la plupart présentés à guichets fermés."}],"http://schema.org/sameAs":[{"@value":"http://kg.artsdata.ca/resource/K12-527"}],"http://schema.org/image":[{"@value":"https://spec.qc.ca/uploads/artistes/emile-proulx-cloutier-4.jpg"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-emile-proulx-cloutier#Offer_0","http://schema.org/price":[{"@value":"47"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E053C\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_chanson-emile-proulx-cloutier#Offer_1","http://schema.org/price":[{"@value":"43"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-les-exseparables","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Les EXséparables"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/humour/les-exseparables.jpg"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/les-exseparables.html"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/les-exseparables.html#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/665dbf55-afad-4745-a6ef-04e0b555222a"}],"http://schema.org/keywords":[{"@value":"humour"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/xVneDTZMdpg?si=YG3GIL1SE5rIugSk"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/c0d618dc-edbe-4c1f-a0b0-6611de8368f8"}],"http://schema.org/description":[{"@language":"fr","@value":"Les EXséparables, Patricia Paquin et Mathieu Gratton — les ex les plus drôles et les parents les plus fiers du Québec — reviennent sur scène dans une toute nouvelle mouture du spectacle Les Ex, cette fois accompagnés de leur fils Benjamin Gratton et Ghyslain Dufresne (l’autre EX de Mathieu Gratton! Allo le retour de CRAMPE EN MASSE!). Formant un trio intergénérationnel aussi touchant qu’hilarant, Patricia, Mathieu, Benjamin proposent une relecture actualisée de la dynamique familiale, où les rôles de parents, d’ex, d’ado devenu adulte — et d’influenceur bienveillant — s’entrelacent dans un tourbillon de gags, de vérités crues et de moments d’émotion. Benjamin ne manquera pas de rappeler à ses parents qu’ils ont bel et bien été un couple, tout en soulevant avec finesse les enjeux de famille et de cohabitation moderne. Quant à Ghyslain Dufresne, il ajoute son grain de sel avec ses chansons décalées, ses mimiques absurdes et son humour mordant — juste assez pour déstabiliser le duo… et faire crouler la salle de rire. Dans ce spectacle à la fois tendre et corrosif, Patricia et Mathieu, toujours aussi incisifs l’un envers l’autre, offrent aussi une véritable thérapie de couple en direct. Ils y abordent l’actualité des 25 dernières années, la conciliation travail-famille, l’ouverture à la communauté LGBTQ+, la haine en ligne et la réalité de devenir parent à 46 ans! Écrit par Mathieu Gratton, supervisé par la contrôlante de service Patricia Paquin et mis en scène par Joël Legendre, Les EXséparables ravit les spectateurs de partout au Québec grâce à son humour intelligent, ses monologues percutants, ses chansons bien senties et sa complicité contagieuse. Une soirée riche en rires et en réflexions…"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-02T20:00:00-04:00"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-les-exseparables#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-les-exseparables#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/les-exseparables.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:34:10.258-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/humour/les-exseparables.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-les-exseparables#Offer_0","http://schema.org/price":[{"@value":"44"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":""}],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E0531\u0026CodeSiteClient=00000V"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_humour-les-exseparables#Offer_1","http://schema.org/price":[{"@value":"40"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-david-corriveau","@type":["http://schema.org/Event"],"http://schema.org/description":[{"@language":"fr","@value":"Impossible de ne pas être fasciné par David Corriveau, ce chanteur à la voix exceptionnelle. Dans son spectacle intitulé 100 contrefaçons, le fil conducteur est tissé de chansons et d’imitations. Si vous fermez les yeux, vous croyez entendre Céline Dion, Ginette Reno ou Lara Fabian. En les ouvrant, vous voyez que ces voix appartiennent à un seul homme, c’est vraiment impressionnant. Gradué du Conservatoire d’art dramatique de Québec, David Corriveau a joué dans la comédie musicale Footloose et fait une apparition remarquée au Gala ComediHa de Marc Dupré en août 2023. Finaliste à l’émission La France a un incroyable talent en décembre 2023, il a le vent dans les voiles et c’est dans ce climat d’effervescence qu’il prépare son spectacle, ça promet! Tout jeune, David Corriveau sait qu’il veut toucher les gens, et on peut dire qu’il a réussi. Avant d’aborder la scène en 2023, il a déjà une grande notoriété virtuelle : il compte 200 000 vues sur You Tube, son TikTok a atteint 18 000 abonnés et plus de 100 000 J’aime! * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/david-corriveau.html"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K2-1664"}],"http://schema.org/name":[{"@language":"fr","@value":"David Corriveau"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/david-corriveau.html#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/additionalType":[{"@id":"http://kg.artsdata.ca/resource/PerformingArtsEvent"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/david-corriveau.jpg"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/R5blK6ye9OA?si=V3nFWqKYQeJFENag"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-14T20:00:00-04:00"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-david-corriveau#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-david-corriveau#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/david-corriveau.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:26:39.515-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/david-corriveau.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-david-corriveau#Offer_0","http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeSalleSpectacle=0E01MM\u0026CodeSiteClient=00000V"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"35"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-david-corriveau#Offer_1","http://schema.org/price":[{"@value":"31"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-david-corriveau-14-03-2026","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K2-1664"}],"http://schema.org/name":[{"@language":"fr","@value":"David Corriveau"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/dca5d22a-9dd4-4d4a-87ed-e2119d55aab5"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.artsdata.ca/resource/K5-4"}],"http://schema.org/keywords":[{"@value":"auditorium-de-la-polyvalente-nicolas-gatineau"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-14T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/david-corriveau-14-03-2026.html#WebPage"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/hRn1k_BxVqU?si=qkOmXM3J91PfKMqH"}],"http://schema.org/image":[{"@value":"https://www.maisondelaculture.ca/salle-odyssee/images/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/david-corriveau.jpg"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/da467f66-a079-4d7a-bde1-08e01b228406"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/david-corriveau-14-03-2026.html"}],"http://schema.org/description":[{"@language":"fr","@value":"Impossible de ne pas être fasciné par David Corriveau, ce chanteur à la voix exceptionnelle. Dans son spectacle intitulé 100 contrefaçons, le fil conducteur est tissé de chansons et d’imitations. Si vous fermez les yeux, vous croyez entendre Céline Dion, Ginette Reno ou Lara Fabian. En les ouvrant, vous voyez que ces voix appartiennent à un seul homme, c’est vraiment impressionnant. Gradué du Conservatoire d’art dramatique de Québec, David Corriveau a joué dans la comédie musicale Footloose et fait une apparition remarquée au Gala ComediHa de Marc Dupré en août 2023. Finaliste à l’émission La France a un incroyable talent en décembre 2023, il a le vent dans les voiles et c’est dans ce climat d’effervescence qu’il prépare son spectacle, ça promet! Tout jeune, David Corriveau sait qu’il veut toucher les gens, et on peut dire qu’il a réussi. Avant d’aborder la scène en 2023, il a déjà une grande notoriété virtuelle : il compte 200 000 vues sur You Tube, son TikTok a atteint 18 000 abonnés et plus de 100 000 J’aime! * Spectacle présenté à l'auditorium de l'école polyvalente Nicolas-Gatineau, 360, boulevard La Vérendrye Est, Gatineau, QC, J8P 6K7"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-david-corriveau-14-03-2026#Offer_0"},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-david-corriveau-14-03-2026#Offer_1"}]},{"@id":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/david-corriveau-14-03-2026.html#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T16:29:43.672-05:00"}],"http://schema.org/url":[{"@value":"https://www.maisondelaculture.ca/Salle-Odyssee/spectacles/auditorium-de-la-polyvalente-nicolas-gatineau/david-corriveau-14-03-2026.html"}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-david-corriveau-14-03-2026#Offer_0","http://schema.org/price":[{"@value":"35"}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://reseau.ovation.ca/pesite.asp?CodeRepresentation=0E04YB\u0026CodeSiteClient=00000V"}],"http://schema.org/name":[{"@language":"fr","@value":""}]},{"@id":"http://kg.footlight.io/resource/maisondelaculture-ca_auditorium-de-la-polyvalente-nicolas-gatineau-david-corriveau-14-03-2026#Offer_1","http://schema.org/price":[{"@value":"31"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"carte Odyssée"}]}]
\ No newline at end of file
diff --git a/data/migration_baseline/one.jsonld b/data/migration_baseline/one.jsonld
new file mode 100644
index 00000000..0637a088
--- /dev/null
+++ b/data/migration_baseline/one.jsonld
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/data/migration_baseline/ptitbonheur_org.jsonld b/data/migration_baseline/ptitbonheur_org.jsonld
new file mode 100644
index 00000000..513ebc60
--- /dev/null
+++ b/data/migration_baseline/ptitbonheur_org.jsonld
@@ -0,0 +1 @@
+[{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_les-hay-babies-tintamarre","@type":["http://schema.org/Event"],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5ecd32c6-6264-437f-a3bb-751c2b85dea6"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-29T20:00:00-04:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/description":[{"@language":"fr","@value":"Les Hay Babies seront en concert sur la scène du P’tit Bonheur de Saint-Camille, le 29 mai 2026, offrant une performance haute en couleur aux amateurs de folk indie. Les Hay Babies, le trio folk-acadien acclamé, apportent leur charme unique et leur énergie vibrante sur scène.\nLes Hay Babies, c’est d’abord des amies : le fun passe avant le travail, pis le travail se fait dans le fun. Acadiennes d’origine néo-brunswickoise, Julie Aubé, Katrine Noël et Vivianne Roy avancent leur folk-rock pluriel et pétillant depuis plus d’une décennie, renouvelant leur proposition musicale à chaque parution. Découvertes via l’EP indie-folk / country Folio en 2012, elles rappliquent deux ans plus tard avec Mon homesickheart en 2014, un premier album aux sonorités folk-rock contemporaines. Le rock 60s / 70s et les ballades 80s de La 4ième dimension (version longue) suivent en 2016, puis la pop / disco 70s de Boîte aux lettres en 2020. Un quatrième album, Tintamarre, celui-ci inspiré du roots rock des jukeboxes louisianais, est paru en octobre 2024. Changements de cap toujours, donc – ce qui fait autant la marque du groupe que son entrain indéfectible et son intérêt pour l’actualisation des sons passés.\n\nPlus d'information"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/7f3ecdba-de65-4041-8871-817fe5b68c82"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Les Hay Babies - Tintamarre"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K2-5781"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-62"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/les-hay-babies-tintamarre/#WebPage"}],"http://schema.org/recordedIn":[{"@value":"[\"https://www.youtube.com/embed/O0XZqMgFAW8?si=gy_UGUYIimsTMudx\", \"https://www.youtube.com/embed/vBCBw8xesAE?si=gy_UGUYIimsTMudx\"]"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-29T20:00:00-04:00"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/les-hay-babies-tintamarre/"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/RNT/26/05/29/001/rnt260529001-640x360-fr-m-68489852.png"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_les-hay-babies-tintamarre#Offer_0"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_les-hay-babies-tintamarre#Offer_1"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_les-hay-babies-tintamarre#Offer_2"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_les-hay-babies-tintamarre#Offer_3"}]},{"@id":"http://kg.footlight.io/resource/5ecd32c6-6264-437f-a3bb-751c2b85dea6","@type":["http://schema.org/Place"],"http://schema.org/name":[{"@language":"fr","@value":"Le P'tit Bonheur de Saint-Camille"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"restaurant, food, point_of_interest, establishment at 162 Rue Miquelon, Saint-Camille, QC J0A 1G0, Canada"}],"http://schema.org/address":[{"@id":"http://kg.footlight.io/resource/5ecd32c6-6264-437f-a3bb-751c2b85dea6#PostalAddress"}],"http://schema.org/longitude":[{"@value":"-71.7036972"}],"http://schema.org/latitude":[{"@value":"45.6834805"}],"http://schema.org/sameAs":[{"@value":"https://maps.google.com/?cid=14081812149791487432"}]},{"@id":"http://kg.footlight.io/resource/7f3ecdba-de65-4041-8871-817fe5b68c82","@type":["http://schema.org/EventType"],"http://www.w3.org/2004/02/skos/core#closeMatch":[{"@id":"http://kg.artsdata.ca/resource/PerformingArtsEvent"}],"http://www.w3.org/2004/02/skos/core#prefLabel":[{"@language":"fr","@value":"Performance"}]},{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/les-hay-babies-tintamarre/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T12:11:08.090-05:00"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/les-hay-babies-tintamarre/"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_les-hay-babies-tintamarre#Offer_0","http://schema.org/url":[{"@value":"https://lepointdevente.com/tickets/rnt260529001"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"45.01"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_les-hay-babies-tintamarre#Offer_1","http://schema.org/price":[{"@value":"23.01"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Souper avant le spectacle - Ajoutez un repas à votre expérience! Places limitées. Les repas sont servis entre 18h00 et 19h15."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_les-hay-babies-tintamarre#Offer_2","http://schema.org/price":[{"@value":"40.61"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif membre - Les membres obtiennent 10% de rabais sur le prix régulier des billets."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_les-hay-babies-tintamarre#Offer_3","http://schema.org/price":[{"@value":"34.01"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif étudiant - 30 ans et moins. Sur présentation d'une preuve le soir du spectacle."}]},{"@id":"http://kg.footlight.io/resource/5ecd32c6-6264-437f-a3bb-751c2b85dea6#PostalAddress","@type":["http://schema.org/PostalAddress"],"http://schema.org/streetAddress":[{"@value":"162 Rue Miquelon"}],"http://schema.org/postalCode":[{"@value":"J0A 1G0"}],"http://schema.org/addressRegion":[{"@value":"QC"}],"http://schema.org/addressCountry":[{"@value":"CA"}],"http://schema.org/addressLocality":[{"@value":"Saint-Camille"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_que-ta-lumiere-maude-audet","@type":["http://schema.org/Event"],"http://schema.org/description":[{"@language":"fr","@value":"Pour ce nouveau spectacle, Maude Audet nous propose un moment lumineux et vibrant, porté par sa voix chaleureuse et son univers poétique. Inspiré de son album à paraître en avril 2026, elle livre ses chansons indie-folk avec la sincérité qu’on lui connaît, en phase avec l’instant présent. Entourée de ses musiciens, la chanteuse et son groupe offrent des envolées mélodiques planantes et rythmées qui cadencent la soirée. Dans cet espace, chaque geste a sa place, les notes brillent et l’art vivant est à l’honneur. Sincère et libre, Maude s’investit auprès du public à travers ces nouvelles chansons qui teintent le décor et la mise en scène sensible qu’elle signe. L’optimisme et la fragilité se côtoient, le tout créant une rencontre humaine et profonde.\n\nPlus d'information"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/RNT/26/11/20/001/rnt261120001-640x360-fr-m-6961355a.png"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/414d2676-ec62-479e-bd62-1303cbb037cc"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-20T20:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/que-ta-lumiere-maude-audet/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-11-20T20:00:00-05:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/7f3ecdba-de65-4041-8871-817fe5b68c82"}],"http://schema.org/name":[{"@language":"fr","@value":"Que ta lumière - Maude Audet"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/que-ta-lumiere-maude-audet/"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5ecd32c6-6264-437f-a3bb-751c2b85dea6"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_que-ta-lumiere-maude-audet#Offer_0"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_que-ta-lumiere-maude-audet#Offer_1"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_que-ta-lumiere-maude-audet#Offer_2"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_que-ta-lumiere-maude-audet#Offer_3"}]},{"@id":"http://kg.footlight.io/resource/414d2676-ec62-479e-bd62-1303cbb037cc","@type":["http://schema.org/Organization"],"http://schema.org/name":[{"@language":"fr","@value":"Le P'tit Bonheur de Saint-Camille"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Organization"}],"http://schema.org/url":[{"@value":"https://www.ptitbonheur.org"}],"http://schema.org/sameAs":[{"@value":"http://kg.artsdata.ca/resource/K16-62"}]},{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/que-ta-lumiere-maude-audet/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T12:10:38.869-05:00"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/que-ta-lumiere-maude-audet/"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_que-ta-lumiere-maude-audet#Offer_0","http://schema.org/price":[{"@value":"35.44"}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://lepointdevente.com/tickets/rnt261120001"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_que-ta-lumiere-maude-audet#Offer_1","http://schema.org/price":[{"@value":"22.5"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Souper avant le spectacle - Ajoutez un repas à votre expérience! Places limitées. Les repas sont servis entre 18h00 et 19h15."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_que-ta-lumiere-maude-audet#Offer_2","http://schema.org/price":[{"@value":"31.92"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif membre - Les membres obtiennent 10% de rabais sur le prix régulier des billets."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_que-ta-lumiere-maude-audet#Offer_3","http://schema.org/price":[{"@value":"26.64"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif étudiant - 30 ans et moins. Sur présentation d'une preuve le soir du spectacle."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_vingt-mille-matins-daniel-boucher","@type":["http://schema.org/Event"],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-02-14T19:00:00-05:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5ecd32c6-6264-437f-a3bb-751c2b85dea6"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/vingt-mille-matins-daniel-boucher/#WebPage"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/vingt-mille-matins-daniel-boucher/"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-62"}],"http://schema.org/name":[{"@language":"fr","@value":"Vingt mille matins - Daniel Boucher"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/f2985dd4-bdd9-4219-bb7f-e4763a066147"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-02-14T19:00:00-05:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/7f3ecdba-de65-4041-8871-817fe5b68c82"}],"http://schema.org/description":[{"@language":"fr","@value":"Pour célébrer à la fois ses vingt-cinq ans de carrière et les vingt-cinq ans de son mythique premier album, Daniel Boucher présente un tout nouveau spectacle pour 2025 : Vingt mille matins.\nCe spectacle, offert en formule solo, offre un équilibre parfait entre le passé et l’avenir, mettant en avant les plus grandes chansons de son répertoire, qui compte maintenant neuf albums.\nCe spectacle promet une explosion de couleurs vives, d’énergie brute, de chaleur humaine et d’amour du Québec.\nUn rendez-vous incontournable en 2025.\nCélébrez avec Daniel Boucher ses vingt-cinq ans de carrière.\nCélébrez ses Vingt mille matins.\n\n\nPlus d'information"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OnlineEventAttendanceMode"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_vingt-mille-matins-daniel-boucher#Offer_0"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_vingt-mille-matins-daniel-boucher#Offer_1"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_vingt-mille-matins-daniel-boucher#Offer_2"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_vingt-mille-matins-daniel-boucher#Offer_3"}]},{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/vingt-mille-matins-daniel-boucher/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-02-14T12:10:50.216-05:00"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/vingt-mille-matins-daniel-boucher/"}]},{"@id":"http://kg.footlight.io/resource/f2985dd4-bdd9-4219-bb7f-e4763a066147","@type":["http://schema.org/Person"],"http://schema.org/sameAs":[{"@value":"http://www.wikidata.org/entity/Q3013791"}],"http://schema.org/name":[{"@language":"fr","@value":"Daniel Boucher"}],"http://schema.org/hasOccupation":[{"@language":"fr","@value":"Artiste musical"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Artiste musical"}],"http://schema.org/url":[{"@value":"https://danielboucher.quebec/"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_vingt-mille-matins-daniel-boucher#Offer_0","http://schema.org/price":[{"@value":"49"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_vingt-mille-matins-daniel-boucher#Offer_1","http://schema.org/price":[{"@value":"21.75"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Souper avant le spectacle - Ajoutez un repas à votre expérience! Places limitées. Les repas sont servis entre 18h00 et 19h15."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_vingt-mille-matins-daniel-boucher#Offer_2","http://schema.org/price":[{"@value":"44.1"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif membre - Les membres obtiennent 10% de rabais sur le prix régulier des billets."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_vingt-mille-matins-daniel-boucher#Offer_3","http://schema.org/price":[{"@value":"36.75"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif étudiant - 30 ans et moins. Sur présentation d'une preuve le soir du spectacle."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_ingrid-st-pierre","@type":["http://schema.org/Event"],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-12-12T20:00:00-05:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/7f3ecdba-de65-4041-8871-817fe5b68c82"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/RNT/25/12/12/001/rnt251212001-640x360-fr-m-6801df1c.png"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-62"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K13-87"}],"http://schema.org/name":[{"@language":"fr","@value":"Ingrid St-Pierre"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/ingrid-st-pierre/#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-12-12T20:00:00-05:00"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/ingrid-st-pierre/"}],"http://schema.org/location":[{"@id":"https://destinationsaintcamille.ca#Place"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/description":[{"@language":"fr","@value":"Des chansons délicatement déshabillées, détricotées, qui se déploient dans leurs apparats originels. Vives, chargées d’histoires, et toutes pleines d’immenses. Dans ce spectacle solo, Ingrid St-Pierre est portée par l’envie d’exister sur scène librement, en laissant toute la place au cinéma des mots, aux silences, aux détails, aux histoires, aux paysages.\n\nPlus d'information"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_ingrid-st-pierre#Offer_0"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_ingrid-st-pierre#Offer_1"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_ingrid-st-pierre#Offer_2"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_ingrid-st-pierre#Offer_3"}]},{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/ingrid-st-pierre/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-12-11T12:10:14.363-05:00"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/ingrid-st-pierre/"}]},{"@id":"https://destinationsaintcamille.ca#Place","@type":["http://schema.org/Place"],"http://schema.org/address":[{"@id":"https://destinationsaintcamille.ca#Place-PostalAddress"}],"http://schema.org/name":[{"@language":"fr","@value":"Centre Le Camillois"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_ingrid-st-pierre#Offer_0","http://schema.org/price":[{"@value":"48"}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://lepointdevente.com/tickets/rnt251212001"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_ingrid-st-pierre#Offer_1","http://schema.org/price":[{"@value":"22.5"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Souper avant le spectacle - Ajoutez un repas à votre expérience! Places limitées. Les repas sont servis entre 18h00 et 19h15."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_ingrid-st-pierre#Offer_2","http://schema.org/price":[{"@value":"43.2"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif membre - Les membres obtiennent 10% de rabais sur le prix régulier des billets."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_ingrid-st-pierre#Offer_3","http://schema.org/price":[{"@value":"36"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif étudiant - 30 ans et moins. Sur présentation d'une preuve le soir du spectacle."}]},{"@id":"https://destinationsaintcamille.ca#Place-PostalAddress","@type":["http://schema.org/PostalAddress"],"http://schema.org/addressLocality":[{"@language":"fr","@value":"Saint-Camille"}],"http://schema.org/postalCode":[{"@value":"J0A 1G0"}],"http://schema.org/addressCountry":[{"@language":"fr","@value":"CA"}],"http://schema.org/streetAddress":[{"@language":"fr","@value":"157 Miquelon"}],"http://schema.org/addressRegion":[{"@language":"fr","@value":"QC"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_alain-francois-lamontagne","@type":["http://schema.org/Event"],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-24T20:00:00-05:00"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/7f3ecdba-de65-4041-8871-817fe5b68c82"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-62"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/alain-francois-lamontagne/"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-24T20:00:00-05:00"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/RNT/26/01/24/001/rnt260124001-640x360-fr-m-6801d95d.png"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/alain-francois-lamontagne/#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/name":[{"@language":"fr","@value":"Alain-François Lamontagne"}],"http://schema.org/description":[{"@language":"fr","@value":"Reconnu pour sa musique festive, son humour et son violon endiablé et fidèle au style, au son et au joyeux délire qui le caractérise, l’auteur-compositeur-interprète Alain-François présente son spectacle « La grande fête ». Un spectacle qui fait suite au grand succès de ses 10 albums en carrière. L’auteur-compositeur violoniste propose des classiques en plus de proposer des pièces inédites qui ont tout pour entrer dans la tradition. Festif, ce spectacle est désormais un incontournable quand vient le temps de faire la fête. Accompagné de 3 musiciens, Alain-François présente un spectacle pour tous les âges, endiablé, percussif et haut en couleur qui joue autant sur le rire que sur les cordes sensibles.\nUn violon déchaîné, des percussions endiablées, un spectacle décapant!\nUn spectacle unique, un prêt-à-fêter!\n\nPlus d'information"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5ecd32c6-6264-437f-a3bb-751c2b85dea6"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_alain-francois-lamontagne#Offer_0"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_alain-francois-lamontagne#Offer_1"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_alain-francois-lamontagne#Offer_2"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_alain-francois-lamontagne#Offer_3"}]},{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/alain-francois-lamontagne/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-01-22T12:11:34.709-05:00"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/alain-francois-lamontagne/"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_alain-francois-lamontagne#Offer_0","http://schema.org/price":[{"@value":"44"}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://lepointdevente.com/tickets/rnt260124001"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_alain-francois-lamontagne#Offer_1","http://schema.org/price":[{"@value":"22.5"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Souper avant le spectacle - Ajoutez un repas à votre expérience! Places limitées. Les repas sont servis entre 18h00 et 19h15."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_alain-francois-lamontagne#Offer_2","http://schema.org/price":[{"@value":"39.6"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif membre - Les membres obtiennent 10% de rabais sur le prix régulier des billets."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_alain-francois-lamontagne#Offer_3","http://schema.org/price":[{"@value":"33"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif étudiant - 30 ans et moins. Sur présentation d'une preuve le soir du spectacle."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_jorane","@type":["http://schema.org/Event"],"http://schema.org/description":[{"@language":"fr","@value":"Du grand vaisseau HEMENETSET, projet créatif phare de Jorane, se détache une petite navette exploratrice nommée : Hemenetset duo. Accompagnée de Martin Lizotte au piano, Jorane part à la rencontre du public avec toute sa force et sa fragilité qui se côtoient dans une invitation à réveiller notre liberté intérieure. Hemenetset : un appel à l’ultime expression de soi.\nRécipiendaire du prestigieux prix André Gagnon de la SPACQ (2015) et décorée d’une mention honorable Elles de la musique de la SOCAN (2023), Jorane s’élève sur une carrière musicale de plus de 20 ans, jalonnée de réalisations ambitieuses moult fois récompensées par l’industrie. Son plus récent album, Hemenetset, lui a valu des nominations aux Junos, à l’Adisq et aux GAMIQ. Avec une douzaine d’albums à son actif et des tournées l’ayant menée aux quatre coins du globe, Jorane se présente comme une exploratrice sonore et une créatrice d’une rarissime liberté, tantôt aux commandes de ses propres projets, tantôt au service de l’art sous diverses formes. Elle signe les trames sonores de nombreuses œuvres théâtrales, cinématographiques, télévisuelles et documentaires, notamment pour les pièces le Journal d’Anne Frank (prix Gascon-Roux) et Albertine en cinq temps, de même que pour le film Un dimanche à Kigali, qui lui vaudra un Jutra en 2007. Parmi ses illustres collaborations, notons son travail avec l’OSQ, l’Orchestre métropolitain, l’ensemble I Musici, Margie Gillis, Michael Brook et Sara Maclachlan. Au cours de sa carrière, Jorane partagera également la scène avec Bobby McFerrin, Daniel Lanois et Anne Sylvestre pour ne nommer qu’eux. Porte-parole du Phare, Enfants et Familles et citoyenne engagée, Jorane s’implique aussi aux côtés de diverses organisations œuvrant auprès de femmes en situations de vulnérabilité.\n\nPlus d'information"}],"http://schema.org/location":[{"@id":"https://destinationsaintcamille.ca#Place"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-14T20:00:00-05:00"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/jorane/"}],"http://schema.org/name":[{"@language":"fr","@value":"Jorane"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/deca6865-c662-415b-92cd-9cd51640b0d5"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-62"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/RNT/26/02/14/001/rnt260214001-640x360-fr-m-6801d5e0.png"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/7f3ecdba-de65-4041-8871-817fe5b68c82"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/TeVyDMCeC5E?si=gy_UGUYIimsTMudx"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-14T20:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/jorane/#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_jorane#Offer_0"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_jorane#Offer_1"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_jorane#Offer_2"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_jorane#Offer_3"}]},{"@id":"http://kg.footlight.io/resource/deca6865-c662-415b-92cd-9cd51640b0d5","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Jorane"}],"http://schema.org/hasOccupation":[{"@language":"fr","@value":"Musicienne"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Musicienne"}],"http://schema.org/url":[{"@value":"https://jorane.com/"}],"http://schema.org/sameAs":[{"@value":"http://www.wikidata.org/entity/Q468714"}]},{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/jorane/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T12:10:52.119-05:00"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/jorane/"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_jorane#Offer_0","http://schema.org/url":[{"@value":"https://lepointdevente.com/tickets/rnt260214001"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"47"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_jorane#Offer_1","http://schema.org/price":[{"@value":"22.5"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Souper avant le spectacle - Ajoutez un repas à votre expérience! Places limitées. Les repas sont servis entre 18h00 et 19h15."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_jorane#Offer_2","http://schema.org/price":[{"@value":"42.3"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif membre - Les membres obtiennent 10% de rabais sur le prix régulier des billets."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_jorane#Offer_3","http://schema.org/price":[{"@value":"35.25"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif étudiant - 30 ans et moins. Sur présentation d'une preuve le soir du spectacle."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_seul-a-seuls-jacques-michel","@type":["http://schema.org/Event"],"http://schema.org/description":[{"@language":"fr","@value":"Un moment privilégié avec Jacques Michel, seul sur scène avec sa guitare et son harmonica, en toute simplicité et en toute complicité. Une occasion de célébrer plus de 60 ans de vie artistique de l’un des plus grands auteurs-compositeurs-interprètes et musiciens du Québec.\nDans ce spectacle, Jacques Michel nous fera naviguer à travers les grands succès qui ont marqué plusieurs générations et pigera dans son répertoire pour vous surprendre. Il partagera aussi ses réflexions et ses souvenirs qui ont inspiré ses chansons. Et surtout, vous serez charmés par sa voix, sa poésie et son énergie qui ont su résister aux modes et au temps.\nNe manquez pas cette chance unique de voir Jacques Michel sur scène dans une ambiance intime et chaleureuse.\njacquesmichel.comfacebook.com/pasbesoindefrapper\nCrédit photo : André Chevrier\n\nPlus d'information"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-02-14T19:00:00-05:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5ecd32c6-6264-437f-a3bb-751c2b85dea6"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-02-14T19:00:00-05:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/7f3ecdba-de65-4041-8871-817fe5b68c82"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K13-93"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-62"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/seul-a-seuls-jacques-michel/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OnlineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/seul-a-seuls-jacques-michel/"}],"http://schema.org/name":[{"@language":"fr","@value":"Seul à seuls - Jacques Michel"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_seul-a-seuls-jacques-michel#Offer_0"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_seul-a-seuls-jacques-michel#Offer_1"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_seul-a-seuls-jacques-michel#Offer_2"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_seul-a-seuls-jacques-michel#Offer_3"}]},{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/seul-a-seuls-jacques-michel/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-02-14T12:11:13.748-05:00"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/seul-a-seuls-jacques-michel/"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_seul-a-seuls-jacques-michel#Offer_0","http://schema.org/price":[{"@value":"45"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_seul-a-seuls-jacques-michel#Offer_1","http://schema.org/price":[{"@value":"21.75"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Souper avant le spectacle - Ajoutez un repas à votre expérience! Places limitées. Les repas sont servis entre 18h00 et 19h15."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_seul-a-seuls-jacques-michel#Offer_2","http://schema.org/price":[{"@value":"40.5"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif membre - Les membres obtiennent 10% de rabais sur le prix régulier des billets."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_seul-a-seuls-jacques-michel#Offer_3","http://schema.org/price":[{"@value":"33.75"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif étudiant - 30 ans et moins. Sur présentation d'une preuve le soir du spectacle."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_indomptable-virginia-woolf","@type":["http://schema.org/Event"],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/7f3ecdba-de65-4041-8871-817fe5b68c82"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/indomptable-virginia-woolf/"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-10-11T20:00:00-04:00"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-62"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/indomptable-virginia-woolf/#WebPage"}],"http://schema.org/location":[{"@id":"https://destinationsaintcamille.ca#Place"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/description":[{"@language":"fr","@value":"Indomptable Virginia Woolf spectacle-littéraire sur Virginia Woolf, grande autrice emblématique ayant à la fois révolutionné l’art d’écrire et pour la première fois interrogé le rôle des femmes. Un texte de Robert Lalonde qui interprète le tendre mari de Virginia, Bénédicte Décary, sa sœur complice et admirative, Johanne Haberlin grande tragédienne qui incarne Virginia avec panache et une chanteuse-compositrice-multi-instrumentiste sur scène. Le tout mis en scène par Lorraine Pintal. Un texte qui ressemble à Virginia, qui était à la fois à la fois curieuse, délirante et dramatique, drôle, ironique et délicieusement cinglante.» Des sujets très actuels : le couple aimant, la place des femmes, la dictature, l’amitié entre sœurs, l’attirance bisexuelle, la liberté, la difficulté de vivre, l’acceptation des choix de l’autre…\nConsultez l’article de La Tribune sur le spectacle!\n\nPlus d'information"}],"http://schema.org/name":[{"@language":"fr","@value":"Indomptable Virginia Woolf"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/RNT/25/10/11/001/rnt251011001-640x360-fr-m-6801ca49.png"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/recordedIn":[{"@value":"https://www.facebook.com/plugins/video.php?height=314\u0026href=https%3A%2F%2Fwww.facebook.com%2FPtitBonheurdeSaintCamille%2Fvideos%2F1451579659230712%2F\u0026show_text=false\u0026width=560\u0026t=0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-10-11T20:00:00-04:00"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_indomptable-virginia-woolf#Offer_0"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_indomptable-virginia-woolf#Offer_1"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_indomptable-virginia-woolf#Offer_2"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_indomptable-virginia-woolf#Offer_3"}]},{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/indomptable-virginia-woolf/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-10-10T13:11:08.179-04:00"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/indomptable-virginia-woolf/"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_indomptable-virginia-woolf#Offer_0","http://schema.org/price":[{"@value":"48"}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://lepointdevente.com/tickets/rnt251011001"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_indomptable-virginia-woolf#Offer_1","http://schema.org/price":[{"@value":"22.5"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Souper avant le spectacle - Ajoutez un repas à votre expérience! Places limitées. Les repas sont servis entre 18h00 et 19h15."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_indomptable-virginia-woolf#Offer_2","http://schema.org/price":[{"@value":"43.2"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif membre - Les membres obtiennent 10% de rabais sur le prix régulier des billets."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_indomptable-virginia-woolf#Offer_3","http://schema.org/price":[{"@value":"36"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif étudiant - 30 ans et moins. Sur présentation d'une preuve le soir du spectacle."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_encore-encore-gab-bouchard","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K13-68"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/encore-encore-gab-bouchard/"}],"http://schema.org/name":[{"@language":"fr","@value":"Encore Encore - Gab Bouchard"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/RNT/26/09/25/001/rnt260925001-640x360-fr-m-6961333d.png"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-25T20:00:00-04:00"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/414d2676-ec62-479e-bd62-1303cbb037cc"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/encore-encore-gab-bouchard/#WebPage"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-09-25T20:00:00-04:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/7f3ecdba-de65-4041-8871-817fe5b68c82"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"https://destinationsaintcamille.ca#Place"}],"http://schema.org/description":[{"@language":"fr","@value":"Commencer et finir sur un gros « high » et se laisser de la manœuvre pour des pièces plus douces tout au long, sans jamais négliger les sentiments : Gab Bouchard sait comment ça marche, un bon show. Fort du succès de la tournée panquébécoise de Grafignes, Gab Bouchard revient avec un spectacle élaboré autour de son troisième album, un disque un peu plus lumineux que son prédécesseur. On y retrouve la confluence de country-folk, pop-rock et soul qui fait sa marque distinctive – on y découvre aussi des arrangements plus rentre-dedans que sur les enregistrements. Sillonnant les routes du Québec, Gab Bouchard s’en vient vers vous accompagné de ses fidèles musiciens Pierre-Olivier Gagnon (basse), Mathieu Quenneville (claviers), Zachary Boileau (guitare) et Victor Tremblay-Desrosiers (batterie).\n\nPlus d'information"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_encore-encore-gab-bouchard#Offer_0"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_encore-encore-gab-bouchard#Offer_1"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_encore-encore-gab-bouchard#Offer_2"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_encore-encore-gab-bouchard#Offer_3"}]},{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/encore-encore-gab-bouchard/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T12:10:35.850-05:00"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/encore-encore-gab-bouchard/"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_encore-encore-gab-bouchard#Offer_0","http://schema.org/url":[{"@value":"https://lepointdevente.com/tickets/rnt260925001"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"38.37"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_encore-encore-gab-bouchard#Offer_1","http://schema.org/price":[{"@value":"22.5"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Souper avant le spectacle - Ajoutez un repas à votre expérience! Places limitées. Les repas sont servis entre 18h00 et 19h15."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_encore-encore-gab-bouchard#Offer_2","http://schema.org/price":[{"@value":"34.56"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif membre - Les membres obtiennent 10% de rabais sur le prix régulier des billets."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_encore-encore-gab-bouchard#Offer_3","http://schema.org/price":[{"@value":"28.84"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif étudiant - 30 ans et moins. Sur présentation d'une preuve le soir du spectacle."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_nadine-walsh","@type":["http://schema.org/Event"],"http://schema.org/description":[{"@language":"fr","@value":"Des contes me hantent. Ces histoires débridées sont peuplées de chevaux, de femmes, d’hommes et de créatures. Je les monte à cru, debout face au vent, me cache dans leur crinière et m’abreuve de leur sueur.\nTantôt indomptable sortant de la mer ou fidèle allié qu’il faut sacrifier, l’allégorie du cheval parle de notre rapport à la nature, à nos désirs, à la domination et à nos croyances.\n\nPlus d'information"}],"http://schema.org/name":[{"@language":"fr","@value":"Nadine Walsh"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K2-619"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/nadine-walsh/#WebPage"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-62"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/nadine-walsh/"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/RNT/25/10/26/001/rnt251026001-640x360-fr-m-6801e0e5.png"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/7f3ecdba-de65-4041-8871-817fe5b68c82"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-10-26T15:00:00-04:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5ecd32c6-6264-437f-a3bb-751c2b85dea6"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/A5S7ZL6ztHY?si=gy_UGUYIimsTMudx"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-10-26T15:00:00-04:00"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_nadine-walsh#Offer_0"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_nadine-walsh#Offer_1"}]},{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/nadine-walsh/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-10-24T13:10:46.659-04:00"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/nadine-walsh/"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_nadine-walsh#Offer_0","http://schema.org/price":[{"@value":"25"}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://lepointdevente.com/tickets/rnt251026001"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale - Âge recommandé pour ce spectacle : 12 ans et plus"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_nadine-walsh#Offer_1","http://schema.org/price":[{"@value":"22.5"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif membre - Les membres obtiennent 10% de rabais sur le prix régulier des billets."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_emile-bourgault","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/RNT/25/11/15/001/rnt251115001-640x360-fr-m-6801dce4.png"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-62"}],"http://schema.org/name":[{"@language":"fr","@value":"Émile Bourgault"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/emile-bourgault/"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K2-5818"}],"http://schema.org/recordedIn":[{"@value":"[\"https://www.youtube.com/embed/o2b6tn6KNms?si=DpxDwdQL-jGUNDeF\", \"https://www.youtube.com/embed/XRwGIW5an8o?si=DpxDwdQL-jGUNDeF\"]"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-15T20:00:00-05:00"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5ecd32c6-6264-437f-a3bb-751c2b85dea6"}],"http://schema.org/description":[{"@language":"fr","@value":"Après quelques années d’écriture, des collaborations de marque et plusieurs expériences de scène enrichissantes, Émile Bourgault est fin prêt à se dévoiler! C’est avec une musique pop-alternative aux teintes folk et rock que l’auteur-compositeur-interprète nous présente son premier album : Tant mieux. Entouré de ses fidèles complices, il lance son premier long-jeu avec un spectacle rempli de surprises, voguant entre moments touchants, ambiance festive et amitié sincère.\nTourbillonnant et sensible, canalisé, effleurant la scène avec un émouvant abandon, Émile Bourgault est un moderne et un classique à la fois; il est bien de son époque, mais il sait d’où il vient et s’installe à sa place dans une lignée qui va de Ferland à Philippe B.\nAu piano, à la guitare ou encore les mains vides, en solo, duo, ou flanqué de son band, le jeune auteur-compositeur-interprète oscille entre chanson à texte et vers d’oreille pop, toujours avec un souci du mot juste et un sens du spectacle aiguisé.\nSon premier album, Tant mieux, paraît au printemps 2024 sous une réalisation de Félix Dyotte (Pierre Lapointe, Évelyne Brochu). En plus de présenter Tant mieux sur les scènes du Québec, Émile s’applique déjà à de nouvelles chansons, ainsi qu’à vivre la vie qui le mènera vers tous les albums à suivre. Il n’a pas dit son dernier mot.\n—\nLa promotion 100% garanti, c’est une promesse alléchante : plaisirs et bons moments seront au rendez-vous ou votre argent sera remis*. Garanti!\nLa mention 100% garanti établit le spectacle choisi comme un incontournable. Le diffuseur est un expert en sélection de spectacles : son talent et son flair sont indéniables. Faites-lui confiance et osez le risque avec lui! La promotion 100% garanti, c’est donc 0% de risque pour vous!\n* Le remboursement du billet doit se faire avant l’entracte ou dans les 35 premières minutes du spectacle.\n\nPlus d'information"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/emile-bourgault/#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/7f3ecdba-de65-4041-8871-817fe5b68c82"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-15T20:00:00-05:00"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_emile-bourgault#Offer_0"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_emile-bourgault#Offer_1"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_emile-bourgault#Offer_2"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_emile-bourgault#Offer_3"}]},{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/emile-bourgault/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-13T12:10:37.669-05:00"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/emile-bourgault/"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_emile-bourgault#Offer_0","http://schema.org/price":[{"@value":"38"}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://lepointdevente.com/tickets/rnt251115001"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_emile-bourgault#Offer_1","http://schema.org/price":[{"@value":"22.5"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Souper avant le spectacle - Ajoutez un repas à votre expérience! Places limitées. Les repas sont servis entre 18h00 et 19h15."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_emile-bourgault#Offer_2","http://schema.org/price":[{"@value":"34.2"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif membre - Les membres obtiennent 10% de rabais sur le prix régulier des billets."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_emile-bourgault#Offer_3","http://schema.org/price":[{"@value":"28.5"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif étudiant - 30 ans et moins. Sur présentation d'une preuve le soir du spectacle."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_funambule-cafe","@type":["http://schema.org/Event"],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/RNT/26/04/25/001/rnt260425001-640x360-fr-m-6801d34c.png"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5ecd32c6-6264-437f-a3bb-751c2b85dea6"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/funambule-cafe/"}],"http://schema.org/name":[{"@language":"fr","@value":"Funambule Café"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-25T20:00:00-04:00"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-62"},{"@id":"http://kg.artsdata.ca/resource/K3-181"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/funambule-cafe/#WebPage"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-25T20:00:00-04:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/7f3ecdba-de65-4041-8871-817fe5b68c82"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/description":[{"@language":"fr","@value":"Funambule Café est une unité de creation au service de l’univers poétique du poète et auteur-compositeur estrien Donald Poisson. Ensemble ils proposent un rendez-vous musical multi genres qui touche au rock, au jazz, au progressif tout comme à la chanson française. Avec Un aller pour la lune ( 4ᵉ album), ils nous invitent à visiter 10 tableaux basés sur divers états humains. Les grands thèmes humains de l’amour, la mort, la renaissance, le désir, l’échec, et de l’espoir s’y retrouvent entremêlés, comme nos vies.\nDonald Poisson est actif depuis les années 90 tant comme artiste solo que comme membre de Funambule Café, il collabore aussi à l’ écriture et la composition pour d’ autres artistes de la scène Québécoise.\nFunambule Café est un collectif de musiciens professionnels aguerris ayant joué séparément avec une multitude d’artistes d’ici comme d’ailleurs. Ils allient solidité, expérience, maitrise etprécision .\n\nPlus d'information"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_funambule-cafe#Offer_0"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_funambule-cafe#Offer_1"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_funambule-cafe#Offer_2"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_funambule-cafe#Offer_3"}]},{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/funambule-cafe/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T12:10:42.021-05:00"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/funambule-cafe/"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_funambule-cafe#Offer_0","http://schema.org/price":[{"@value":"28"}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://lepointdevente.com/tickets/rnt260425001"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_funambule-cafe#Offer_1","http://schema.org/price":[{"@value":"22.5"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Souper avant le spectacle - Ajoutez un repas à votre expérience! Places limitées. Les repas sont servis entre 18h00 et 19h15."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_funambule-cafe#Offer_2","http://schema.org/price":[{"@value":"25.2"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif membre - Les membres obtiennent 10% de rabais sur le prix régulier des billets."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_funambule-cafe#Offer_3","http://schema.org/price":[{"@value":"21"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif étudiant - 30 ans et moins. Sur présentation d'une preuve le soir du spectacle."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_cosse-tu-veux-mon-tit-bonhomme-lalonde-kerouac","@type":["http://schema.org/Event"],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5ecd32c6-6264-437f-a3bb-751c2b85dea6"}],"http://schema.org/description":[{"@language":"fr","@value":"Une occasion inestimable de renouer avec une idole mondiale de la Beat generation, tout en replongeant dans nos racines retraduites, interprétées et diffusées en français kerouaquien, par un trio d’artistes remarquables.\nL’importance de Kerouac, ce petit gars du bas St-Laurent, ce qui le distingue dans la littérature en général, sa langue, son style uniques sont célébrés encore et encore. Cette quête de retrouvailles, ce fort désir de fraternité, de rassemblement, de reconsidérer notre histoire sont aussi enracinés qu’universels.\nLe ton de « Cosse-tu penses mon p’tit bonhomme? » – le début de « On the road », roman culte- La nuit est ma femme-est celui d’une ardente et généreuse confession, d’une é mouvante confidence soufflé e par le dé sir de Kerouac de retrouver par-delà la lancinante distance de l’exil, ses compatriotes à la fois à demi oubliés et douloureusement inoubliables.\nLa détermination, la ténacité, l’acceptation de sa singularité, l’amour pour ses parents, ses proches, touchent l’humanité en chacun et tissent l’espoir chez le spectateur qui en a bien besoin.\nRobert Lalonde auteur et comédien lui-même célébré prête tout son exceptionnel talent à Kerouac de façon saisissante avec les voix et la conception sonore du lumineux duo de Tomas Jensen et Karèya qui ravissent le coeur et l’âme. \n\nPlus d'information"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/84a4f2fb-6b69-486a-8942-e1ebe84432bb"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-02-14T20:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/cosse-tu-veux-mon-tit-bonhomme-lalonde-kerouac/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/7f3ecdba-de65-4041-8871-817fe5b68c82"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-02-14T20:00:00-05:00"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/cosse-tu-veux-mon-tit-bonhomme-lalonde-kerouac/"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/RNT/25/02/14/001/rnt250214001-640x360-fr-m-66459f34.png"}],"http://schema.org/name":[{"@language":"fr","@value":"Cosse-tu penses mon p'tit bonhomme? - Lalonde / Kerouac"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-62"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_cosse-tu-veux-mon-tit-bonhomme-lalonde-kerouac#Offer_0"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_cosse-tu-veux-mon-tit-bonhomme-lalonde-kerouac#Offer_1"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_cosse-tu-veux-mon-tit-bonhomme-lalonde-kerouac#Offer_2"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_cosse-tu-veux-mon-tit-bonhomme-lalonde-kerouac#Offer_3"}]},{"@id":"http://kg.footlight.io/resource/84a4f2fb-6b69-486a-8942-e1ebe84432bb","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Robert Lalonde"}],"http://schema.org/hasOccupation":[{"@language":"fr","@value":"Acteur et écrivain"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Acteur et écrivain"}],"http://schema.org/sameAs":[{"@value":"http://www.wikidata.org/entity/Q3435645"}]},{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/cosse-tu-veux-mon-tit-bonhomme-lalonde-kerouac/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-02-12T12:10:55.655-05:00"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/cosse-tu-veux-mon-tit-bonhomme-lalonde-kerouac/"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_cosse-tu-veux-mon-tit-bonhomme-lalonde-kerouac#Offer_0","http://schema.org/url":[{"@value":"https://lepointdevente.com/tickets/rnt250214001"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"44"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_cosse-tu-veux-mon-tit-bonhomme-lalonde-kerouac#Offer_1","http://schema.org/price":[{"@value":"21.75"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Souper avant le spectacle - Ajoutez un repas à votre expérience! Places limitées. Les repas sont servis entre 18h00 et 19h15."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_cosse-tu-veux-mon-tit-bonhomme-lalonde-kerouac#Offer_2","http://schema.org/price":[{"@value":"39.6"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif membre - Les membres obtiennent 10% de rabais sur le prix régulier des billets."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_cosse-tu-veux-mon-tit-bonhomme-lalonde-kerouac#Offer_3","http://schema.org/price":[{"@value":"33"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif étudiant - 30 ans et moins. Sur présentation d'une preuve le soir du spectacle."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_avec-pas-dcasque","@type":["http://schema.org/Event"],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-02-14T19:00:00-05:00"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/ca6fbc3e-53ed-4843-ac7b-5f7ebd7c2bea"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/avec-pas-dcasque/"}],"http://schema.org/name":[{"@language":"fr","@value":"(COMPLET) Avec Pas D'casque"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-62"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/avec-pas-dcasque/#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5ecd32c6-6264-437f-a3bb-751c2b85dea6"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OnlineEventAttendanceMode"}],"http://schema.org/recordedIn":[{"@value":"https://www.youtube.com/embed/eR_44AzYIZs?si=gy_UGUYIimsTMudx"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-02-14T19:00:00-05:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/7f3ecdba-de65-4041-8871-817fe5b68c82"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/description":[{"@language":"fr","@value":"De retour avec un cinquième album longuement attendu, le groupe Avec pas d’casque continue de peaufiner son « folk maison » à sa manière et à son rythme. Nous invitant à la lenteur et à la gratitude du temps qui nous est alloué, les textes imagés de l’album se déploient dans une ambiance musicale feutrée de guitares, de lapsteel et de claviers. Un spectacle intimiste et planant.\n\nPlus d'information"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_avec-pas-dcasque#Offer_0"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_avec-pas-dcasque#Offer_1"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_avec-pas-dcasque#Offer_2"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_avec-pas-dcasque#Offer_3"}]},{"@id":"http://kg.footlight.io/resource/ca6fbc3e-53ed-4843-ac7b-5f7ebd7c2bea","@type":["http://schema.org/Organization"],"http://schema.org/name":[{"@language":"fr","@value":"Avec pas d'casque"}],"http://schema.org/url":[{"@value":"https://www.avecpasdcasque.com/"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Organization"}]},{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/avec-pas-dcasque/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-02-14T12:10:54.098-05:00"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/avec-pas-dcasque/"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_avec-pas-dcasque#Offer_0","http://schema.org/price":[{"@value":"40"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_avec-pas-dcasque#Offer_1","http://schema.org/price":[{"@value":"21.75"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Souper avant le spectacle - Ajoutez un repas à votre expérience! Places limitées. Les repas sont servis entre 18h00 et 19h15."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_avec-pas-dcasque#Offer_2","http://schema.org/price":[{"@value":"36"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif membre - Les membres obtiennent 10% de rabais sur le prix régulier des billets."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_avec-pas-dcasque#Offer_3","http://schema.org/price":[{"@value":"30"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif étudiant - 30 ans et moins. Sur présentation d'une preuve le soir du spectacle."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_arleen-thibault","@type":["http://schema.org/Event"],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/7f3ecdba-de65-4041-8871-817fe5b68c82"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-02-14T19:00:00-05:00"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OnlineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5ecd32c6-6264-437f-a3bb-751c2b85dea6"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-62"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/arleen-thibault/"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/abde2be9-c0ee-4b5d-8f64-917ebdd94c17"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/arleen-thibault/#WebPage"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-02-14T19:00:00-05:00"}],"http://schema.org/name":[{"@language":"fr","@value":"Fille de laitier - Arleen Thibault"}],"http://schema.org/recordedIn":[{"@value":"[\"https://www.youtube.com/embed/_jqmRbBR-cY?si=wXaRCJmSnB99D8Qd\", \"https://www.facebook.com/plugins/video.php?height=476\u0026href=https%3A%2F%2Fwww.facebook.com%2FGrandeVeillee%2Fvideos%2F1532983727361699%2F\u0026show_text=false\u0026width=380\u0026t=0\"]"}],"http://schema.org/description":[{"@language":"fr","@value":"Fille, filleule et arrière-petite-fille de laitiers, Arleen Thibault a puisé dans les anecdotes de sa famille et une collecte de souvenirs de livreurs de lait auprès du grand public pour nous raconter les vérités et les mythes d’un métier sur le point d’entrer dans la légende. Accompagné du musicien Robin Servant, elle nous livre ce deuxième solo de conte en carrière comme un hommage vibrant au rire et à cette façon désarmante d’accueillir la vie dans les épreuves, qui ont fait de son père et des petits travailleurs de toute une génération des personnages attachants et d’une grande richesse humaine.\nOriginaire de Québec et active sur la scène francophone du conte depuis plus de 20 ans, Arleen a su créer un univers de conte original, rythmé et plein d’humour. Amorçant sa carrière à l’âge de 19 ans, elle a déjà une importante feuille de route à l’internationale et fait partie des figures féminines incontournables du conte au Québec.\n\nPlus d'information"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_arleen-thibault#Offer_0"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_arleen-thibault#Offer_1"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_arleen-thibault#Offer_2"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_arleen-thibault#Offer_3"}]},{"@id":"http://kg.footlight.io/resource/abde2be9-c0ee-4b5d-8f64-917ebdd94c17","@type":["http://schema.org/Person"],"http://schema.org/hasOccupation":[{"@language":"fr","@value":"Conteuse"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Conteuse"}],"http://schema.org/url":[{"@value":"https://arleenthibault.com/"}],"http://schema.org/sameAs":[{"@value":"http://www.wikidata.org/entity/Q110272484"}],"http://schema.org/name":[{"@language":"fr","@value":"Arleen Thibault"}]},{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/arleen-thibault/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-02-14T12:11:08.055-05:00"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/arleen-thibault/"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_arleen-thibault#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"35"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_arleen-thibault#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"Souper avant le spectacle - Ajoutez un repas à votre expérience! Places limitées. Les repas sont servis entre 18h00 et 19h15."}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"21.75"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_arleen-thibault#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"Tarif membre - Les membres obtiennent 10% de rabais sur le prix régulier des billets."}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"31.5"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_arleen-thibault#Offer_3","http://schema.org/name":[{"@language":"fr","@value":"Tarif étudiant - 30 ans et moins. Sur présentation d'une preuve le soir du spectacle."}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"26.25"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_tire-le-coyote","@type":["http://schema.org/Event"],"http://schema.org/description":[{"@language":"fr","@value":"Tire le coyote nous revient avec Dynastie, nouvel album, nouveau concert.\nAmbiances folk-rock habitées par les sonorités des guitares, piano, mellotron, percussions, et toujours l’excellence des textes portés par la plume poétique si touchante et la voix unique de Benoit Pinette.\nEncore une fois un grand rendez-vous.\nÀ l’achat d’un billet, 1$ ira à la « Fondation Cowboys Fringants ».\n\nPlus d'information"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/d72180f1-e600-4814-b61e-e5b303982043"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/tire-le-coyote/"}],"http://schema.org/name":[{"@language":"fr","@value":"Dynastie - Tire le coyote"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-02-14T19:00:00-05:00"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-62"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/tire-le-coyote/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/recordedIn":[{"@value":"[\"https://www.youtube.com/embed/cgqs5OEMox0?si=HvvnegN_HOSUnC44\", \"https://www.youtube.com/embed/7GrE2Mv3y4c?si=8_0vHkr7zYHgDfSk\"]"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-02-14T19:00:00-05:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/7f3ecdba-de65-4041-8871-817fe5b68c82"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OnlineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"https://destinationsaintcamille.ca#Place"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_tire-le-coyote#Offer_0"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_tire-le-coyote#Offer_1"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_tire-le-coyote#Offer_2"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_tire-le-coyote#Offer_3"}]},{"@id":"http://kg.footlight.io/resource/d72180f1-e600-4814-b61e-e5b303982043","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Tire Le Coyote"}],"http://schema.org/hasOccupation":[{"@value":"Auteur-compositeur-interprète"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Auteur-compositeur-interprète"}],"http://schema.org/url":[{"@value":"http://www.tirelecoyote.com/"}],"http://schema.org/alternateName":[{"@language":"fr","@value":"Tire le coyote"}]},{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/tire-le-coyote/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-02-14T12:11:10.803-05:00"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/tire-le-coyote/"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_tire-le-coyote#Offer_0","http://schema.org/price":[{"@value":"45"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_tire-le-coyote#Offer_1","http://schema.org/price":[{"@value":"21.75"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Souper avant le spectacle - Ajoutez un repas à votre expérience! Places limitées. Les repas sont servis entre 18h00 et 19h15."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_tire-le-coyote#Offer_2","http://schema.org/price":[{"@value":"40.5"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif membre - Les membres obtiennent 10% de rabais sur le prix régulier des billets."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_tire-le-coyote#Offer_3","http://schema.org/price":[{"@value":"33.75"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif étudiant - 30 ans et moins. Sur présentation d'une preuve le soir du spectacle."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_alphonse-bisaillon","@type":["http://schema.org/Event"],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/7f3ecdba-de65-4041-8871-817fe5b68c82"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/alphonse-bisaillon/"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/ca59e084-3dfb-431e-b45c-fe537555abef"}],"http://schema.org/name":[{"@language":"fr","@value":"Alphonse Bisaillon"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-02-14T19:00:00-05:00"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-62"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/alphonse-bisaillon/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/recordedIn":[{"@value":"[\"https://www.youtube.com/embed/wDbWKQinkew?si=qAjmI_KBnfN7daOp\", \"https://www.youtube.com/embed/99g1xVUUFJM?si=HLpxGjxP4lhRQjv5\"]"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-02-14T19:00:00-05:00"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OnlineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5ecd32c6-6264-437f-a3bb-751c2b85dea6"}],"http://schema.org/description":[{"@language":"fr","@value":"Alphonse Bisaillon, c’est la fougue d’un Charlebois et la gaminerie d’un Boris Vian. C’est le perfectionnisme maladif et la sensibilité secrète d’un Gainsbourg, l’humilité d’un Bill Evans. C’est un projet de chanson, qui doit beaucoup à Aznavour, mais aussi un projet de musique progressive, qui nous amène dans tous les sens.\nAlphonse, c’est une sensibilité de combat, c’est un respect absolu de l’art et de ce que ça implique être artiste. Alphonse c’est l’art avant tout, parce que c’est l’humain et rien de moins. Alphonse c’est des violons, des violoncelles, de gros synthétiseurs sales, des sections instrumentales à la Astor Piazzolla, du rap, du rock, du disco, des paroles travaillées et retravaillées sur des années. Alphonse, ça peut sembler compliqué, mais c’est tout traversé d’une énergie punk et d’une autodérision à tout casser.\nSon nouvel EP éponyme est paru le 28 octobre 2022 sous l’étiquette Studio B-12. Ce dernier a été réalisé par François Richard et enregistré aux Studio B12 et Studio Le Hublot.\n—\nLa promotion 100% garanti, c’est une promesse alléchante : plaisirs et bons moments seront au rendez-vous ou votre argent sera remis*. Garanti!\nLa mention 100% garanti établit le spectacle choisi comme un incontournable. Le diffuseur est un expert en sélection de spectacles : son talent et son flair sont indéniables. Faites-lui confiance et osez le risque avec lui! La promotion 100% garanti, c’est donc 0% de risque pour vous!\n* Le remboursement du billet doit se faire avant l’entracte ou dans les 35 premières minutes du spectacle.\n\nPlus d'information"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_alphonse-bisaillon#Offer_0"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_alphonse-bisaillon#Offer_1"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_alphonse-bisaillon#Offer_2"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_alphonse-bisaillon#Offer_3"}]},{"@id":"http://kg.footlight.io/resource/ca59e084-3dfb-431e-b45c-fe537555abef","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Alphonse Bisaillon"}],"http://schema.org/hasOccupation":[{"@language":"fr","@value":"Artiste musical"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Artiste musical"}],"http://schema.org/url":[{"@value":"https://alphonse-bisaillon.com/"}],"http://schema.org/sameAs":[{"@value":"https://www.facebook.com/AlphonseBisaillon"}]},{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/alphonse-bisaillon/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-02-14T12:11:01.866-05:00"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/alphonse-bisaillon/"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_alphonse-bisaillon#Offer_0","http://schema.org/price":[{"@value":"33"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_alphonse-bisaillon#Offer_1","http://schema.org/price":[{"@value":"21.75"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Souper avant le spectacle - Ajoutez un repas à votre expérience! Places limitées. Les repas sont servis entre 18h00 et 19h15."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_alphonse-bisaillon#Offer_2","http://schema.org/price":[{"@value":"29.7"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif membre - Les membres obtiennent 10% de rabais sur le prix régulier des billets."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_alphonse-bisaillon#Offer_3","http://schema.org/price":[{"@value":"24.75"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif étudiant - 30 ans et moins. Sur présentation d'une preuve le soir du spectacle."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_alex-burger","@type":["http://schema.org/Event"],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5ecd32c6-6264-437f-a3bb-751c2b85dea6"}],"http://schema.org/recordedIn":[{"@value":"https://player.vimeo.com/video/889648490?h=c943f861ea"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-02-14T19:00:00-05:00"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/alex-burger/"}],"http://schema.org/name":[{"@language":"fr","@value":"Ça s'invente pas - Alex Burger"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K12-485"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-62"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-02-14T19:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/alex-burger/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OnlineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/7f3ecdba-de65-4041-8871-817fe5b68c82"}],"http://schema.org/description":[{"@language":"fr","@value":"Artiste country-rock arrivé par la grand porte, Alex Burger brasse ses cartes sur scène avec brio – alliant country, blues, rock et folk pour propulser des chansons d’observation à la fois orfévrées, drôles et touchantes. Appuyé par ses fidèles et rodés Prix Staff, il chante l’amour, la musique, le sucrage de bec, le contentement simple et la dérape, avançant ses tounes avec un proverbial naturel désarmant, enchaînant ballades contemplatives et cavalcades rock, berçant l’intérieur et faisant rire sans niaiser (ou si peu) avec sa voix dégagée et son verbe à la fois décomplexé et sagace. Son deuxième album paraîtra à l’automne, puis suivront une panoplie de dates québécoises : venez catcher, vous débrayer ou virer sur le top, au choix.\n—\nLa promotion 100% garanti, c’est une promesse alléchante : plaisirs et bons moments seront au rendez-vous ou votre argent sera remis*. Garanti!\nLa mention 100% garanti établit le spectacle choisi comme un incontournable. Le diffuseur est un expert en sélection de spectacles : son talent et son flair sont indéniables. Faites-lui confiance et osez le risque avec lui! La promotion 100% garanti, c’est donc 0% de risque pour vous!\n* Le remboursement du billet doit se faire avant l’entracte ou dans les 35 premières minutes du spectacle.\n\nPlus d'information"}]},{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/alex-burger/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-02-14T12:10:47.090-05:00"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/alex-burger/"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_jean-francois-leger","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/jean-francois-leger/"}],"http://schema.org/description":[{"@language":"fr","@value":"Amoureux des rythmes du Brésil, Jean-François Léger interprète les grandes chansons qui ont fait de la Bossa Nova une véritable révolution culturelle au Brésil et dans le monde. En français et en portugais, Jean-François Léger chante et raconte avec sensibilité, finesse et un brin d’humour, les 60 ans de ces grandes chansons du répertoire de la Bossa Nova qui ont marqué le monde en passant par la France, les États-Unis et le Québec.\nLauréat du Festival international de la chanson de Granby en 1990 dans la catégorie auteur-compositeur-interprète, Jean-François Léger poursuit sa carrière et réalisant plusieurs spectacles de chansons et produit parallèlement des musiques pour le cinéma, le multimédia, le théâtre et le web. Sa musique a traversé les frontières jusqu’en Chine, en passant par le Japon, la France, Israël et même par le mythique Broadway de New-York.\nDepuis sa création au Hudson Music Festival en 2012, son spectacle 60 ans de Bossa Nova a effectué plusieurs tournées très réussies au Québec. Maintenant, Jean-François Léger et son équipe de production préparent pour le marché international, une tournée de son spectacle en version anglaise: “60 years of Bossa Nova”.\n\n\nPlus d'information"}],"http://schema.org/name":[{"@language":"fr","@value":"(ANNULÉ) 60 ans de Bossa Nova - Jean-François Léger"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-02-01T20:00:00-05:00"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-62"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/2f2faaf4-e57d-4f3b-a3b0-af074d031061"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/jean-francois-leger/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5ecd32c6-6264-437f-a3bb-751c2b85dea6"}],"http://schema.org/recordedIn":[{"@value":"[\"https://www.youtube.com/embed/BXhRZ5dERmU?si=ZYgXwKWqmqfEe15I\", \"https://www.youtube.com/embed/Kff-DHvxqdk?si=ZYgXwKWqmqfEe15I\"]"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/7f3ecdba-de65-4041-8871-817fe5b68c82"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/RNT/24/10/26/001/rnt241026001-640x360-fr-m-67929f87.png"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-02-01T20:00:00-05:00"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_jean-francois-leger#Offer_0"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_jean-francois-leger#Offer_1"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_jean-francois-leger#Offer_2"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_jean-francois-leger#Offer_3"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_jean-francois-leger#Offer_4"}]},{"@id":"http://kg.footlight.io/resource/2f2faaf4-e57d-4f3b-a3b0-af074d031061","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Jean-François Léger"}],"http://schema.org/hasOccupation":[{"@language":"fr","@value":"Auteur-compositeur-interprète"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Auteur-compositeur-interprète"}],"http://schema.org/url":[{"@value":"https://jeanfrancoisleger.com/"}]},{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/jean-francois-leger/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-01-31T12:10:22.655-05:00"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/jean-francois-leger/"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_jean-francois-leger#Offer_0","http://schema.org/url":[{"@value":"https://lepointdevente.com/tickets/rnt241026001"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"35"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_jean-francois-leger#Offer_1","http://schema.org/price":[{"@value":"21.75"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Souper avant le spectacle - Ajoutez un repas à votre expérience! Places limitées. Les repas sont servis entre 18h00 et 19h15."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_jean-francois-leger#Offer_2","http://schema.org/price":[{"@value":"31.5"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif membre - Les membres obtiennent 10% de rabais sur le prix régulier des billets."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_jean-francois-leger#Offer_3","http://schema.org/price":[{"@value":"26.25"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif étudiant - 30 ans et moins. Sur présentation d'une preuve le soir du spectacle."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_jean-francois-leger#Offer_4","http://schema.org/price":[{"@value":"17.5"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif personne aînée - 65 ans et plus. Offre valide cette année grâce à l'entente de développement culturel de Saint-Camille."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_carmen-piculeata-et-lorchestre-tzigane-de-montreal","@type":["http://schema.org/Event"],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5ecd32c6-6264-437f-a3bb-751c2b85dea6"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-02-14T19:00:00-05:00"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OnlineEventAttendanceMode"}],"http://schema.org/recordedIn":[{"@value":"https://www.facebook.com/plugins/video.php?height=314\u0026href=https%3A%2F%2Fwww.facebook.com%2FPtitBonheurdeSaintCamille%2Fvideos%2F1279561766492579%2F\u0026show_text=false\u0026width=560\u0026t=0"}],"http://schema.org/description":[{"@language":"fr","@value":"Sous la direction du violoniste virtuose Carmen Piculeata, l’orchestre présente un répertoire très diversifié de musique tzigane traditionnelle intégrant des couleurs de jazz, de classique, et des improvisations toujours originales et enlevantes.\nConsidéré comme l’un des meilleurs violonistes tziganes dans le monde entier, Carmen Piculeata est reconnu pour sa virtuosité et finesse musicale. Issu d’une famille de musiciens tzigane très connue depuis des générations dans sa Roumanie d’origine, il occupe une place tout à fait particulière dans notre paysage musical depuis de nombreuses années. C’est en 2009 qu’il fonde l’Orchestre tzigane de Montréal, dans lequel il s’allie à de jeunes musiciens au talent rare, formés musicalement en Roumanie et au Québec.\n"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/carmen-piculeata-et-lorchestre-tzigane-de-montreal/"}],"http://schema.org/name":[{"@language":"fr","@value":"Carmen Piculeata et l’Orchestre tzigane de Montréal"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/2783d617-e5c3-4a7f-a136-f7b7f075ca6f"},{"@id":"http://kg.footlight.io/resource/3d86b436-7234-409f-8fe8-9312d17f437f"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-62"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-02-14T19:00:00-05:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/7f3ecdba-de65-4041-8871-817fe5b68c82"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/carmen-piculeata-et-lorchestre-tzigane-de-montreal/#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_carmen-piculeata-et-lorchestre-tzigane-de-montreal#Offer_0"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_carmen-piculeata-et-lorchestre-tzigane-de-montreal#Offer_1"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_carmen-piculeata-et-lorchestre-tzigane-de-montreal#Offer_2"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_carmen-piculeata-et-lorchestre-tzigane-de-montreal#Offer_3"}]},{"@id":"http://kg.footlight.io/resource/2783d617-e5c3-4a7f-a136-f7b7f075ca6f","@type":["http://schema.org/Organization"],"http://schema.org/name":[{"@language":"fr","@value":"Orchestre tzigane de Montréal"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Organization"}]},{"@id":"http://kg.footlight.io/resource/3d86b436-7234-409f-8fe8-9312d17f437f","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Carmen Piculeata"}],"http://schema.org/hasOccupation":[{"@language":"fr","@value":"Violoniste"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Violoniste"}]},{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/carmen-piculeata-et-lorchestre-tzigane-de-montreal/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-02-14T12:11:04.939-05:00"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/carmen-piculeata-et-lorchestre-tzigane-de-montreal/"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_carmen-piculeata-et-lorchestre-tzigane-de-montreal#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"38"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_carmen-piculeata-et-lorchestre-tzigane-de-montreal#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"Souper avant le spectacle - Ajoutez un repas à votre expérience! Places limitées. Les repas sont servis entre 18h00 et 19h15."}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"21.75"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_carmen-piculeata-et-lorchestre-tzigane-de-montreal#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"Tarif membre - Les membres obtiennent 10% de rabais sur le prix régulier des billets."}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"34.2"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_carmen-piculeata-et-lorchestre-tzigane-de-montreal#Offer_3","http://schema.org/name":[{"@language":"fr","@value":"Tarif étudiant - 30 ans et moins. Sur présentation d'une preuve le soir du spectacle."}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"28.5"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_ptit-belliveau","@type":["http://schema.org/Event"],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-02-14T19:00:00-05:00"}],"http://schema.org/recordedIn":[{"@value":"[\"https://www.youtube.com/embed/i7d5yh5_9SM?si=SSpxMrsoZJqf2pla\", \"https://www.youtube.com/embed/53yfOXva3ps?si=SSpxMrsoZJqf2pla\", \"https://www.youtube.com/embed/Wleyr2IYCVI?si=xnCzh9zZq0zyEOuX\"]"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/ptit-belliveau/"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/cbe04dde-ab55-42ab-89f1-ebcd2c91a906"}],"http://schema.org/name":[{"@language":"fr","@value":"P'tit Belliveau"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-62"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OnlineEventAttendanceMode"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-02-14T19:00:00-05:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/ptit-belliveau/#WebPage"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5ecd32c6-6264-437f-a3bb-751c2b85dea6"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/7f3ecdba-de65-4041-8871-817fe5b68c82"}],"http://schema.org/description":[{"@language":"fr","@value":"P’tit Belliveau, c’est le projet temps plein de l’expérimentaliste pop et prime alien acadien Jonah Guimond, qui est déjà gréé de deux albums célébrés sur lesquels on retrouve des hymnes générationnels tels que « Income Tax » et « J’aimerais d’avoir un John Deere ». Fort de quatre (4) Club Soda affichant complet, il entend bien se rendre au « […] gros poster [du] Centre Bell sold-out […] » avec P’tit Belliveau, son 3e album à paraître le 27 avril 2024. Homme-orchestre sur disque mais toujours accompagné de son band en show, il colle au simple mais essentiel credo du fun et du « bigger, better, stronger », offrant des versions davantage rock et éclatées de ses chansons les plus propices au party, pour que « ça danse, ça bouge, on boit de la bière » avec « plus de boom, plus de pow », parsemant l’ensemble de quelques reprises avérées : « C’est pas une affaire d’être subtil – faut que ça soit big, pis si ça prend des skulls pis des motorbikes, so be it ». Venez voir par vous-mêmes, y a pas grand chance que vous en ressortiez sans vous joindre à la horde de P’tit Bellivers. \n\nPlus d'information"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_ptit-belliveau#Offer_0"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_ptit-belliveau#Offer_1"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_ptit-belliveau#Offer_2"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_ptit-belliveau#Offer_3"}]},{"@id":"http://kg.footlight.io/resource/cbe04dde-ab55-42ab-89f1-ebcd2c91a906","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"P'tit Belliveau"}],"http://schema.org/hasOccupation":[{"@language":"fr","@value":"Musicien"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Musicien"}],"http://schema.org/alternateName":[{"@language":"fr","@value":"Ptit Belliveau"}],"http://schema.org/sameAs":[{"@value":"http://kg.artsdata.ca/resource/K12-387"},{"@value":"http://www.wikidata.org/entity/Q97051009"}]},{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/ptit-belliveau/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-02-14T12:10:57.873-05:00"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/ptit-belliveau/"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_ptit-belliveau#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"44"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_ptit-belliveau#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"Souper avant le spectacle - Ajoutez un repas à votre expérience! Places limitées. Les repas sont servis entre 18h00 et 19h15."}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"21.75"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_ptit-belliveau#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"Tarif membre - Les membres obtiennent 10% de rabais sur le prix régulier des billets."}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"39.6"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_ptit-belliveau#Offer_3","http://schema.org/name":[{"@language":"fr","@value":"Tarif étudiant - 30 ans et moins. Sur présentation d'une preuve le soir du spectacle."}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"33"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_charles-beauchesne","@type":["http://schema.org/Event"],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/7f3ecdba-de65-4041-8871-817fe5b68c82"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/6a49463e-3b56-40da-82e8-e3176842b682"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-62"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-14T20:00:00-04:00"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/RNT/26/03/14/001/rnt260314001-640x360-fr-m-6801e3c8.png"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-03-14T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/charles-beauchesne/#WebPage"}],"http://schema.org/location":[{"@id":"https://destinationsaintcamille.ca#Place"}],"http://schema.org/description":[{"@language":"fr","@value":"Londres, la fin du 19ᵉ siècle. Alors que la populace oeuvre matin et soir dans les usines toxiques et dangereuses de l’ère industrielle, un monstre sans nom ni visage se terre dans les ruelles. Un meurtrier dont on ne connait rien, qui donnera naissance au 20ᵉ siècle par l’utérus ensanglanté des prostitués de Whitechapel, avant d’inexplicablement disparaître à jamais dans le brouillard londonien. Suivez l’humoriste le plus sinistre en ville sur les traces de Jack l’éventreur alors qu’il tente (tout en humour doit-on le rappeler) de percer le secret du plus légendaire des tueurs en série.\nComment décrire Charles Beauchesne, outre que comme l’intellectuel vampirique préféré de toute la famille. Humoriste, auteur et conférencier, son parler soutenu aux allures de documentaire scientifique et de vieux grimoire médiéval ne laisse certes personne indifférent en tout cas…\nVous le connaissez sans doute grâce au succès (relatif) de son célèbre balado historico-humoristique LES PIRES MOMENTS DE L’HISTOIRE (récipiendaire de l’Olivier du meilleur podcast en 2021/2022/2023 mais en même temps c’est pas un concours non plus), mais plus régulièrement aussi à titre de chroniqueur funné à la prestigieuse émission LA JOURNÉE EST ENCORE JEUNE (du grand art ça aussi en tout cas!).\nEh bien soyez informés que ce genre d’hurluberlu écume présentement les contrés avec une série de TROIS SPECTACLES. Premièrement, une série de deux conférences «Charles Beauchesne parle de la peste noire pendant soixante minute» ainsi que son analogue «Charles Beauchesne parle de Jack l’éventreur pendant soixante minutes» (je vous avertis, il y a des diapos), mais également en bon vieux stand up avec son spectacle pour le moins labyrinthique «Les corbeaux me regardent d’un air bizarre». En fait, la biographie aurait simplement pu être une liste de ses titres de spectacles et vous auriez compris le personnage. J’en prends note pour la prochaine fois!\n\nPlus d'information"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/charles-beauchesne/"}],"http://schema.org/name":[{"@language":"fr","@value":"Charles Beauchesne"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_charles-beauchesne#Offer_0"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_charles-beauchesne#Offer_1"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_charles-beauchesne#Offer_2"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_charles-beauchesne#Offer_3"}]},{"@id":"http://kg.footlight.io/resource/6a49463e-3b56-40da-82e8-e3176842b682","@type":["http://schema.org/Person"],"http://schema.org/name":[{"@language":"fr","@value":"Charles Beauchesne"}],"http://schema.org/hasOccupation":[{"@value":"Humoriste"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Humoriste"}]},{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/charles-beauchesne/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T12:10:45.450-05:00"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/charles-beauchesne/"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_charles-beauchesne#Offer_0","http://schema.org/price":[{"@value":"38"}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://lepointdevente.com/tickets/rnt260314001"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_charles-beauchesne#Offer_1","http://schema.org/price":[{"@value":"22.5"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Souper avant le spectacle - Ajoutez un repas à votre expérience! Places limitées. Les repas sont servis entre 18h00 et 19h15."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_charles-beauchesne#Offer_2","http://schema.org/price":[{"@value":"34.2"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif membre - Les membres obtiennent 10% de rabais sur le prix régulier des billets."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_charles-beauchesne#Offer_3","http://schema.org/price":[{"@value":"28.5"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif étudiant - 30 ans et moins. Sur présentation d'une preuve le soir du spectacle."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_dans-lunivers-dastor","@type":["http://schema.org/Event"],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/f810db20-3016-46a0-b26d-dbf05f018682"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-62"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-10-25T20:00:00-04:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/7f3ecdba-de65-4041-8871-817fe5b68c82"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5ecd32c6-6264-437f-a3bb-751c2b85dea6"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-10-25T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/dans-lunivers-dastor/#WebPage"}],"http://schema.org/description":[{"@language":"fr","@value":"Dans l’univers d’Astor est un spectacle musical et narratif qui plonge le public dans les récits de vie, la musique et les inspirations d’Astor Piazzolla. De New York à Paris, en passant par Buenos Aires, c’est un portrait original où les œuvres du grand compositeur argentin s’entremêlent à la musique classique, le jazz, le tango traditionnel, la musique klezmer et de fascinantes anecdotes biographiques.\nL’ensemble Mistral est le fruit de la collaboration de musiciens issus de la musique classique et du jazz, dont le mandat est de créer un point de rencontre entre divers genres musicaux, en créant des concerts multidisciplinaires avec une instrumentation originale et unique: violon/alto, saxophones/flûte/clarinette, guitare et contrebasse.\nNadia Monczak est une violoniste et altiste canadienne d’origine polonaise qui s’est produite en tant que chambriste et soliste dans près de 30 pays à travers le monde dans des salles de renommées internationales. \nBenjamin Deschamps est un saxophoniste, multi-instrumentiste et compositeur jazz montréalais qui s’est établi comme un incontournable de la scène jazz canadienne (Révélation Radio-Canada Jazz 2017-18, Grand Prix TD 2019 du FIJM et le Prix Opus Album Jazz de l’année 2023). \nHugo Larenas est un guitariste montréalais d’origine chilienne. Son parcours musical l’a amené à explorer plusieurs styles musicaux: du folklore de sa terre d’origine, en passant par le rock, le flamenco (Intakto) et la musique classique. \nJean-Félix Mailloux est actif au sein de plusieurs projets auxquels il contribue en tant que producteur, compositeur, arrangeur et contrebassiste. Il a produit 10 disques et a participé à de nombreuses tournées au Canada, en Europe et en Asie.\n\nPlus d'information"}],"http://schema.org/recordedIn":[{"@value":"[\"https://www.youtube.com/embed/xMGpJl7IKO0?si=gy_UGUYIimsTMudx\", \"https://www.youtube.com/embed/zdLywA2vrys?si=gy_UGUYIimsTMudx\", \"https://www.youtube.com/embed/lW1-shzvXak?si=gy_UGUYIimsTMudx\", \"https://www.youtube.com/embed/vyMxHE1DhZ4?si=gy_UGUYIimsTMudx\"]"}],"http://schema.org/name":[{"@language":"fr","@value":"Dans l'univers d'Astor"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/RNT/25/10/25/001/rnt251025001-640x360-fr-m-6801ccea.png"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/dans-lunivers-dastor/"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_dans-lunivers-dastor#Offer_0"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_dans-lunivers-dastor#Offer_1"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_dans-lunivers-dastor#Offer_2"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_dans-lunivers-dastor#Offer_3"}]},{"@id":"http://kg.footlight.io/resource/f810db20-3016-46a0-b26d-dbf05f018682","@type":["http://schema.org/Organization"],"http://schema.org/name":[{"@language":"fr","@value":"Ensemble Mistral"}],"http://schema.org/url":[{"@value":"https://ensemblemistral.com/"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Organization"}],"http://schema.org/sameAs":[{"@value":"https://www.facebook.com/ens.mistral/"}]},{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/dans-lunivers-dastor/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-10-24T13:10:13.027-04:00"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/dans-lunivers-dastor/"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_dans-lunivers-dastor#Offer_0","http://schema.org/url":[{"@value":"https://lepointdevente.com/tickets/rnt251025001"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"34"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_dans-lunivers-dastor#Offer_1","http://schema.org/price":[{"@value":"22.5"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Souper avant le spectacle - Ajoutez un repas à votre expérience! Places limitées. Les repas sont servis entre 18h00 et 19h15."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_dans-lunivers-dastor#Offer_2","http://schema.org/price":[{"@value":"30.5"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif membre - Les membres obtiennent 10% de rabais sur le prix régulier des billets."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_dans-lunivers-dastor#Offer_3","http://schema.org/price":[{"@value":"25.5"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif étudiant - 30 ans et moins. Sur présentation d'une preuve le soir du spectacle."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_too-many-cooks","@type":["http://schema.org/Event"],"http://schema.org/name":[{"@language":"fr","@value":"Too Many Cooks - en trio"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/description":[{"@language":"fr","@value":"C’est accompagné par quatre musiciens et leur fidèle choriste Lulu Hughes que le groupe Sherbrookois fondé par Dan Georgesco et Richard D’Anjou vous feront revivre avec fougue les grands moments de leur carrière avec les succès qu’on leur connait : Rita, Refuse to die, Believe me Sister et Who pour ne nommer que ceux-là\nUn spectacle rock, énergique et mémorable!\n—\nLa promotion 100% garanti, c’est une promesse alléchante : plaisirs et bons moments seront au rendez-vous ou votre argent sera remis*. Garanti!\nLa mention 100% garanti établit le spectacle choisi comme un incontournable. Le diffuseur est un expert en sélection de spectacles : son talent et son flair sont indéniables. Faites-lui confiance et osez le risque avec lui! La promotion 100% garanti, c’est donc 0% de risque pour vous!\n* Le remboursement du billet doit se faire avant l’entracte ou dans les 35 premières minutes du spectacle.\n\nPlus d'information"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5ecd32c6-6264-437f-a3bb-751c2b85dea6"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-09-13T20:00:00-04:00"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-62"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/RNT/25/09/13/001/rnt250913001-640x360-fr-m-6801c6fe.png"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-09-13T20:00:00-04:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/7f3ecdba-de65-4041-8871-817fe5b68c82"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/too-many-cooks/"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/too-many-cooks/#WebPage"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_too-many-cooks#Offer_0"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_too-many-cooks#Offer_1"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_too-many-cooks#Offer_2"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_too-many-cooks#Offer_3"}]},{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/too-many-cooks/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-09-12T13:11:01.812-04:00"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/too-many-cooks/"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_too-many-cooks#Offer_0","http://schema.org/url":[{"@value":"https://lepointdevente.com/tickets/rnt250913001"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"45"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_too-many-cooks#Offer_1","http://schema.org/price":[{"@value":"22.5"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Souper avant le spectacle - Ajoutez un repas à votre expérience! Places limitées. Les repas sont servis entre 18h00 et 19h15."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_too-many-cooks#Offer_2","http://schema.org/price":[{"@value":"40.5"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif membre - Les membres obtiennent 10% de rabais sur le prix régulier des billets."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_too-many-cooks#Offer_3","http://schema.org/price":[{"@value":"33.75"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif étudiant - 30 ans et moins. Sur présentation d'une preuve le soir du spectacle."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_alice-bro","@type":["http://schema.org/Event"],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/7f3ecdba-de65-4041-8871-817fe5b68c82"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-08T20:00:00-04:00"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/alice-bro/"}],"http://schema.org/performer":[{"@id":"http://kg.footlight.io/resource/1d725b86-0112-42cb-84b7-2f0178ea6110"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-62"}],"http://schema.org/name":[{"@language":"fr","@value":"Alice Bro"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/alice-bro/#WebPage"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/RNT/26/05/08/001/rnt260508001-640x360-fr-m-6801d000.png"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-08T20:00:00-04:00"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5ecd32c6-6264-437f-a3bb-751c2b85dea6"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/description":[{"@language":"fr","@value":"Alice Bro présente son projet solo au son festif, folk, grivois, qui laisse son public presque aussi perplexe qu’un carambolage en hiver. Souvent appelé la reine des « botchs », la compositrice explore les convois douteux de la vulgarité, tout en exprimant de profonds tourments existentiels. C’est en partageant l’ivresse du moment que vous comprendrez l’essence de cet artiste flamboyante.\n—\nLa promotion 100% garanti, c’est une promesse alléchante : plaisirs et bons moments seront au rendez-vous ou votre argent sera remis*. Garanti!\nLa mention 100% garanti établit le spectacle choisi comme un incontournable. Le diffuseur est un expert en sélection de spectacles : son talent et son flair sont indéniables. Faites-lui confiance et osez le risque avec lui! La promotion 100% garanti, c’est donc 0% de risque pour vous!\n* Le remboursement du billet doit se faire avant l’entracte ou dans les 35 premières minutes du spectacle.\n\nPlus d'information"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_alice-bro#Offer_0"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_alice-bro#Offer_1"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_alice-bro#Offer_2"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_alice-bro#Offer_3"}]},{"@id":"http://kg.footlight.io/resource/1d725b86-0112-42cb-84b7-2f0178ea6110","@type":["http://schema.org/Organization"],"http://schema.org/name":[{"@language":"fr","@value":"Alice Bro"}],"http://schema.org/url":[{"@value":"https://www.coyoterecords.ca/en/artists/alice-bro"}],"http://schema.org/disambiguatingDescription":[{"@language":"en","@value":"Organization"}]},{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/alice-bro/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T12:10:55.238-05:00"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/alice-bro/"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_alice-bro#Offer_0","http://schema.org/price":[{"@value":"25"}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://lepointdevente.com/tickets/rnt260508001"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_alice-bro#Offer_1","http://schema.org/price":[{"@value":"22.5"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Souper avant le spectacle - Ajoutez un repas à votre expérience! Places limitées. Les repas sont servis entre 18h00 et 19h15."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_alice-bro#Offer_2","http://schema.org/price":[{"@value":"22.5"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif membre - Les membres obtiennent 10% de rabais sur le prix régulier des billets."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_alice-bro#Offer_3","http://schema.org/price":[{"@value":"18.75"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif étudiant - 30 ans et moins. Sur présentation d'une preuve le soir du spectacle."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_diane-tell","@type":["http://schema.org/Event"],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/description":[{"@language":"fr","@value":"« Je n’aime pas les anniversaires. Noël, encore moins… et je suis née pendant le réveillon ! Entre la dinde et les cadeaux. J’aurais pu finir dans la crèche avec le petit Jésus ! Exactement comme Zac dans le film C.R.A.Z.Y. de notre si cher et regretté Jean-Marc Vallée. Chez Bonsound, notre belle maison de concerts, quelqu’un a lancé l’idée : « Diane, ça te dirait de faire une tournée anniversaire ? » Je n’ai pas dit oui tout de suite. Bon. Un 50 i’aime. Quand même. Ça se fête ! Et puis, on sera tranquille. Il n’y aura pas de 100 i’aime ! J’ai mordu à l’hameçon comme un beau doré du lac Blouin… et j’ai signé. Déjà plusieurs concerts programmés au Québec, en Ontario, au Nouveau-Brunswick. C’est fantastique. \nEn 1977, je lançais mon premier album. Celui où je pose sur la pochette avec Raggedy Ann dans les bras, le pouce dans la bouche et les jambes écartées ! Ah ! J’étais Polissonne à 19 ans. Je le suis toujours un peu. Polissonne, c’est aussi le titre de mon prochain album. Ça promet. Je tournais déjà en 1976. Je chantais partout, seule avec ma guitare : dans les bars, les restaurants, au Maples Inn à Pointe-Claire, à l’Évêché de l’Hôtel Nelson dans le Vieux-Montréal. J’ai même chanté au Forum, en première partie de Chris de Burgh, à l’époque. \nPour cette série de concerts, je ne serai pas seule. Un extraordinaire band québécois cinq étoiles, dirigé par l’unique et talentueux Julien Fillion, prendra la route à mes côtés. Quelle chance. Le répertoire — puisé dans quinze albums — choisi pour notre bonheur et le vôtre, sera sublimé par le talent de ces jeunes musiciens. Alors… on se donne le mot ? Rendez-vous compte : 50 ans de musique, de chansons, de scène, non-stop. Mais c’est surtout votre fidélité qui mérite d’être célébrée. Allez. C’est ma tournée. Le 50 i’aime anniversaire de notre rencontre. Tu te souviens ?»\n\n\nPlus d'information"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/7f3ecdba-de65-4041-8871-817fe5b68c82"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/diane-tell/"}],"http://schema.org/name":[{"@language":"fr","@value":"Diane Tell - Tournée 50 i'aime"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/RNT/26/10/11/001/rnt261011001-640x360-fr-m-68c9bf8f.png"}],"http://schema.org/performer":[{"@id":"http://kg.artsdata.ca/resource/K13-52"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-62"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-11T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/diane-tell/#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-10-11T20:00:00-04:00"}],"http://schema.org/location":[{"@id":"https://destinationsaintcamille.ca#Place"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_diane-tell#Offer_0"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_diane-tell#Offer_1"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_diane-tell#Offer_2"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_diane-tell#Offer_3"}]},{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/diane-tell/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T12:11:04.997-05:00"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/diane-tell/"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_diane-tell#Offer_0","http://schema.org/price":[{"@value":"49"}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://lepointdevente.com/tickets/rnt261011001"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_diane-tell#Offer_1","http://schema.org/price":[{"@value":"23.35"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Souper avant le spectacle - Ajoutez un repas à votre expérience! Places limitées. Les repas sont servis entre 18h00 et 19h15."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_diane-tell#Offer_2","http://schema.org/price":[{"@value":"44.21"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif membre - Les membres obtiennent 10% de rabais sur le prix régulier des billets."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_diane-tell#Offer_3","http://schema.org/price":[{"@value":"37.02"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif étudiant - 30 ans et moins. Sur présentation d'une preuve le soir du spectacle."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_les-charbonniers-de-lenfer","@type":["http://schema.org/Event"],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/7f3ecdba-de65-4041-8871-817fe5b68c82"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/les-charbonniers-de-lenfer/"}],"http://schema.org/name":[{"@language":"fr","@value":"COMPLET - Les Charbonniers de l'enfer"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-11T20:00:00-04:00"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-62"}],"http://schema.org/location":[{"@id":"https://destinationsaintcamille.ca#Place"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-11T20:00:00-04:00"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/RNT/26/04/11/001/rnt260411001-640x360-fr-m-6801e57b.png"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/les-charbonniers-de-lenfer/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/description":[{"@language":"fr","@value":"« Le temps passe et il y met le temps » disait une chanson. Et le temps a passé… après plus de trente années à charbonner joyeusement ici comme ailleurs sur les braises ardentes du répertoire traditionnel québécois,\n30 ans de rapaillages et de partage;\n30 ans à chanter sans jamais déchanter sur l’accord du pied;\n30 ans de camaraderie à l’unisson ou en harmonie;\n30 ans à bien s’entendre et des fois à s’attendre.\nAprès tous ces temps de joie et une grosse peine marquée par le deuil de leur grand Jean-Claude Rossignolet des bois, pourquoi ne pas se faire plaisir et du même coup faire plaisir? \n C’est ainsi que Michel Bordeleau, Michel Faubert, André Marchand et Normand Miron reprennent les grands chemins qu’ils ont tant parcourus. Un ultime tour de leur monde, question de pouvoir offrir une dernière occasion de voir sur les planches, dans un spectacle mis en scène par le regard brillant d’Émile Proulx-Cloutier, charbonniers chanter… Les Charbonniers de l’enfer!\n\nPlus d'information"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_les-charbonniers-de-lenfer#Offer_0"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_les-charbonniers-de-lenfer#Offer_1"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_les-charbonniers-de-lenfer#Offer_2"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_les-charbonniers-de-lenfer#Offer_3"}]},{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/les-charbonniers-de-lenfer/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T12:11:01.911-05:00"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/les-charbonniers-de-lenfer/"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_les-charbonniers-de-lenfer#Offer_0","http://schema.org/price":[{"@value":"48"}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://lepointdevente.com/tickets/rnt260411001"}],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_les-charbonniers-de-lenfer#Offer_1","http://schema.org/price":[{"@value":"22.5"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Souper avant le spectacle - Ajoutez un repas à votre expérience! Places limitées. Les repas sont servis entre 18h00 et 19h15."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_les-charbonniers-de-lenfer#Offer_2","http://schema.org/price":[{"@value":"43.2"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif membre - Les membres obtiennent 10% de rabais sur le prix régulier des billets."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_les-charbonniers-de-lenfer#Offer_3","http://schema.org/price":[{"@value":"36"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Tarif étudiant - 30 ans et moins. Sur présentation d'une preuve le soir du spectacle."}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_anyma-ora","@type":["http://schema.org/Event"],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/7f3ecdba-de65-4041-8871-817fe5b68c82"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/name":[{"@language":"fr","@value":"Anyma Ora'"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-62"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5ecd32c6-6264-437f-a3bb-751c2b85dea6"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/RNT/26/03/28/001/rnt260328001-640x360-fr-m-6801d17b.png"}],"http://schema.org/recordedIn":[{"@value":"[\"https://www.facebook.com/plugins/video.php?height=476\u0026href=https%3A%2F%2Fwww.facebook.com%2Freel%2F1332597798323247%2F\u0026show_text=false\u0026width=267\u0026t=0\", \"https://www.youtube.com/embed/uukOA1iKU-o?si=7AVehsqx9SD6_tPO\", \"https://www.youtube.com/embed/2WlGQK09dOU?si=Jhqt48LI-GkFhn2C\"]"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-03T20:00:00-04:00"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/anyma-ora/"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/anyma-ora/#WebPage"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-04-03T20:00:00-04:00"}],"http://schema.org/description":[{"@language":"fr","@value":"ANYMA ORA’ transporte son public dans un univers électro-pop envoûtant, où musique et danse s’entrelacent pour créer un spectacle hors du commun et d’une qualité exceptionnelle. Originaire de Wendake, elle fusionne son héritage autochtone avec des sonorités modernes, offrant un spectacle à la fois mystique et électrisant. Une véritable célébration de la connexion entre le passé et le futur.\nSur scène, rythmes électroniques et percussions traditionnelles se mêlent à des tableaux chorégraphiques saisissants, portés par la présence magnétique d’ANYMA ORA’.\nAvec un premier EP, Humans (2021), acclamé par la critique et plusieurs distinctions marquantes en 2025 (Prix Stingray, ROSEQ et LOJIQ), ANYMA ORA’ s’impose comme une artiste incontournable, à la croisée des mondes et des cultures.\nÀ ne pas manquer!\n\nPlus d'information"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_anyma-ora#Offer_0"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_anyma-ora#Offer_1"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_anyma-ora#Offer_2"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_anyma-ora#Offer_3"}]},{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/anyma-ora/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T12:10:48.658-05:00"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/anyma-ora/"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_anyma-ora#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://lepointdevente.com/tickets/rnt260328001"}],"http://schema.org/price":[{"@value":"30"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_anyma-ora#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"Souper avant le spectacle - Ajoutez un repas à votre expérience! Places limitées. Les repas sont servis entre 18h00 et 19h15."}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"22.5"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_anyma-ora#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"Tarif membre - Les membres obtiennent 10% de rabais sur le prix régulier des billets."}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"27"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_anyma-ora#Offer_3","http://schema.org/name":[{"@language":"fr","@value":"Tarif étudiant - 30 ans et moins. Sur présentation d'une preuve le soir du spectacle."}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"22.5"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_supplementaire-vingt-mille-matins-daniel-boucher","@type":["http://schema.org/Event"],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-62"}],"http://schema.org/description":[{"@language":"fr","@value":"En supplémentaire! Merci de votre confiance.\nPour célébrer à la fois ses vingt-cinq ans de carrière et les vingt-cinq ans de son mythique premier album, Daniel Boucher présente un tout nouveau spectacle pour 2025 : Vingt mille matins.\nCe spectacle, offert en formule solo, offre un équilibre parfait entre le passé et l’avenir, mettant en avant les plus grandes chansons de son répertoire, qui compte maintenant neuf albums.\nCe spectacle promet une explosion de couleurs vives, d’énergie brute, de chaleur humaine et d’amour du Québec.\nUn rendez-vous incontournable en 2025.\nCélébrez avec Daniel Boucher ses vingt-cinq ans de carrière.\nCélébrez ses Vingt mille matins.\n\n\nPlus d'information"}],"http://schema.org/name":[{"@language":"fr","@value":"COMPLET - (Supplémentaire) Vingt mille matins - Daniel Boucher"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-09-26T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/supplementaire-vingt-mille-matins-daniel-boucher/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/supplementaire-vingt-mille-matins-daniel-boucher/"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5ecd32c6-6264-437f-a3bb-751c2b85dea6"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/RNT/25/09/27/001/rnt250927001-640x360-fr-m-674f65da.png"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-09-26T20:00:00-04:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/7f3ecdba-de65-4041-8871-817fe5b68c82"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_supplementaire-vingt-mille-matins-daniel-boucher#Offer_0"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_supplementaire-vingt-mille-matins-daniel-boucher#Offer_1"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_supplementaire-vingt-mille-matins-daniel-boucher#Offer_2"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_supplementaire-vingt-mille-matins-daniel-boucher#Offer_3"}]},{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/supplementaire-vingt-mille-matins-daniel-boucher/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-09-24T13:10:57.269-04:00"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/supplementaire-vingt-mille-matins-daniel-boucher/"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_supplementaire-vingt-mille-matins-daniel-boucher#Offer_0","http://schema.org/url":[{"@value":"https://lepointdevente.com/tickets/rnt250926001"}],"@type":["http://schema.org/Offer"],"http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"http://schema.org/price":[{"@value":"49"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_supplementaire-vingt-mille-matins-daniel-boucher#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"Souper avant le spectacle - Ajoutez un repas à votre expérience! Places limitées. Les repas sont servis entre 18h00 et 19h15."}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"22.5"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_supplementaire-vingt-mille-matins-daniel-boucher#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"Tarif membre - Les membres obtiennent 10% de rabais sur le prix régulier des billets."}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"44.1"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_supplementaire-vingt-mille-matins-daniel-boucher#Offer_3","http://schema.org/name":[{"@language":"fr","@value":"Tarif étudiant - 30 ans et moins. Sur présentation d'une preuve le soir du spectacle."}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"36.75"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_un-petit-trottoir-au-dessus-de-labime","@type":["http://schema.org/Event"],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/7f3ecdba-de65-4041-8871-817fe5b68c82"}],"http://schema.org/name":[{"@language":"fr","@value":"Indomptable Virginia Woolf"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/RNT/25/10/11/001/rnt251011001-640x360-fr-m-6801ca49.png"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-62"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/un-petit-trottoir-au-dessus-de-labime/"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-10-11T20:00:00-04:00"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/un-petit-trottoir-au-dessus-de-labime/#WebPage"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/description":[{"@language":"fr","@value":"Indomptable Virginia Woolf spectacle-littéraire sur Virginia Woolf, grande autrice emblématique ayant à la fois révolutionné l’art d’écrire et pour la première fois interrogé le rôle des femmes. Un texte de Robert Lalonde qui interprète le tendre mari de Virginia, Bénédicte Décary, sa sœur complice et admirative, Johanne Haberlin grande tragédienne qui incarne Virginia avec panache et une chanteuse-compositrice-multi-instrumentiste sur scène. Le tout mis en scène par Lorraine Pintal. Un texte qui ressemble à Virginia, qui était à la fois à la fois curieuse, délirante et dramatique, drôle, ironique et délicieusement cinglante.» Des sujets très actuels : le couple aimant, la place des femmes, la dictature, l’amitié entre sœurs, l’attirance bisexuelle, la liberté, la difficulté de vivre, l’acceptation des choix de l’autre…\nConsultez l’article de La Tribune sur le spectacle!\n\nPlus d'information"}],"http://schema.org/location":[{"@id":"https://destinationsaintcamille.ca#Place"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/recordedIn":[{"@value":"https://www.facebook.com/plugins/video.php?height=314\u0026href=https%3A%2F%2Fwww.facebook.com%2FPtitBonheurdeSaintCamille%2Fvideos%2F1451579659230712%2F\u0026show_text=false\u0026width=560\u0026t=0"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-10-11T20:00:00-04:00"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_un-petit-trottoir-au-dessus-de-labime#Offer_0"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_un-petit-trottoir-au-dessus-de-labime#Offer_1"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_un-petit-trottoir-au-dessus-de-labime#Offer_2"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_un-petit-trottoir-au-dessus-de-labime#Offer_3"}]},{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/un-petit-trottoir-au-dessus-de-labime/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-10-10T13:11:32.039-04:00"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/un-petit-trottoir-au-dessus-de-labime/"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_un-petit-trottoir-au-dessus-de-labime#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale"}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://lepointdevente.com/tickets/rnt251011001"}],"http://schema.org/price":[{"@value":"48"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_un-petit-trottoir-au-dessus-de-labime#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"Souper avant le spectacle - Ajoutez un repas à votre expérience! Places limitées. Les repas sont servis entre 18h00 et 19h15."}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"22.5"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_un-petit-trottoir-au-dessus-de-labime#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"Tarif membre - Les membres obtiennent 10% de rabais sur le prix régulier des billets."}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"43.2"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_un-petit-trottoir-au-dessus-de-labime#Offer_3","http://schema.org/name":[{"@language":"fr","@value":"Tarif étudiant - 30 ans et moins. Sur présentation d'une preuve le soir du spectacle."}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"36"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_osmoze","@type":["http://schema.org/Event"],"http://schema.org/mainEntityOfPage":[{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/osmoze/#WebPage"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/RNT/25/11/08/001/rnt251108001-640x360-fr-m-68490114.png"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/osmoze/"}],"http://schema.org/name":[{"@language":"fr","@value":"COMPLET - Osmoze"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-08T20:00:00-05:00"}],"http://schema.org/organizer":[{"@id":"http://kg.footlight.io/resource/414d2676-ec62-479e-bd62-1303cbb037cc"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-08T20:00:00-05:00"}],"http://schema.org/description":[{"@language":"fr","@value":"Le P’tit Bonheur de Saint-Camille a l’honneur de recevoir Osmoze, un groupe rock dont fait partie le camillois Gabriel Bourque.\nVenez faire le party avec nous sur les succès rock des années 50 à aujourd’hui!\nAchat par téléphone seulement.\nDemandez Adèle au 819 828-0123.\nAdultes : 25 $\nAdos (12-18 ans) : 12 $\nEnfants (11 ans et moins) : Gratuit\n"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/7f3ecdba-de65-4041-8871-817fe5b68c82"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/location":[{"@id":"http://kg.footlight.io/resource/5ecd32c6-6264-437f-a3bb-751c2b85dea6"}]},{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/osmoze/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2025-11-07T12:11:18.028-05:00"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/osmoze/"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_le-bibliothecaire","@type":["http://schema.org/Event"],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/le-bibliothecaire/"}],"http://schema.org/description":[{"@language":"fr","@value":"À première vue, Paul-Émile Dumoulin est un bibliothécaire tout à fait comme les autres. Il est sérieux, fier et soucieux de l’ordre : ses rayons sont impeccables et chaque livre est à sa place. Le hic, c’est que dès qu’il met le nez dans un livre, la réalité disparaît et laisse place aux univers un peu fous découverts au fil de ses lectures. Au gré des aventures qu’il découvre, le sage bibliothécaire se transforme. Il incarne soudainement d’étranges personnages loufoques, enchaînant acrobaties, équilibres, jonglerie, break dance et beat box. Jusqu’où la lecture le mènera-t-il?\nLe Gros Orteil\nLa compagnie Le Gros Orteil, fondée en 2013, a pour mission première la création de spectacles, d’animations et d’ateliers professionnels jeune public, mixant les disciplines des arts du cirque, du théâtre physique et du jeu clownesque. La compagnie vise à favoriser l’improvisation comme départ à la naissance d’un spectacle, traduire l’authenticité des émotions, et privilégier des interactions conviviales avec son public. Parce que les enfants sont des pépites de spontanéité et d’une imagination débordante, nos spectacles font une large place à l’imaginaire, au voyage, et au rêve. Sans quatrième mur, les artistes embarquent les enfants dans leurs histoires.\n\nPlus d'information"}],"http://schema.org/organizer":[{"@id":"http://kg.artsdata.ca/resource/K16-62"}],"http://schema.org/endDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-17T15:00:00-04:00"}],"http://schema.org/name":[{"@language":"fr","@value":"Le bibliothécaire"}],"http://schema.org/eventStatus":[{"@id":"http://schema.org/EventScheduled"}],"http://schema.org/location":[{"@id":"https://destinationsaintcamille.ca#Place"}],"http://schema.org/image":[{"@value":"https://tpos.s3.amazonaws.com/events/RNT/26/05/17/001/rnt260517001-640x360-fr-m-6801e9a6.png"}],"http://schema.org/mainEntityOfPage":[{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/le-bibliothecaire/#WebPage"}],"http://schema.org/eventAttendanceMode":[{"@id":"http://schema.org/OfflineEventAttendanceMode"}],"http://schema.org/startDate":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-05-17T15:00:00-04:00"}],"http://schema.org/additionalType":[{"@id":"http://kg.footlight.io/resource/7f3ecdba-de65-4041-8871-817fe5b68c82"}],"http://schema.org/offers":[{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_le-bibliothecaire#Offer_0"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_le-bibliothecaire#Offer_1"},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_le-bibliothecaire#Offer_2"}]},{"@id":"https://ptitbonheur.org/nos-evenements/spectacles/le-bibliothecaire/#WebPage","@type":["http://schema.org/WebPage"],"http://schema.org/inLanguage":[{"@value":"fr"}],"http://schema.org/lastReviewed":[{"@type":"http://www.w3.org/2001/XMLSchema#dateTime","@value":"2026-02-01T12:10:58.110-05:00"}],"http://schema.org/url":[{"@value":"https://ptitbonheur.org/nos-evenements/spectacles/le-bibliothecaire/"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_le-bibliothecaire#Offer_0","http://schema.org/name":[{"@language":"fr","@value":"Admission générale - 13 ans et plus"}],"@type":["http://schema.org/Offer"],"http://schema.org/url":[{"@value":"https://lepointdevente.com/tickets/rnt260517001"}],"http://schema.org/price":[{"@value":"25"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_le-bibliothecaire#Offer_1","http://schema.org/name":[{"@language":"fr","@value":"Tarif membre - Les membres obtiennent 10% de rabais sur le prix régulier des billets."}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"22.5"}]},{"@id":"http://kg.footlight.io/resource/ptitbonheur-org_le-bibliothecaire#Offer_2","http://schema.org/name":[{"@language":"fr","@value":"Tarif enfant - 5 à 12 ans"}],"@type":["http://schema.org/Offer"],"http://schema.org/price":[{"@value":"15"}]}]
\ No newline at end of file
diff --git a/data/migration_fixtures/distillator_phase1_export/fetch/09e5d2f82cf6330cd1d599969d948d8f3200002e.json b/data/migration_fixtures/distillator_phase1_export/fetch/09e5d2f82cf6330cd1d599969d948d8f3200002e.json
new file mode 100644
index 00000000..c525b993
--- /dev/null
+++ b/data/migration_fixtures/distillator_phase1_export/fetch/09e5d2f82cf6330cd1d599969d948d8f3200002e.json
@@ -0,0 +1,30 @@
+{
+ "url": "https://phase1.example/missing-event",
+ "recorded_at": "2026-04-27T00:00:00Z",
+ "response": {
+ "status": "abort",
+ "body": [
+ "abort_update",
+ {
+ "error": "Not Found",
+ "error_type": "http_404",
+ "source": "wringer"
+ }
+ ],
+ "headers": {
+ "content_type": "text/html"
+ },
+ "final_url": "https://phase1.example/missing-event",
+ "redirect_chain": [],
+ "wringer": {
+ "error_type": "http_404",
+ "http_code": 404,
+ "received_404": true,
+ "system_error": false,
+ "unreachable": false,
+ "signals": {},
+ "hints": []
+ },
+ "duration_ms": 9.0
+ }
+}
diff --git a/data/migration_fixtures/distillator_phase1_export/fetch/94d75c291d0f741cd4dd4da98ffafdcf8e61479e.json b/data/migration_fixtures/distillator_phase1_export/fetch/94d75c291d0f741cd4dd4da98ffafdcf8e61479e.json
new file mode 100644
index 00000000..8d499646
--- /dev/null
+++ b/data/migration_fixtures/distillator_phase1_export/fetch/94d75c291d0f741cd4dd4da98ffafdcf8e61479e.json
@@ -0,0 +1,21 @@
+{
+ "url": "https://phase1.example/jsonld-event",
+ "recorded_at": "2026-04-27T00:00:00Z",
+ "response": {
+ "status": "ok",
+ "body": "{\"@context\":\"https://schema.org\",\"@type\":\"Event\",\"name\":\"JSON-LD Fixture Event\"}",
+ "headers": {
+ "content_type": "application/ld+json"
+ },
+ "final_url": "https://phase1.example/jsonld-event",
+ "redirect_chain": [],
+ "wringer": {
+ "signals": {
+ "network_status": "ok",
+ "content_type": "json"
+ },
+ "hints": []
+ },
+ "duration_ms": 11.0
+ }
+}
diff --git a/data/migration_fixtures/distillator_phase1_export/fetch/bb20b5f1f265716ff6451dc9fb316b4fd309777d.json b/data/migration_fixtures/distillator_phase1_export/fetch/bb20b5f1f265716ff6451dc9fb316b4fd309777d.json
new file mode 100644
index 00000000..ac923295
--- /dev/null
+++ b/data/migration_fixtures/distillator_phase1_export/fetch/bb20b5f1f265716ff6451dc9fb316b4fd309777d.json
@@ -0,0 +1,21 @@
+{
+ "url": "https://phase1.example/simple-event",
+ "recorded_at": "2026-04-27T00:00:00Z",
+ "response": {
+ "status": "ok",
+ "body": "
Simple Fixture Event ",
+ "headers": {
+ "content_type": "text/html"
+ },
+ "final_url": "https://phase1.example/simple-event",
+ "redirect_chain": [],
+ "wringer": {
+ "signals": {
+ "network_status": "ok",
+ "content_type": "html"
+ },
+ "hints": []
+ },
+ "duration_ms": 10.0
+ }
+}
diff --git a/data/migration_fixtures/distillator_phase1_export/fetch/c4426f1663c35f2e73d3d9f41841f6db04805baa.json b/data/migration_fixtures/distillator_phase1_export/fetch/c4426f1663c35f2e73d3d9f41841f6db04805baa.json
new file mode 100644
index 00000000..9ff4ca6f
--- /dev/null
+++ b/data/migration_fixtures/distillator_phase1_export/fetch/c4426f1663c35f2e73d3d9f41841f6db04805baa.json
@@ -0,0 +1,26 @@
+{
+ "url": "https://phase1.example/redirected-event",
+ "recorded_at": "2026-04-27T00:00:00Z",
+ "response": {
+ "status": "ok",
+ "body": "
Redirect Fixture Event ",
+ "headers": {
+ "content_type": "text/html"
+ },
+ "final_url": "https://phase1.example/final-event",
+ "redirect_chain": [
+ "https://phase1.example/redirected-event",
+ "https://phase1.example/final-event"
+ ],
+ "wringer": {
+ "signals": {
+ "network_status": "ok",
+ "content_type": "html"
+ },
+ "hints": [
+ "redirected"
+ ]
+ },
+ "duration_ms": 12.0
+ }
+}
diff --git a/data/migration_fixtures/distillator_phase1_export/website.json b/data/migration_fixtures/distillator_phase1_export/website.json
new file mode 100644
index 00000000..937a8e51
--- /dev/null
+++ b/data/migration_fixtures/distillator_phase1_export/website.json
@@ -0,0 +1,37 @@
+{
+ "site": "distillator_phase1_export",
+ "seedurl": "distillator-phase1-export",
+ "name": "Distillator Phase 1 Export Fixture",
+ "pages": [
+ {
+ "case": "simple_event_page",
+ "url": "https://phase1.example/simple-event",
+ "rdf_uri": "footlight:phase1-simple-event",
+ "title": "Simple Fixture Event",
+ "start_date": "2026-05-01T20:00:00Z",
+ "virtual_location": "https://phase1.example/simple-event"
+ },
+ {
+ "case": "jsonld_event_page",
+ "url": "https://phase1.example/jsonld-event",
+ "rdf_uri": "footlight:phase1-jsonld-event",
+ "title": "JSON-LD Fixture Event",
+ "start_date": "2026-05-02T20:00:00Z",
+ "virtual_location": "https://phase1.example/jsonld-event"
+ },
+ {
+ "case": "redirect_final_url",
+ "url": "https://phase1.example/redirected-event",
+ "rdf_uri": "footlight:phase1-redirect-event",
+ "title": "Redirect Fixture Event",
+ "start_date": "2026-05-03T20:00:00Z",
+ "virtual_location": "https://phase1.example/final-event"
+ },
+ {
+ "case": "stored_404_failure",
+ "url": "https://phase1.example/missing-event",
+ "rdf_uri": "footlight:phase1-missing-event",
+ "failure": true
+ }
+ ]
+}
diff --git a/db/migrate/20260427000000_create_distillator_fetch_caches.rb b/db/migrate/20260427000000_create_distillator_fetch_caches.rb
new file mode 100644
index 00000000..24964520
--- /dev/null
+++ b/db/migrate/20260427000000_create_distillator_fetch_caches.rb
@@ -0,0 +1,24 @@
+class CreateDistillatorFetchCaches < ActiveRecord::Migration[8.0]
+ def change
+ create_table :distillator_fetch_caches do |t|
+ t.string :uri_key, null: false
+ t.string :normalized_url
+ t.text :html
+ t.text :body
+ t.string :name
+ t.jsonb :json_ld
+ t.datetime :scrape_date
+ t.datetime :successful_refresh
+ t.integer :http_response_code
+ t.jsonb :headers, null: false, default: {}
+ t.jsonb :signals, null: false, default: {}
+ t.jsonb :hints, null: false, default: []
+ t.string :final_url
+ t.jsonb :redirect_chain, null: false, default: []
+
+ t.timestamps
+ end
+
+ add_index :distillator_fetch_caches, :uri_key, unique: true
+ end
+end
diff --git a/db/migrate/20260502000000_add_health_fields_to_distillator_fetch_caches.rb b/db/migrate/20260502000000_add_health_fields_to_distillator_fetch_caches.rb
new file mode 100644
index 00000000..3782ef10
--- /dev/null
+++ b/db/migrate/20260502000000_add_health_fields_to_distillator_fetch_caches.rb
@@ -0,0 +1,28 @@
+class AddHealthFieldsToDistillatorFetchCaches < ActiveRecord::Migration[8.0]
+ def change
+ change_table :distillator_fetch_caches, bulk: true do |t|
+ t.string :health_status
+ t.string :health_severity
+ t.jsonb :health_reasons, null: false, default: []
+ t.integer :html_bytes, null: false, default: 0
+ t.integer :body_bytes, null: false, default: 0
+ t.boolean :redirected, null: false, default: false
+ t.string :network_status
+ t.string :content_type
+ t.jsonb :hint_keys, null: false, default: []
+ end
+
+ add_index :distillator_fetch_caches, :health_status
+ add_index :distillator_fetch_caches, :health_severity
+ add_index :distillator_fetch_caches, :http_response_code
+ add_index :distillator_fetch_caches, :network_status
+ add_index :distillator_fetch_caches, :content_type
+ add_index :distillator_fetch_caches, :redirected
+ add_index :distillator_fetch_caches, :successful_refresh
+ add_index :distillator_fetch_caches, :scrape_date
+ add_index :distillator_fetch_caches, :updated_at
+ add_index :distillator_fetch_caches, :signals, using: :gin
+ add_index :distillator_fetch_caches, :hints, using: :gin
+ add_index :distillator_fetch_caches, :redirect_chain, using: :gin
+ end
+end
diff --git a/db/migrate/20260502120000_add_distillator_mode_to_websites.rb b/db/migrate/20260502120000_add_distillator_mode_to_websites.rb
new file mode 100644
index 00000000..07cdf204
--- /dev/null
+++ b/db/migrate/20260502120000_add_distillator_mode_to_websites.rb
@@ -0,0 +1,5 @@
+class AddDistillatorModeToWebsites < ActiveRecord::Migration[8.0]
+ def change
+ add_column :websites, :distillator_mode, :string, null: false, default: "legacy"
+ end
+end
diff --git a/db/migrate/20260503000000_add_issue_fields_to_distillator_fetch_caches.rb b/db/migrate/20260503000000_add_issue_fields_to_distillator_fetch_caches.rb
new file mode 100644
index 00000000..3000844e
--- /dev/null
+++ b/db/migrate/20260503000000_add_issue_fields_to_distillator_fetch_caches.rb
@@ -0,0 +1,21 @@
+class AddIssueFieldsToDistillatorFetchCaches < ActiveRecord::Migration[8.0]
+ def change
+ change_table :distillator_fetch_caches, bulk: true do |t|
+ t.string :primary_issue_key
+ t.string :primary_issue_error_code
+ t.string :primary_issue_label
+ t.string :primary_issue_severity
+ t.string :primary_issue_category
+ t.jsonb :issue_keys, null: false, default: []
+ t.jsonb :issue_hints, null: false, default: []
+ t.boolean :delete_candidate, null: false, default: false
+ end
+
+ add_index :distillator_fetch_caches, :primary_issue_key
+ add_index :distillator_fetch_caches, :primary_issue_severity
+ add_index :distillator_fetch_caches, :primary_issue_category
+ add_index :distillator_fetch_caches, :delete_candidate
+ add_index :distillator_fetch_caches, :issue_keys, using: :gin
+ add_index :distillator_fetch_caches, :issue_hints, using: :gin
+ end
+end
diff --git a/db/migrate/20260519010000_create_distillator_transition_evidence.rb b/db/migrate/20260519010000_create_distillator_transition_evidence.rb
new file mode 100644
index 00000000..f9ba74b0
--- /dev/null
+++ b/db/migrate/20260519010000_create_distillator_transition_evidence.rb
@@ -0,0 +1,34 @@
+class CreateDistillatorTransitionEvidence < ActiveRecord::Migration[8.0]
+ def change
+ create_table :distillator_transition_evidence do |t|
+ t.references :website, null: false, foreign_key: true
+ t.string :url, null: false
+ t.string :cohort_key
+ t.string :check_kind, null: false
+ t.string :status, null: false
+ t.integer :statement_delta
+ t.boolean :statement_count_delta_acceptable
+ t.boolean :export_diff_checked
+ t.string :export_diff_status
+ t.boolean :export_diff_accepted
+ t.integer :rdf_added_count
+ t.integer :rdf_removed_count
+ t.string :wringer_uri_key
+ t.string :distillator_uri_key
+ t.integer :wringer_http_code
+ t.integer :distillator_http_code
+ t.string :primary_issue_key
+ t.jsonb :details, default: {}, null: false
+ t.datetime :checked_at, null: false
+
+ t.timestamps
+ end
+
+ add_index :distillator_transition_evidence, :cohort_key
+ add_index :distillator_transition_evidence, :check_kind
+ add_index :distillator_transition_evidence, :status
+ add_index :distillator_transition_evidence, :checked_at
+ add_index :distillator_transition_evidence, [:website_id, :check_kind, :checked_at], name: "index_transition_evidence_on_site_kind_checked_at"
+ add_index :distillator_transition_evidence, [:website_id, :url, :check_kind, :checked_at], name: "index_transition_evidence_on_site_url_kind_checked_at"
+ end
+end
diff --git a/db/migrate/20260519011000_create_distillator_rollout_events.rb b/db/migrate/20260519011000_create_distillator_rollout_events.rb
new file mode 100644
index 00000000..b589d246
--- /dev/null
+++ b/db/migrate/20260519011000_create_distillator_rollout_events.rb
@@ -0,0 +1,17 @@
+class CreateDistillatorRolloutEvents < ActiveRecord::Migration[8.0]
+ def change
+ create_table :distillator_rollout_events do |t|
+ t.references :website, null: false, foreign_key: true
+ t.string :from_mode
+ t.string :to_mode, null: false
+ t.string :actor
+ t.string :reason
+ t.jsonb :readiness_snapshot, default: {}, null: false
+
+ t.timestamps
+ end
+
+ add_index :distillator_rollout_events, :to_mode
+ add_index :distillator_rollout_events, [:website_id, :created_at]
+ end
+end
diff --git a/db/migrate/20260519012000_add_distillator_reporting_indexes.rb b/db/migrate/20260519012000_add_distillator_reporting_indexes.rb
new file mode 100644
index 00000000..273503aa
--- /dev/null
+++ b/db/migrate/20260519012000_add_distillator_reporting_indexes.rb
@@ -0,0 +1,6 @@
+class AddDistillatorReportingIndexes < ActiveRecord::Migration[8.0]
+ def change
+ add_index :websites, :distillator_mode unless index_exists?(:websites, :distillator_mode)
+ add_index :distillator_fetch_caches, :normalized_url unless index_exists?(:distillator_fetch_caches, :normalized_url)
+ end
+end
diff --git a/db/migrate/20260525120000_add_transition_check_requested_at_to_websites.rb b/db/migrate/20260525120000_add_transition_check_requested_at_to_websites.rb
new file mode 100644
index 00000000..76df13a2
--- /dev/null
+++ b/db/migrate/20260525120000_add_transition_check_requested_at_to_websites.rb
@@ -0,0 +1,6 @@
+class AddTransitionCheckRequestedAtToWebsites < ActiveRecord::Migration[8.0]
+ def change
+ add_column :websites, :transition_check_requested_at, :datetime
+ add_index :websites, :transition_check_requested_at
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index f6dabf89..dd20da07 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,12 +10,113 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[8.0].define(version: 2026_03_07_214222) do
+ActiveRecord::Schema[8.0].define(version: 2026_05_25_120000) do
create_schema "heroku_ext"
# These are extensions that must be enabled in order to support this database
- enable_extension "heroku_ext.pg_stat_statements"
enable_extension "pg_catalog.plpgsql"
+ enable_extension "pg_stat_statements"
+
+ create_table "distillator_fetch_caches", force: :cascade do |t|
+ t.string "uri_key", null: false
+ t.string "normalized_url"
+ t.text "html"
+ t.text "body"
+ t.string "name"
+ t.jsonb "json_ld"
+ t.datetime "scrape_date"
+ t.datetime "successful_refresh"
+ t.integer "http_response_code"
+ t.jsonb "headers", default: {}, null: false
+ t.jsonb "signals", default: {}, null: false
+ t.jsonb "hints", default: [], null: false
+ t.string "final_url"
+ t.jsonb "redirect_chain", default: [], null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.string "health_status"
+ t.string "health_severity"
+ t.jsonb "health_reasons", default: [], null: false
+ t.integer "html_bytes", default: 0, null: false
+ t.integer "body_bytes", default: 0, null: false
+ t.boolean "redirected", default: false, null: false
+ t.string "network_status"
+ t.string "content_type"
+ t.jsonb "hint_keys", default: [], null: false
+ t.string "primary_issue_key"
+ t.string "primary_issue_error_code"
+ t.string "primary_issue_label"
+ t.string "primary_issue_severity"
+ t.string "primary_issue_category"
+ t.jsonb "issue_keys", default: [], null: false
+ t.jsonb "issue_hints", default: [], null: false
+ t.boolean "delete_candidate", default: false, null: false
+ t.index ["content_type"], name: "index_distillator_fetch_caches_on_content_type"
+ t.index ["delete_candidate"], name: "index_distillator_fetch_caches_on_delete_candidate"
+ t.index ["health_severity"], name: "index_distillator_fetch_caches_on_health_severity"
+ t.index ["health_status"], name: "index_distillator_fetch_caches_on_health_status"
+ t.index ["hints"], name: "index_distillator_fetch_caches_on_hints", using: :gin
+ t.index ["http_response_code"], name: "index_distillator_fetch_caches_on_http_response_code"
+ t.index ["issue_hints"], name: "index_distillator_fetch_caches_on_issue_hints", using: :gin
+ t.index ["issue_keys"], name: "index_distillator_fetch_caches_on_issue_keys", using: :gin
+ t.index ["network_status"], name: "index_distillator_fetch_caches_on_network_status"
+ t.index ["normalized_url"], name: "index_distillator_fetch_caches_on_normalized_url"
+ t.index ["primary_issue_category"], name: "index_distillator_fetch_caches_on_primary_issue_category"
+ t.index ["primary_issue_key"], name: "index_distillator_fetch_caches_on_primary_issue_key"
+ t.index ["primary_issue_severity"], name: "index_distillator_fetch_caches_on_primary_issue_severity"
+ t.index ["redirect_chain"], name: "index_distillator_fetch_caches_on_redirect_chain", using: :gin
+ t.index ["redirected"], name: "index_distillator_fetch_caches_on_redirected"
+ t.index ["scrape_date"], name: "index_distillator_fetch_caches_on_scrape_date"
+ t.index ["signals"], name: "index_distillator_fetch_caches_on_signals", using: :gin
+ t.index ["successful_refresh"], name: "index_distillator_fetch_caches_on_successful_refresh"
+ t.index ["updated_at"], name: "index_distillator_fetch_caches_on_updated_at"
+ t.index ["uri_key"], name: "index_distillator_fetch_caches_on_uri_key", unique: true
+ end
+
+ create_table "distillator_rollout_events", force: :cascade do |t|
+ t.bigint "website_id", null: false
+ t.string "from_mode"
+ t.string "to_mode", null: false
+ t.string "actor"
+ t.string "reason"
+ t.jsonb "readiness_snapshot", default: {}, null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["to_mode"], name: "index_distillator_rollout_events_on_to_mode"
+ t.index ["website_id", "created_at"], name: "index_distillator_rollout_events_on_website_id_and_created_at"
+ t.index ["website_id"], name: "index_distillator_rollout_events_on_website_id"
+ end
+
+ create_table "distillator_transition_evidence", force: :cascade do |t|
+ t.bigint "website_id", null: false
+ t.string "url", null: false
+ t.string "cohort_key"
+ t.string "check_kind", null: false
+ t.string "status", null: false
+ t.integer "statement_delta"
+ t.boolean "statement_count_delta_acceptable"
+ t.boolean "export_diff_checked"
+ t.string "export_diff_status"
+ t.boolean "export_diff_accepted"
+ t.integer "rdf_added_count"
+ t.integer "rdf_removed_count"
+ t.string "wringer_uri_key"
+ t.string "distillator_uri_key"
+ t.integer "wringer_http_code"
+ t.integer "distillator_http_code"
+ t.string "primary_issue_key"
+ t.jsonb "details", default: {}, null: false
+ t.datetime "checked_at", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["check_kind"], name: "index_distillator_transition_evidence_on_check_kind"
+ t.index ["checked_at"], name: "index_distillator_transition_evidence_on_checked_at"
+ t.index ["cohort_key"], name: "index_distillator_transition_evidence_on_cohort_key"
+ t.index ["status"], name: "index_distillator_transition_evidence_on_status"
+ t.index ["website_id", "check_kind", "checked_at"], name: "index_transition_evidence_on_site_kind_checked_at"
+ t.index ["website_id", "url", "check_kind", "checked_at"], name: "index_transition_evidence_on_site_url_kind_checked_at"
+ t.index ["website_id"], name: "index_distillator_transition_evidence_on_website_id"
+ end
create_table "jsonld_outputs", force: :cascade do |t|
t.string "name"
@@ -117,9 +218,17 @@
t.integer "schedule_every_days"
t.datetime "last_refresh", precision: nil
t.time "schedule_time"
+ t.string "province"
+ t.string "city"
t.boolean "monitorable"
+ t.string "distillator_mode", default: "legacy", null: false
+ t.datetime "transition_check_requested_at"
+ t.index ["distillator_mode"], name: "index_websites_on_distillator_mode"
+ t.index ["transition_check_requested_at"], name: "index_websites_on_transition_check_requested_at"
end
+ add_foreign_key "distillator_rollout_events", "websites"
+ add_foreign_key "distillator_transition_evidence", "websites"
add_foreign_key "properties", "rdfs_classes"
add_foreign_key "search_exceptions", "rdfs_classes"
add_foreign_key "sources", "properties"
diff --git a/docs/distillator/cache_compatibility_contract.md b/docs/distillator/cache_compatibility_contract.md
new file mode 100644
index 00000000..ca2cd1dd
--- /dev/null
+++ b/docs/distillator/cache_compatibility_contract.md
@@ -0,0 +1,125 @@
+# Distillator Cache Compatibility Contract
+
+This document defines the legacy Wringer cache semantics that Distillator must preserve during migration work.
+
+Compatibility references:
+- [Wringer Compatibility Matrix](/home/educa/rails-upgrade/githubed/footlight-condenser/docs/distillator/wringer_compatibility_matrix.md)
+- [Phase 1 Wringer Readiness](/home/educa/rails-upgrade/githubed/footlight-condenser/docs/distillator_phase1_wringer_readiness.md)
+- Legacy controller contract: [wringer/app/controllers/websites_controller.rb](/home/educa/rails-upgrade/githubed/footlight-condenser/wringer/app/controllers/websites_controller.rb)
+- Current cache store: [app/services/distillator/fetch_cache_store.rb](/home/educa/rails-upgrade/githubed/footlight-condenser/app/services/distillator/fetch_cache_store.rb)
+- Current compatibility controller: [app/controllers/websites_controller.rb](/home/educa/rails-upgrade/githubed/footlight-condenser/app/controllers/websites_controller.rb)
+
+## Compatibility Rules
+
+- `scrape_date` = last attempted scrape.
+- `successful_refresh` = last successful 2xx content refresh.
+- Non-2xx responses must preserve existing `html`.
+- Raw, JSON, and HTML formats are compatibility outputs, not new APIs.
+- Distillator-specific refactors must not invent new cache semantics that differ from Wringer unless a later migration document explicitly approves that change.
+- Cache inspection sorting defaults to `updated_at desc` and remains read-only.
+- JSON inspection endpoints continue to return array payloads plus pagination headers.
+- Health/status UI is diagnostic only; it must not mutate cache state.
+- Internal/native statement refresh without `force_scrape` or `force_scrape_every_hrs` remains transient and must not write Distillator fetch cache rows.
+- Crawl URI refresh with `force_scrape_every_hrs` routes eligible native HTTP GET fetches through `Distillator::FetchCacheStore`.
+
+## Contract Matrix
+
+| Field or parameter | Legacy Wringer behavior | Current Distillator implementation | Current tests | Missing tests | UI visibility |
+|---|---|---|---|---|---|
+| `uri` / `uri_key` | Wringer stores and looks up cache rows by escaped URI key. Missing or invalid `uri` returns no content rather than raising. | `Distillator::WringerUrlKey` builds the normalized URL and escaped `uri_key`; `Distillator::FetchCacheStore` persists by `uri_key`; `WebsitesController#wring` returns `no_content` for invalid input. | `test/services/distillator/wringer_url_key_test.rb`, `test/controllers/wringer_compat_controller_test.rb`, `test/services/distillator/fetch_cache_store_test.rb`, `test/services/distillator/cache_refresh_preview_test.rb` | No missing test identified for current cache-key compatibility behavior. | User-visible in `/websites/wring`, `/websites.json?term=...`, and the read-only JSON/raw/wring_json/preview cache endpoints. |
+| `html` | Cached HTML is the compatibility source of truth used by Wringer raw output and JSON output. On successful 2xx refresh, it is replaced. On non-2xx refresh, last known good HTML is preserved. | `Distillator::FetchCacheStore#write_result` writes `html` only in the 2xx branch and preserves prior HTML on non-2xx results. | `test/services/distillator/fetch_cache_store_test.rb`, `test/controllers/wringer_compat_controller_test.rb`, `test/controllers/distillator/cache_controller_test.rb` | No missing test identified for 2xx/404/500 preservation semantics in the current compatibility path. | Directly visible in `format=raw`, `format=json`, and the read-only raw/wring_json cache endpoints. |
+| `json_ld` | Stored cache metadata field used by Condenser lookup surfaces; not part of the `/websites/wring` compatibility payload. | Persisted on `Distillator::FetchCache`; surfaced by `/websites.json?term=...`. | `test/controllers/wringer_compat_controller_test.rb` | No new cache-contract test was added here; refresh-preservation coverage for `json_ld` remains unproven. | Visible in `/websites.json?term=...`; not yet exposed by the new read-only cache endpoints. |
+| `name` | Cache row name is updated from refreshed content metadata when available. Failed refreshes may still update metadata without replacing HTML. | `Distillator::FetchCacheStore#write_result` assigns `name` from normalized fetch results before the 2xx-only HTML overwrite branch. | `test/services/distillator/fetch_cache_store_test.rb` | No missing test identified for current 2xx and non-2xx name-update behavior. | Visible in lookup results and the serialized read-only cache JSON endpoints. |
+| `scrape_date` | Last attempted scrape time, whether the fetch succeeded or failed. | `Distillator::FetchCacheStore#write_result` sets `scrape_date` on every refresh attempt. | `test/services/distillator/fetch_cache_store_test.rb`, `test/services/distillator/cache_refresh_preview_test.rb`, `test/controllers/distillator/cache_controller_test.rb`, `docs/distillator_phase1_wringer_readiness.md` | No missing test identified for current cache-store and preview semantics. | Important for UI messaging, refresh previews, and the read-only JSON cache endpoints. |
+| `successful_refresh` | Last successful 2xx content refresh time. It only changes when fresh content is successfully written. | `Distillator::FetchCacheStore#write_result` updates `successful_refresh` only in the 2xx branch. | `test/services/distillator/fetch_cache_store_test.rb`, `test/services/distillator/cache_refresh_preview_test.rb`, `test/controllers/distillator/cache_controller_test.rb`, `docs/distillator_phase1_wringer_readiness.md` | No missing test identified for current 2xx-update and 404/500 preservation behavior. | Important for UI confidence indicators and the read-only JSON cache endpoints. |
+| `http_response_code` | Stores the latest HTTP result code. It is updated on both successful and non-2xx refreshes so callers can see the last attempt outcome while preserved HTML remains available. | `Distillator::FetchCacheStore#write_result` assigns `http_response_code` before the 2xx-only content branch; `WebsitesController#wring` returns it as `http_code` in JSON. | `test/services/distillator/fetch_cache_store_test.rb`, `test/controllers/wringer_compat_controller_test.rb`, `test/controllers/distillator/cache_controller_test.rb` | No missing test identified for current success, non-2xx, and failure-shaped JSON compatibility output. | Visible in `format=json` and the read-only `wring_json`/serialized cache endpoints. |
+| `signals` | Wringer-compatible fetch signals capture network/content state and are preserved in cache and JSON output. | `Distillator::FetchCacheStore#normalize_fetch_cache_signals` writes normalized `signals`; controller JSON returns them unchanged. | `test/services/distillator/fetch_cache_store_test.rb`, `test/controllers/wringer_compat_controller_test.rb`, `test/services/distillator/fetch_service_test.rb`, `test/controllers/distillator/cache_controller_test.rb` | No missing test identified for current cache-store and JSON compatibility output. | Visible in `format=json` and the read-only `wring_json`/serialized cache endpoints. |
+| `hints` | Wringer-compatible hints preserve fetch diagnoses such as redirect/content observations and empty-body warnings. | `Distillator::FetchCacheStore#normalize_fetch_cache_hints` writes normalized `hints`; controller JSON returns them unchanged. | `test/services/distillator/fetch_cache_store_test.rb`, `test/controllers/wringer_compat_controller_test.rb`, `test/services/distillator/fetch_service_test.rb`, `test/controllers/distillator/cache_controller_test.rb` | No missing test identified for current cache-store and JSON compatibility output. | Visible in `format=json` and the read-only `wring_json`/serialized cache endpoints. |
+| `final_url` | Final resolved URL after redirects is stored with the cache row and returned to JSON callers. | `Distillator::FetchCacheStore#write_result` persists `final_url`; `WebsitesController#wring` returns it in JSON. | `test/services/distillator/fetch_cache_store_test.rb`, `test/controllers/wringer_compat_controller_test.rb`, `test/services/distillator/native_fetch_test.rb`, `test/controllers/distillator/cache_controller_test.rb` | No missing test identified for current cache-store and JSON compatibility output. | Visible in `format=json` and the read-only `wring_json`/serialized cache endpoints. |
+| `redirect_chain` | Ordered list of visited URLs; defaults to `[]` rather than `nil`; stored with cache and returned in JSON. | `Distillator::FetchCacheStore#write_result` normalizes `redirect_chain` to an array; controller JSON returns it unchanged. | `test/services/distillator/fetch_cache_store_test.rb`, `test/controllers/wringer_compat_controller_test.rb`, `test/services/distillator/native_fetch_test.rb`, `test/controllers/distillator/cache_controller_test.rb` | No missing test identified for current normalization and JSON compatibility output. | Visible in `format=json` and the read-only `wring_json`/serialized cache endpoints. |
+| `format=raw` | Compatibility output that renders cached `html` only, with frame protections removed. It is not a new API contract. | `WebsitesController#wring` renders `cache.html` and clears `X-Frame-Options`; `Distillator::CacheController#raw` provides read-only cache inspection output with the same frame-header removal. | `test/controllers/wringer_compat_controller_test.rb`, `test/controllers/distillator/cache_controller_test.rb` | No missing test identified for current raw compatibility output. | Directly user-visible in browser/manual verification flows, the read-only raw cache endpoint, and the implemented `raw_view` HTML inspection page. |
+| `format=html` | Compatibility output that redirects to the websites UI with the legacy success notice. It is not a new API contract. | `WebsitesController#wring` redirects to `websites_path` with `Website was successfully wrung.` | `test/controllers/wringer_compat_controller_test.rb` | No missing test identified for current redirect behavior after cache hit and forced refresh. | Directly user-visible in existing UI flows; no new cache mutation UI has been added. |
+| `format=json` | Compatibility output that returns exactly `html`, `signals`, `hints`, `final_url`, `redirect_chain`, and `http_code`. It is not a new API contract. | `WebsitesController#wring` serializes the cached row/result into the legacy JSON shape; `Distillator::CacheController#wring_json` provides the same shape from cache only. | `test/controllers/wringer_compat_controller_test.rb`, `test/controllers/distillator/cache_controller_test.rb` | No missing test identified for current JSON compatibility shape, including empty redirect chains and failure-shaped payloads. | Directly user-visible in current consumers, the read-only `wring_json` cache endpoint, and the implemented `wring_json_view` HTML inspection page. |
+| `force_scrape` | Forces a fresh attempt even when cache exists and scrape_date is recent. | `Distillator::FetchCacheStore#should_refresh?` returns true when `force_scrape` is truthy. | `test/services/distillator/fetch_cache_store_test.rb`, `test/controllers/wringer_compat_controller_test.rb`, `test/services/distillator/cache_refresh_preview_test.rb`, `test/controllers/distillator/cache_controller_test.rb` | No missing test identified for current cache-store, controller plumbing, and preview semantics. | Important for manual refresh controls and preview endpoints, but should remain a compatibility flag. |
+| `force_scrape_every_hrs` | Forces refresh only when cached `scrape_date` is older than the threshold; `0` means always refresh; blank means do not force based on age. | `Distillator::FetchCacheStore#should_refresh?` compares `scrape_date` against `now - hours`. | `test/services/distillator/fetch_cache_store_test.rb`, `test/controllers/wringer_compat_controller_test.rb`, `test/services/distillator/cache_refresh_preview_test.rb`, `test/controllers/distillator/cache_controller_test.rb` | No missing test identified for current threshold, controller plumbing, and preview semantics. | Important for preview/explanation endpoints, but semantics must stay Wringer-compatible. |
+| `include_fragment` | Fragment is ignored by default and only included in the cache key when explicitly requested. | `Distillator::WringerUrlKey` strips fragments unless `include_fragment` is truthy; `FetchCacheStore` uses the generated key. | `test/services/distillator/wringer_url_key_test.rb`, `test/controllers/wringer_compat_controller_test.rb`, `test/services/distillator/cache_refresh_preview_test.rb` | No missing test identified for current controller/store fragment-key compatibility behavior. | Important for cache inspection clarity and duplicate-row explanation in the JSON preview/read-only endpoints. |
+| `absolute_src` | On successful 2xx refresh only, Wringer rewrites relative `src` and `href` to absolute URLs for compatibility display. Failed refreshes must not rewrite preserved old HTML. | `Distillator::FetchCacheStore#write_fetch_result` calls `Distillator::HtmlAbsolutizer.call` only in the 2xx branch when `absolute_src` is truthy. | `test/services/distillator/html_absolutizer_test.rb`, `test/services/distillator/fetch_cache_store_test.rb`, `test/controllers/wringer_compat_controller_test.rb` | No missing test identified for current controller plumbing and cache-store success-only rewrite behavior. | User-visible in raw/html verification surfaces and in cached HTML exposed by raw endpoints. |
+| `json_post` | Compatibility mode fetches through POST, preserves JSON/content hints, and must still preserve last known good HTML on failed refreshes. | `Distillator::NativeFetch` now performs native POST fetches with JSON-aware metadata; `Distillator::FetchService` marks eligible POST requests as `native_http_post`; `Distillator::FetchCacheStore` persists the result through the same cache contract as GET fetches. | `test/services/distillator/native_fetch_test.rb`, `test/services/distillator/fetch_service_test.rb`, `test/services/distillator/fetch_cache_store_test.rb`, `test/controllers/wringer_compat_controller_test.rb`, `test/services/dsl_algorithm_runner_test.rb` | No missing test identified for current native POST fetch/cache semantics. | UI and operators can now treat this as a first-class Distillator cache path. |
+| `use_phantomjs` | Compatibility mode routes fetch through PhantomJS Cloud for rendered content. Distillator now preserves this through an explicit rendered-fetch abstraction, while still using the legacy PhantomJS service rather than a modern renderer. | `Distillator::RenderedFetch` selects `legacy_phantomjs` or `disabled`; `Distillator::Renderers::LegacyPhantomjsRenderer` owns PhantomJS URL generation, missing-key fallback, and renderer metadata; `Distillator::NativeFetch` dispatches rendered requests there. | `test/services/distillator/rendered_fetch_test.rb`, `test/services/distillator/renderers/legacy_phantomjs_renderer_test.rb`, `test/services/distillator/fetch_cache_store_test.rb`, `test/services/distillator/fetch_service_test.rb`, `test/controllers/wringer_compat_controller_test.rb` | Modern renderer replacement remains deferred, but current compatibility behavior is directly covered. | UI should treat this as an explicit compatibility renderer strategy, not as a modern-renderer migration. |
+| `iframe` suffix behavior | When the encoded `uri_key` ends with `iframe`, Wringer forces rendered JSON mode and extracts the first child frame’s HTML, but only when the fetch result is successful enough to use returned content. | `WebsitesController#wring` forces `use_phantomjs` for `iframe` suffixes; `Distillator::NativeFetch` flags iframe requests; `Distillator::Renderers::LegacyPhantomjsRenderer` extracts the first child frame HTML from PhantomJS JSON. | `test/services/distillator/renderers/legacy_phantomjs_renderer_test.rb`, `test/services/distillator/fetch_cache_store_test.rb`, `test/controllers/wringer_compat_controller_test.rb` | No missing test identified for current rendered-cache and controller behavior. | Mostly hidden from UI, but important for debug/read-only verification tools because it changes stored HTML semantics. |
+
+## Notes For Follow-up Work
+
+- This contract is intentionally narrower than a future Distillator-native cache model. It documents what must remain true while Wringer compatibility is still the migration target.
+- Any UI work that exposes cache state should use these semantics first:
+ - `scrape_date` means last attempt.
+ - `successful_refresh` means last successful 2xx write.
+ - preserved `html` can coexist with a failed latest `http_response_code`.
+- The completed cache inspection work now includes both endpoint and read-only HTML inspection pages:
+ - `GET /distillator/cache`
+ - `GET /distillator/cache/:id`
+ - `GET /distillator/cache/:id/raw_view`
+ - `GET /distillator/cache/:id/wring_json_view`
+ - `GET /distillator/cache/preview`
+ - `GET /distillator/cache/compare?uri=...`
+ - `GET /distillator/cache.json`
+ - `GET /distillator/cache/:id.json`
+ - `GET /distillator/cache/:id/raw`
+ - `GET /distillator/cache/:id/wring_json`
+ - `GET /distillator/cache/preview.json`
+- `Distillator::CacheIndexQuery` now owns cache-index filtering, sorting, and pagination behavior:
+ - SQL-backed filters: `term`, `http_response_code`, `has_html`, `network_status`, `health`, `status_group`, `content_type`, `hint`, `redirected`, `last_attempt`, `last_success`
+ - SQL-backed sorts: `id`, `updated_at`, `scrape_date`, `successful_refresh`, `http_response_code`, `name`, `normalized_url`, `uri_key`, `html_bytes`, `body_bytes`
+- Cache index HTML URLs canonicalize blank and invalid params to stable shareable URLs. JSON endpoints normalize internally but keep the existing array response shape and pagination headers.
+- The read-only cache index now follows the Condenser Websites table pattern:
+ - a single GET form wrapped around the table
+ - sortable column headers in the first `thead` row
+ - filters directly under corresponding columns in the second `thead` row
+ - like Websites, current `sort` and `direction` are preserved through hidden fields when applying filters
+ - `per_page` is only preserved in the form when it differs from the default page size
+ - compact quick-filter summary links instead of a dominant card block
+ - no refresh mutation controls on the index
+ - pagination headers: `X-Page`, `X-Per-Page`, `X-Total-Count`, `X-Total-Pages`
+- `Distillator::CacheSummary` is relation-aware and now SQL-backed for standard cards through materialized cache fields.
+- Materialized cache-health fields now live directly on `distillator_fetch_caches`:
+ - `health_status`
+ - `health_severity`
+ - `health_reasons`
+ - `html_bytes`
+ - `body_bytes`
+ - `redirected`
+ - `network_status`
+ - `content_type`
+ - `hint_keys`
+- `bin/rails distillator:cache:backfill_health` backfills those materialized fields for older cache rows without making network calls.
+- Cache health states currently exposed by the serializer and UI are:
+ - `healthy`
+ - `preserved_after_failure`
+ - `never_fetched`
+ - `network_failed`
+ - `blocked`
+ - `empty_body`
+ - `redirect_changed`
+ - `stale`
+ - `unknown`
+- Mode-aware operator links now resolve read-only cache destinations for:
+ - legacy mode
+ - internal mode
+ - shadow mode via `/distillator/cache/compare`
+ - replay mode with a warning that replay may not represent persisted live cache
+- Statement trace-step views now expose both `Open active cache` and `Open Wringer cache` links without triggering fetches, and shadow mode routes the active link to `/distillator/cache/compare`.
+- `Distillator::CacheCompare` now labels cache data sources explicitly:
+ - `legacy_source`: `remote_wringer`, `injected_lookup`, or `unavailable`
+ - `legacy_lookup_error`: populated when legacy lookup fails
+ - `distillator_source`: `local_fetch_cache`
+- The read-only cache inspector now has CSS-only operator polish for:
+ - compact quick-filter strip
+ - Websites-style table header/filter layout
+ - badge severity colors
+ - monospace cache identities and JSON blocks
+ - compare same/different highlighting
+- Read-only cache UI pages are implemented, but refresh mutation UI remains deferred.
+- Audit-event work remains deferred.
+- `json_post` now has native Distillator fetch/cache parity for POST-mode refreshes.
+- `use_phantomjs` now has an explicit Distillator renderer strategy, but a modern renderer replacement is still deferred.
+- Follow-up tasks should update this document only when behavior or proof changes, not to speculate about future native-only semantics.
diff --git a/docs/distillator/wringer_compatibility_matrix.md b/docs/distillator/wringer_compatibility_matrix.md
new file mode 100644
index 00000000..a729bcff
--- /dev/null
+++ b/docs/distillator/wringer_compatibility_matrix.md
@@ -0,0 +1,44 @@
+# Wringer Compatibility Matrix
+
+Source material:
+- Legacy Wringer parameter contract: [wringer/app/controllers/websites_controller.rb](/home/educa/rails-upgrade/githubed/footlight-condenser/wringer/app/controllers/websites_controller.rb)
+- Legacy Wringer controller tests: [wringer/test/controllers/websites_controller_test.rb](/home/educa/rails-upgrade/githubed/footlight-condenser/wringer/test/controllers/websites_controller_test.rb)
+- Current Condenser compatibility controller: [app/controllers/websites_controller.rb](/home/educa/rails-upgrade/githubed/footlight-condenser/app/controllers/websites_controller.rb)
+- Current Distillator readiness audit: [docs/distillator_phase1_wringer_readiness.md](/home/educa/rails-upgrade/githubed/footlight-condenser/docs/distillator_phase1_wringer_readiness.md)
+
+Status values:
+- `done`: current Distillator/Condenser path matches legacy behavior and has direct coverage.
+- `partial`: current path exists, but test coverage or migration scope is incomplete.
+- `missing`: legacy behavior is not implemented on the target path.
+- `legacy_fallback`: behavior currently survives through compatibility or legacy-backed code rather than a fully native path.
+- `unknown`: source behavior is known, but current migration evidence is incomplete.
+
+| Legacy parameter or behavior | Legacy behavior | Current Condenser/Distillator implementation location | Current test coverage | Missing test coverage | Migration status | Required follow-up phase |
+|---|---|---|---|---|---|---|
+| `uri` | Required input URL for `/websites/wring`; invalid or missing `uri` returns no content instead of raising. | `app/controllers/websites_controller.rb`, `app/services/distillator/wringer_url_key.rb`, `app/services/distillator/fetch_cache_store.rb` | `test/controllers/wringer_compat_controller_test.rb`, `test/services/distillator/wringer_url_key_test.rb` | No dedicated matrix-style test proving all `uri` variants from legacy comments in one place. | `done` | Phase A complete; maintain in readiness audits |
+| `include_fragment` | When truthy, include the `#fragment` in the persisted URI key; otherwise strip it. | `app/services/distillator/wringer_url_key.rb`, `app/controllers/websites_controller.rb` | `test/services/distillator/wringer_url_key_test.rb` | No controller-level `/websites/wring` test proving the param is plumbed end to end. | `partial` | Phase C hardening |
+| `use_phantomjs` | Route fetch through PhantomJS Cloud; for normal requests use `outputAsJson:false`. | `app/services/distillator/rendered_fetch.rb`, `app/services/distillator/renderers/legacy_phantomjs_renderer.rb`, `app/services/distillator/native_fetch.rb`, `app/services/distillator/fetch_cache_store.rb` | `test/services/distillator/rendered_fetch_test.rb`, `test/services/distillator/renderers/legacy_phantomjs_renderer_test.rb`, `test/services/distillator/fetch_cache_store_test.rb`, `test/services/distillator/fetch_service_test.rb`, `test/controllers/wringer_compat_controller_test.rb` | Modern renderer replacement remains deferred, but the rendered compatibility path is now directly covered. | `partial` | Deferred modern-renderer migration |
+| `force_scrape` | Force a fresh crawl even when cached content exists. | `app/services/distillator/fetch_cache_store.rb` | `test/services/distillator/fetch_cache_store_test.rb` | No controller-level compatibility test proving the param changes cache-refresh behavior through `/websites/wring`. | `partial` | Phase C cleanup |
+| `force_scrape_every_hrs` | Refresh only when the cached scrape is older than the provided threshold. | `app/services/distillator/fetch_cache_store.rb` | `test/services/distillator/fetch_cache_store_test.rb` | No controller-level compatibility test for the query param path. | `partial` | Phase C cleanup |
+| `format=raw` | Return cached HTML directly with frame protection removed. | `app/controllers/websites_controller.rb`, `app/services/distillator/fetch_cache_store.rb` | `test/controllers/wringer_compat_controller_test.rb` | No explicit legacy-vs-current body normalization fixture beyond cached HTML happy paths. | `done` | Phase A complete |
+| `format=html` | Redirect to the websites UI instead of rendering raw JSON/HTML payloads. | `app/controllers/websites_controller.rb` | `test/controllers/wringer_compat_controller_test.rb`, `test/controllers/websites_controller_test.rb` | No explicit compatibility test for a forced scrape plus `format=html`. | `done` | Phase A complete |
+| `format=json` | Return JSON with `html`, `signals`, `hints`, `final_url`, `redirect_chain`, and `http_code`. | `app/controllers/websites_controller.rb`, `app/services/distillator/fetch_cache_store.rb` | `test/controllers/wringer_compat_controller_test.rb` | No broad fixture set covering every status/error combination through the controller JSON surface. | `partial` | Phase E reporting and parity review |
+| `absolute_src` | Rewrite relative `src` and `href` attributes to absolute URLs for iframe-safe display. | `app/services/distillator/html_rewriter.rb`, `app/services/distillator/fetch_cache_store.rb`, `app/services/distillator/native_fetch.rb` | `test/services/distillator/html_rewriter_test.rb`, `test/services/distillator/fetch_cache_store_test.rb` | No controller-level `/websites/wring` test exercising `absolute_src=true`. | `partial` | Phase C complete with follow-up controller coverage |
+| `json_post` | Fetch using POST, expecting JSON-style content metadata. | `app/services/distillator/native_fetch.rb`, `app/services/distillator/fetch_cache_store.rb`, `app/services/distillator/fetch_service.rb` | `test/services/distillator/native_fetch_test.rb`, `test/services/distillator/fetch_cache_store_test.rb`, `test/services/distillator/fetch_service_test.rb`, `test/controllers/wringer_compat_controller_test.rb`, `test/services/dsl_algorithm_runner_test.rb` | No missing test identified for current native POST parity. | `done` | Phase D complete |
+| Unsafe URL rejection | Reject localhost, private-network, link-local, and non-HTTP(S) inputs before crawling. | `app/services/distillator/fetch_guard.rb`, `app/services/distillator/fetch_cache_store.rb`, `app/services/distillator/fetch_service.rb`, `app/controllers/websites_controller.rb` | `test/services/distillator/fetch_guard_test.rb`, `test/services/distillator/fetch_cache_store_test.rb`, `test/services/distillator/fetch_service_test.rb`, `wringer/test/controllers/websites_controller_test.rb` | DNS-resolution and mixed redirect-chain cases still need stronger redirect-oriented coverage in the native path. | `partial` | Phase D Task 12 |
+| Redirect metadata | Preserve `final_url`, `redirect_chain`, and redirect signals in responses and cache metadata. | `app/services/distillator/native_fetch.rb`, `app/services/distillator/fetch_cache_store.rb`, `app/services/distillator/fetch_service.rb`, `app/controllers/websites_controller.rb` | `test/services/distillator/native_fetch_test.rb`, `test/services/distillator/fetch_cache_store_test.rb`, `test/services/distillator/fetch_service_test.rb`, `test/controllers/wringer_compat_controller_test.rb`, `wringer/test/controllers/websites_controller_test.rb` | No persistent comparison/reporting evidence yet. | `partial` | Phase E Tasks 13-15 |
+| 2xx cache overwrite | On successful fetch, overwrite cached HTML/body and update `scrape_date`, `successful_refresh`, `http_response_code`, headers, signals, hints, and redirect metadata. | `app/services/distillator/fetch_cache_store.rb` | `test/services/distillator/fetch_cache_store_test.rb` | No direct controller test proving all metadata fields update through `/websites/wring`. | `partial` | Phase C verification |
+| Non-2xx cache preservation | On 404 and control-style failures, preserve last known good HTML while still updating failure metadata like `scrape_date` and `http_response_code`. | `app/services/distillator/fetch_cache_store.rb`, `app/services/distillator/native_fetch.rb` | `test/services/distillator/fetch_cache_store_test.rb`, `test/services/distillator/native_fetch_test.rb` | No 500-specific cache-preservation fixture through the cache store. | `partial` | Phase C verification |
+| Network/SSL errors | Return failure metadata and hints without crashing; legacy Wringer treated these as guarded failures. | `app/services/distillator/native_fetch.rb`, `app/services/distillator/fetch_cache_store.rb`, `app/services/distillator/fetch_service.rb` | `test/services/distillator/native_fetch_test.rb`, `test/services/distillator/fetch_cache_store_test.rb`, `test/services/distillator/fetch_response_contract_test.rb` | No controller JSON test showing network-failure payload shape at `/websites/wring`. | `partial` | Phase C/Phase E parity review |
+| ERB delimiter escaping | Escape ERB delimiters before caching HTML so cached pages cannot execute embedded ERB when rendered. | `app/services/distillator/fetch_cache_store.rb` | `test/services/distillator/fetch_cache_store_test.rb` | No controller-level raw-format test asserting escaped output for cached content. | `done` | Phase C complete |
+| Iframe extraction when URI key ends with `iframe` | Special-case PhantomJS JSON output and extract the first child frame’s HTML. | `app/controllers/websites_controller.rb`, `app/services/distillator/native_fetch.rb`, `app/services/distillator/renderers/legacy_phantomjs_renderer.rb`, `app/services/distillator/fetch_cache_store.rb` | `test/services/distillator/renderers/legacy_phantomjs_renderer_test.rb`, `test/services/distillator/fetch_cache_store_test.rb`, `test/controllers/wringer_compat_controller_test.rb` | No missing test identified for the current rendered compatibility path. | `done` | Phase D complete |
+| PhantomJS missing-key fallback | If `PHANTOMJS_API_KEY` is absent, fall back to direct URL fetch instead of failing closed. | `app/services/distillator/renderers/legacy_phantomjs_renderer.rb` | `test/services/distillator/renderers/legacy_phantomjs_renderer_test.rb` | No controller-level proof of the fallback surface, but renderer-path behavior is directly covered. | `partial` | Deferred modern-renderer migration |
+| Cache health summary performance | Summary counts and byte sorts must stay SQL-backed as `distillator_fetch_caches` grows. | `app/services/distillator/cache_health_materializer.rb`, `app/services/distillator/cache_summary.rb`, `app/services/distillator/cache_index_query.rb`, `lib/tasks/distillator_cache.rake` | `test/services/distillator/cache_health_materializer_test.rb`, `test/services/distillator/cache_summary_test.rb`, `test/services/distillator/cache_index_query_test.rb`, `test/tasks/distillator_cache_task_test.rb` | No benchmark suite exists yet, but the summary/index path now avoids Ruby-side record loading for the standard cards. | `done` | Phase D complete |
+| Statement refresh fetch path | Legacy DSL refresh behavior fetched through Wringer URLs; current internal-mode refresh now reaches `NativeFetch` and intentionally does not use `Distillator::FetchCache`. | `app/helpers/statements_helper.rb`, `app/services/dsl/core/algorithm_runner.rb`, `app/services/distillator/fetch_service.rb` | `test/helpers/statements_helper_refresh_test.rb`, `test/services/dsl_algorithm_runner_test.rb` | No dedicated doc-linked controller or service test outside helper/runner scope. | `done` | Phase I complete for fetch integration |
+
+## Notes
+
+- The legacy parameter list in the Wringer controller comment is fully represented above:
+ `uri`, `include_fragment`, `use_phantomjs`, `force_scrape`, `force_scrape_every_hrs`, `format`, `absolute_src`, and `json_post`.
+- `format` is split into `format=raw`, `format=html`, and `format=json` because the migration risk and current implementation differ by response type.
+- Several behaviors are already working in Condenser compatibility mode but remain `partial` because coverage is still concentrated in lower-level Distillator tests rather than explicit `/websites/wring` end-to-end fixtures.
diff --git a/docs/distillator_migration.md b/docs/distillator_migration.md
new file mode 100644
index 00000000..dcaceb50
--- /dev/null
+++ b/docs/distillator_migration.md
@@ -0,0 +1,89 @@
+# How To Transition A Site
+
+This is the operator playbook for moving a site through the Condenser production transition.
+
+Production states:
+
+- `legacy`: Wringer remains the production fetch path.
+- `shadow`: Wringer remains the production fetch path while Condenser is compared in the background.
+- `active`: Condenser becomes the production fetch path while Wringer remains available for inspection and rollback.
+
+Use the web Transition Report as the primary dashboard. The shadow-log summary script is a fallback for offline log analysis, not a required transition step.
+
+## Preflight
+
+Before changing any site mode:
+
+- confirm the app boots and jobs run normally
+- confirm the cache index and compare page load read-only
+- confirm Wringer inspection links still resolve
+- keep the global default safe while site-level rollout proceeds
+- run `bin/rails distillator:transition:preflight`
+
+## Shadow
+
+Move a site from `legacy` to `shadow` first.
+
+While a site is in `shadow`:
+
+- Wringer remains the active production backend
+- the Transition Report is the daily dashboard
+- comparison links and cache links stay available without triggering fetches
+- the site should collect enough evidence for promotion
+
+## Inspect
+
+Promote by evidence, not by ceremony. A site is ready for `active` when the operator evidence is clean or explicitly explained:
+
+- the Transition Report detail page loads read-only
+- important webpages have active cache links
+- the compare page shows no blocking regressions
+- export invariance or graph diff is clean or accepted
+- queue behavior is stable under refresh
+- rollback still points to Wringer inspection
+
+Run the same readiness flow from either the report or the CLI:
+
+- `bin/rails distillator:transition:preflight`
+- `bin/rails distillator:transition:check[website_id]`
+
+## Promote
+
+Normal production rollout order is:
+
+1. `legacy`
+2. `shadow`
+3. `active`
+
+Do not use replay or internal aliases as operator-facing rollout steps.
+
+## Rollback
+
+Rollback is a mode change, not a redeploy.
+
+- change the site from `active` back to `legacy`
+- confirm the active backend flips back to Wringer
+- keep the Condenser cache link available for inspection
+- review the recorded rollout event and readiness snapshot
+
+## Common Blockers
+
+Common reasons a site should stay out of `active`:
+
+- fetch check failed or is stale
+- statements evidence is missing or failed
+- export diff is missing, stale, or failed
+- redirect or cache health requires review
+- representative URLs are not yet covered
+
+## Commands
+
+- `bin/rails distillator:transition:preflight`
+- `bin/rails distillator:transition:check[website_id]`
+- `DISTILLATOR_CACHE_REFRESH_UI=true` only when refresh controls must be exposed intentionally
+
+## Historical Notes
+
+Older phase-oriented notes remain in `docs/` as archived project history. They are not the current operator workflow.
+
+Legacy Apify scripts are external automation and are not part of the Condenser production transition. They will be reassessed separately.
diff --git a/docs/distillator_phase1_full_migration_staging_plan.md b/docs/distillator_phase1_full_migration_staging_plan.md
new file mode 100644
index 00000000..97023762
--- /dev/null
+++ b/docs/distillator_phase1_full_migration_staging_plan.md
@@ -0,0 +1,111 @@
+# Distillator Phase 1 Full-Migration Staging Plan
+
+This plan validates the completed Wringer-in-Condenser migration as one staging
+deployment package. It should be run after the
+[Distillator Phase 1 Wringer Readiness](distillator_phase1_wringer_readiness.md)
+audit is green and before production rollout.
+
+## Deployment Scope
+
+- Wringer-compatible `/websites/wring` is now served by Condenser.
+- `/websites.json?term` lookup is served from the Distillator fetch cache.
+- The `distillator_fetch_caches` table is migrated.
+- `Distillator::FetchService` contract is unchanged.
+- Default fetch mode remains legacy unless explicitly changed.
+
+## Pre-Deploy Checks
+
+- Readiness audit is green:
+ - `bundle exec rails test test/docs/distillator_phase1_wringer_readiness_test.rb`
+- Wringer compatibility tests are green:
+ - `bundle exec rails test test/controllers/wringer_compat_controller_test.rb`
+- Fetch cache store tests are green:
+ - `bundle exec rails test test/services/distillator/fetch_cache_store_test.rb`
+- Fetch guard tests are green:
+ - `bundle exec rails test test/services/distillator/fetch_guard_test.rb`
+- Export invariance tests are green:
+ - `bundle exec rails test test/services/distillator/export_invariance_test.rb`
+- Zeitwerk check is green:
+ - `bundle exec rails zeitwerk:check`
+- Migration applies cleanly in staging before traffic is shifted.
+
+## Migration Checks
+
+- Confirm `distillator_fetch_caches` table exists.
+- Confirm the unique index on `uri_key` exists.
+- Confirm JSONB fields are available for:
+ - `headers`
+ - `signals`
+ - `hints`
+ - `redirect_chain`
+ - `json_ld`
+- Confirm rollback plan for the migration is known before deploy.
+- Confirm whether table rollback would destroy staging cache data.
+
+## Staging Smoke Tests
+
+Use a known safe public staging URL for `
` and its escaped form for
+``.
+
+- `GET /websites/wring?uri=&format=raw`
+ - Expect HTTP success and raw cached/fetched HTML.
+- `GET /websites/wring?uri=&format=json`
+ - Expect JSON with `html`, `signals`, `hints`, `final_url`,
+ `redirect_chain`, and `http_code`.
+- `GET /websites.json?term=`
+ - Expect a matching cache record when one exists.
+- `GET /websites/wring?uri=&format=json&force_scrape=true`
+ - Expect refresh attempt and updated `scrape_date`.
+- `GET /websites/wring?uri=&format=json&force_scrape_every_hrs=1`
+ - Expect stale cache refresh and non-stale cache reuse.
+- `GET /websites/wring?uri=&format=raw&absolute_src=true`
+ - Expect relative `src` and `href` values rewritten to absolute URLs.
+- `GET /websites/wring?uri=&format=json&json_post=true`
+ - Run only if staging has a safe POST target.
+ - Expect JSON content signals and no unsafe external side effects.
+- `GET /websites/wring?uri=&format=json&use_phantomjs=true`
+ - With `PHANTOMJS_API_KEY` absent, expect direct URL fallback behavior.
+- `GET /websites/wring?uri=&format=json&force_scrape=true`
+ - Expect 404 metadata and preservation of last successful HTML when present.
+- `GET /websites/wring?uri=http://127.0.0.1&format=json`
+ - Expect unsafe URL blocked behavior with no network bypass.
+
+## Export Validation
+
+- Run representative website export in staging.
+- Compare Artsdata export against prior known-good output when available.
+- Review expected differences, if any, before accepting staging.
+- Confirm no unexplained changes in:
+ - `final_url`
+ - `redirect_chain`
+ - stored or surfaced 404 behavior
+
+## Operational Observations
+
+Capture these observations during and after staging smoke tests:
+
+- Number of Distillator fetch cache records created.
+- Number of blocked fetches.
+- Number of 404s.
+- Number of failed network scrapes.
+- Examples of recorded `signals`.
+- Examples of recorded `hints`.
+
+## Acceptance Criteria
+
+- Compatibility endpoints respond correctly.
+- Cache refresh semantics are verified.
+- Stored 404 lookup is verified.
+- Representative export is accepted.
+- No unsafe fetch bypass is observed.
+- No unexpected error spike is observed.
+- Rollback path is understood.
+
+## Rollback
+
+- Route traffic back to old Wringer if it is still available, or revert the
+ Condenser deployment.
+- Unset any non-default fetch mode.
+- Preserve `distillator_fetch_caches` data unless migration rollback is
+ required.
+- Document whether cache table rollback is destructive before executing it.
diff --git a/docs/distillator_phase1_rollout.md b/docs/distillator_phase1_rollout.md
new file mode 100644
index 00000000..d9858de8
--- /dev/null
+++ b/docs/distillator_phase1_rollout.md
@@ -0,0 +1,16 @@
+# Archived Fetch Rollout Notes
+
+This file is retained as historical rollout planning.
+
+For the current operator workflow use:
+
+- `docs/distillator_migration.md`
+- `docs/rollout_modes.md`
+
+Current production transition rules:
+
+- operator-facing rollout uses `legacy`, `shadow`, and `active`
+- `internal` is not a user-facing rollout step
+- replay is diagnostic-only
+
+Historical staging notes and older runtime terminology remain archived here for reference only.
diff --git a/docs/distillator_phase1_shadow_observation.md b/docs/distillator_phase1_shadow_observation.md
new file mode 100644
index 00000000..a313dba8
--- /dev/null
+++ b/docs/distillator_phase1_shadow_observation.md
@@ -0,0 +1,11 @@
+# Archived Shadow Observation Notes
+
+This file is retained for historical staging notes.
+
+Current workflow:
+
+- use the web Transition Report first
+- use `bin/rails distillator:transition:check[website_id]` for the shared readiness check
+- use `ruby script/distillator_shadow_log_summary.rb ...` only for offline log analysis or raw log review
+
+Historical phase wording is archived here on purpose and should not be reused in current operator docs.
diff --git a/docs/distillator_phase1_staging_acceptance_report.md b/docs/distillator_phase1_staging_acceptance_report.md
new file mode 100644
index 00000000..897b33e7
--- /dev/null
+++ b/docs/distillator_phase1_staging_acceptance_report.md
@@ -0,0 +1,77 @@
+# Distillator Phase 1 Staging Acceptance Report
+
+Use this report to record the result of the full-migration staging validation.
+
+## Run Metadata
+
+- Date/time window:
+- Commit SHA:
+- Migration version:
+- Environment:
+
+## Test URLs Used
+
+- Known HTML URL:
+- Known JSON URL:
+- Safe JSON POST target:
+- Known 404 URL:
+- Unsafe URL test:
+- Escaped URI used for `/websites.json?term`:
+
+## Endpoint Smoke-Test Results
+
+| Check | URL or command | Result | Notes |
+|---|---|---|---|
+| raw `/websites/wring` | `GET /websites/wring?uri=&format=raw` | | |
+| json `/websites/wring` | `GET /websites/wring?uri=&format=json` | | |
+| `/websites.json?term` lookup | `GET /websites.json?term=` | | |
+| force_scrape behavior | `GET /websites/wring?uri=&format=json&force_scrape=true` | | |
+| force_scrape_every_hrs behavior | `GET /websites/wring?uri=&format=json&force_scrape_every_hrs=1` | | |
+| absolute_src behavior | `GET /websites/wring?uri=&format=raw&absolute_src=true` | | |
+| json_post behavior | `GET /websites/wring?uri=&format=json&json_post=true` | | |
+| PhantomJS missing-key fallback | `GET /websites/wring?uri=&format=json&use_phantomjs=true` | | |
+| 404/stored failure behavior | `GET /websites/wring?uri=&format=json&force_scrape=true` | | |
+| unsafe URL blocked behavior | `GET /websites/wring?uri=http://127.0.0.1&format=json` | | |
+
+## Export Validation Result
+
+- Representative website:
+- Export command:
+- Prior known-good output used:
+- Comparison result:
+- Expected differences reviewed:
+- Unexplained `final_url` changes:
+- Unexplained `redirect_chain` changes:
+- Unexplained 404 changes:
+
+## Cache Record Count
+
+- Before:
+- After:
+- Net new records:
+
+## Blocked Fetch Review
+
+- Count:
+- Sample URLs:
+- Expected or unexpected:
+- Notes:
+
+## Failed Scrape Review
+
+- Count:
+- Sample URLs:
+- Error types:
+- Signals/hints examples:
+- Notes:
+
+## Decision
+
+- Decision: accept / reject / accept with follow-ups
+- Decider:
+- Decision timestamp:
+- Rationale:
+
+## Follow-Up Tasks
+
+- [ ]
diff --git a/docs/distillator_phase1_wringer_readiness.md b/docs/distillator_phase1_wringer_readiness.md
new file mode 100644
index 00000000..93c5e47b
--- /dev/null
+++ b/docs/distillator_phase1_wringer_readiness.md
@@ -0,0 +1,98 @@
+# Distillator Phase 1 Wringer Readiness
+
+This audit tracks each required Wringer-compatible behavior against the current
+Distillator implementation. A behavior is only marked `tested` when the named
+test exercises the Distillator-owned path, not just legacy Wringer behavior.
+
+Status values:
+- `legacy-only`: preserved only through the legacy fetch path.
+- `native`: implemented in Distillator, but not yet proven by a Distillator-path test.
+- `shadow-only`: observed only in shadow comparison mode.
+- `tested`: implemented in Distillator and exercised by the named test.
+- `open`: not ready for cutover; must also appear in Open items.
+
+| Required behavior | Status | Owning code path | Test file proving behavior | Migration risk | Cutover requirement |
+|---|---|---|---|---|---|
+| GET /websites/wring route | tested | `config/routes.rb`, `app/controllers/websites_controller.rb` | `test/controllers/wringer_compat_controller_test.rb` | Low: route drift would break callers immediately. | Keep the route and controller action wired to `Distillator::FetchCacheStore.fetch`. |
+| raw response format | tested | `app/controllers/websites_controller.rb`, `app/services/distillator/fetch_cache_store.rb` | `test/controllers/wringer_compat_controller_test.rb` | Medium: wrong body rendering would change caller-visible HTML. | Preserve raw body passthrough from cached Distillator HTML. |
+| json response format | tested | `app/controllers/websites_controller.rb`, `app/services/distillator/fetch_cache_store.rb` | `test/controllers/wringer_compat_controller_test.rb` | High: response-key drift would break Wringer consumers. | Keep JSON keys and value shapes aligned with the compatibility contract. |
+| html redirect/default response | tested | `app/controllers/websites_controller.rb` | `test/controllers/wringer_compat_controller_test.rb`, `test/controllers/websites_controller_test.rb` | Low: fallback UX regression is visible but localized. | Preserve redirect-to-websites behavior for non-raw and non-json requests. |
+| invalid params no_content behavior | tested | `app/controllers/websites_controller.rb`, `app/services/distillator/wringer_url_key.rb` | `test/controllers/wringer_compat_controller_test.rb`, `test/services/distillator/wringer_url_key_test.rb` | Medium: raising instead of returning `204` would break compatibility callers. | Keep invalid or missing URI handling on the Distillator controller path. |
+| /websites.json?term lookup | tested | `app/controllers/websites_controller.rb`, `app/services/distillator/fetch_cache_store.rb` | `test/controllers/wringer_compat_controller_test.rb` | High: stored-cache lookup is a direct migration dependency. | Continue serving lookup results from `Distillator::FetchCacheStore.lookup_by_term`. |
+| URI key generation | tested | `app/services/distillator/wringer_url_key.rb` | `test/services/distillator/wringer_url_key_test.rb` | High: key drift would orphan cache records and lookups. | Preserve Wringer-compatible normalization and escaping rules. |
+| no-scheme URL handling | tested | `app/services/distillator/wringer_url_key.rb` | `test/services/distillator/wringer_url_key_test.rb` | Medium: normalization mismatch would miss cache hits. | Keep implicit `http://` normalization before key generation. |
+| query preservation | tested | `app/services/distillator/wringer_url_key.rb` | `test/services/distillator/wringer_url_key_test.rb` | High: dropping query strings would alias distinct pages. | Preserve query strings in both normalized URL and `uri_key`. |
+| fragment exclusion by default | tested | `app/services/distillator/wringer_url_key.rb` | `test/services/distillator/wringer_url_key_test.rb` | Medium: fragment handling changes cache identity. | Keep default behavior excluding fragments unless explicitly requested. |
+| fragment inclusion with include_fragment | tested | `app/services/distillator/wringer_url_key.rb` | `test/services/distillator/wringer_url_key_test.rb` | Medium: include-fragment callers need stable cache identity. | Preserve `include_fragment` support on the Distillator key path. |
+| CGI.escape uri_key storage | tested | `app/services/distillator/wringer_url_key.rb`, `app/services/distillator/fetch_cache_store.rb` | `test/services/distillator/wringer_url_key_test.rb`, `test/services/distillator/fetch_cache_store_test.rb` | High: escaping drift breaks storage and lookup parity. | Keep `CGI.escape` as the persisted key format. |
+| cache hit | tested | `app/services/distillator/fetch_cache_store.rb` | `test/services/distillator/fetch_cache_store_test.rb` | Medium: unnecessary refetches change behavior and load. | Preserve Distillator cache-hit short-circuiting. |
+| force_scrape | tested | `app/services/distillator/fetch_cache_store.rb` | `test/services/distillator/fetch_cache_store_test.rb` | Medium: ignored refresh flags would delay updates. | Keep explicit force-refresh semantics on the Distillator fetch cache path. |
+| force_scrape_every_hrs | tested | `app/services/distillator/fetch_cache_store.rb` | `test/services/distillator/fetch_cache_store_test.rb` | Medium: stale-threshold drift changes refresh cadence. | Preserve stale-cache refresh timing logic. |
+| successful 2xx cache update | tested | `app/services/distillator/fetch_cache_store.rb` | `test/services/distillator/fetch_cache_store_test.rb` | High: bad writes would corrupt cached source HTML and metadata. | Keep 2xx writes updating HTML, body, name, refresh timestamps, and HTTP code. |
+| failed 404 metadata update without overwriting last successful HTML | tested | `app/services/distillator/fetch_cache_store.rb` | `test/services/distillator/fetch_cache_store_test.rb` | High: overwriting good HTML on 404 would damage exports. | Preserve the current 404 write policy before cutover. |
+| scrape_date | tested | `app/services/distillator/fetch_cache_store.rb` | `test/services/distillator/fetch_cache_store_test.rb` | Medium: missing scrape timestamps hides refresh behavior. | Keep `scrape_date` updates on refresh attempts. |
+| successful_refresh | tested | `app/services/distillator/fetch_cache_store.rb` | `test/services/distillator/fetch_cache_store_test.rb` | Medium: bad success timestamps obscure last-known-good content. | Keep `successful_refresh` updates restricted to successful refreshes. |
+| http_response_code | tested | `app/services/distillator/fetch_cache_store.rb`, `app/controllers/websites_controller.rb` | `test/controllers/wringer_compat_controller_test.rb`, `test/services/distillator/fetch_cache_store_test.rb` | High: response-code drift changes stored-404 behavior and compatibility JSON. | Preserve storage and surfacing of HTTP response codes. |
+| headers | tested | `app/services/distillator/fetch_cache_store.rb`, `app/services/distillator/fetch_service.rb` | `test/services/distillator/fetch_service_test.rb`, `test/services/distillator/fetch_replay_test.rb` | Medium: header normalization errors can misclassify content. | Keep normalized header extraction on Distillator fetch outputs. |
+| signals | tested | `app/services/distillator/fetch_cache_store.rb`, `app/services/distillator/fetch_service.rb` | `test/controllers/wringer_compat_controller_test.rb`, `test/services/distillator/fetch_cache_store_test.rb`, `test/services/distillator/fetch_service_test.rb` | High: signal drift changes downstream error handling. | Preserve Wringer-compatible signal keys from Distillator fetch paths. |
+| hints | tested | `app/services/distillator/fetch_cache_store.rb`, `app/services/distillator/fetch_service.rb` | `test/controllers/wringer_compat_controller_test.rb`, `test/services/distillator/fetch_cache_store_test.rb`, `test/services/distillator/fetch_service_test.rb` | Medium: lost hints reduce debugging and retry accuracy. | Keep Distillator hint population and JSON surfacing unchanged. |
+| final_url | tested | `app/services/distillator/fetch_cache_store.rb`, `app/services/distillator/fetch_service.rb` | `test/controllers/wringer_compat_controller_test.rb`, `test/services/distillator/fetch_cache_store_test.rb`, `test/services/distillator/fetch_service_test.rb`, `test/services/distillator/export_invariance_test.rb` | High: wrong final URLs can change export semantics and redirect auditing. | Preserve stored and returned final URLs through cutover. |
+| redirect_chain | tested | `app/services/distillator/fetch_cache_store.rb`, `app/services/distillator/fetch_service.rb` | `test/controllers/wringer_compat_controller_test.rb`, `test/services/distillator/fetch_cache_store_test.rb`, `test/services/distillator/fetch_service_test.rb`, `test/services/distillator/export_invariance_test.rb` | High: redirect-chain drift can hide unsafe redirects and change parity checks. | Preserve ordered redirect-chain capture and surfacing. |
+| absolute_src rewriting for src and href | tested | `app/services/distillator/html_rewriter.rb`, `app/services/distillator/fetch_cache_store.rb`, `app/services/distillator/native_fetch.rb` | `test/services/distillator/html_rewriter_test.rb`, `test/services/distillator/fetch_cache_store_test.rb` | Medium: broken rewriting changes cached HTML consumers see. | Keep parser-based relative `src` and `href` rewriting tied to `absolute_src` on the NativeFetch-backed cache path. |
+| json_post behavior | tested | `app/services/distillator/native_fetch.rb`, `app/services/distillator/fetch_cache_store.rb`, `app/services/distillator/fetch_service.rb` | `test/services/distillator/native_fetch_test.rb`, `test/services/distillator/fetch_cache_store_test.rb`, `test/services/distillator/fetch_service_test.rb` | Medium: POST-mode divergence could break specialized callers. | Keep native Distillator POST refreshes preserving JSON metadata and last-known-good HTML on failed refreshes. |
+| use_phantomjs behavior | tested | `app/services/distillator/rendered_fetch.rb`, `app/services/distillator/renderers/legacy_phantomjs_renderer.rb`, `app/services/distillator/native_fetch.rb`, `app/services/distillator/fetch_cache_store.rb` | `test/services/distillator/rendered_fetch_test.rb`, `test/services/distillator/renderers/legacy_phantomjs_renderer_test.rb`, `test/services/distillator/fetch_cache_store_test.rb` | Medium: rendered-request drift would break compatibility rendering. | Preserve the explicit rendered-fetch strategy and renderer metadata while modern renderer migration stays deferred. |
+| PhantomJS missing-key fallback | tested | `app/services/distillator/renderers/legacy_phantomjs_renderer.rb` | `test/services/distillator/renderers/legacy_phantomjs_renderer_test.rb` | Medium: missing fallback would turn config gaps into outages. | Keep direct-fetch fallback when `PHANTOMJS_API_KEY` is absent. |
+| iframe special case | tested | `app/controllers/websites_controller.rb`, `app/services/distillator/renderers/legacy_phantomjs_renderer.rb`, `app/services/distillator/native_fetch.rb` | `test/services/distillator/renderers/legacy_phantomjs_renderer_test.rb`, `test/controllers/wringer_compat_controller_test.rb` | Medium: iframe extraction drift would change stored HTML. | Preserve iframe detection, PhantomJS JSON mode, and child-frame extraction on the rendered compatibility path. |
+| materialized cache health summary | tested | `app/services/distillator/cache_health_materializer.rb`, `app/services/distillator/cache_summary.rb`, `app/services/distillator/cache_index_query.rb`, `lib/tasks/distillator_cache.rake` | `test/services/distillator/cache_health_materializer_test.rb`, `test/services/distillator/cache_summary_test.rb`, `test/services/distillator/cache_index_query_test.rb`, `test/tasks/distillator_cache_task_test.rb` | Medium: summary/index regressions would make large cache tables expensive to inspect. | Keep materialized health fields current on write and backfillable without network calls. |
+| ERB delimiter escaping | tested | `app/services/distillator/fetch_cache_store.rb`, `app/services/distillator/native_fetch.rb` | `test/services/distillator/fetch_cache_store_test.rb` | Medium: unescaped ERB in cached HTML is a safety risk. | Keep ERB delimiter escaping before NativeFetch-backed cache writes. |
+| initial URL guard | tested | `app/services/distillator/fetch_cache_store.rb`, `app/services/distillator/fetch_guard.rb`, `app/services/distillator/fetch_service.rb` | `test/services/distillator/fetch_cache_store_test.rb`, `test/services/distillator/fetch_guard_test.rb`, `test/services/distillator/fetch_service_test.rb` | High: guard regressions would reintroduce unsafe fetches. | Preserve URL guard checks on both cache-store and internal-fetch entry points. |
+| redirect URL guard | tested | `app/services/distillator/fetch_cache_store.rb`, `app/services/distillator/fetch_guard.rb`, `app/services/distillator/fetch_service.rb` | `test/services/distillator/fetch_cache_store_test.rb`, `test/services/distillator/fetch_guard_test.rb`, `test/services/distillator/fetch_service_test.rb` | High: unsafe redirects must stay blocked after cutover. | Preserve redirect-chain and final-URL guard enforcement. |
+| SSL/Mechanize/Socket error metadata | tested | `app/services/distillator/fetch_cache_store.rb` | `test/services/distillator/fetch_cache_store_test.rb` | Medium: wrong failure metadata changes retries and diagnosis. | Keep failed-network signal and hint mapping stable. |
+| export invariance | tested | `app/services/distillator/fetch_service.rb`, `app/services/export_artsdata_service.rb` | `test/services/distillator/export_invariance_test.rb` | High: export drift is the core migration risk. | Keep normalized Artsdata output equivalent across replay, legacy, and internal runs. |
+| legacy/internal fetch parity | tested | `app/services/distillator/fetch_service.rb` | `test/services/distillator/fetch_service_test.rb`, `test/services/distillator/export_invariance_test.rb` | High: parity gaps would make full cutover unsafe. | Preserve current internal-path contract and parity assertions before enabling it broadly. |
+| default legacy mode | legacy-only | `app/services/distillator/fetch_service.rb`, `app/services/distillator/fetch_mode.rb` | `test/services/distillator/fetch_service_test.rb` | Low for current rollout, but it means production still depends on legacy behavior by default. | Change only when an explicit fetch-mode cutover decision is made and parity evidence stays green. |
+| replay fixture compatibility | tested | `app/services/distillator/fetch_replay.rb`, `app/services/distillator/fetch_service.rb` | `test/services/distillator/fetch_replay_test.rb`, `test/services/distillator/export_invariance_test.rb` | Low for production, medium for migration verification quality. | Keep replay response shape stable so migration audits remain trustworthy. |
+
+## Open items
+
+None.
+
+## Wringer Parity Target
+
+Use this focused regression target before calling the migration Wringer-parity-complete:
+
+```bash
+DISABLE_SPRING=1 bundle exec rails test \
+ test/helpers/statements_helper_test.rb \
+ test/helpers/statements_helper_format_datatype_test.rb \
+ test/controllers/statements_controller_test.rb \
+ test/presenters/trace_presenter_test.rb \
+ test/services/statements/refresh_webpage_statements_service_test.rb \
+ test/services/distillator/wringer_system_error_matcher_test.rb \
+ test/integration/distillator_refresh_then_export_test.rb \
+ test/integration/distillator_refresh_rdf_uri_cache_test.rb
+```
+
+This target covers the active Wringer-parity surface that most recently drifted:
+- SPARQL extraction compatibility and structured abort propagation.
+- linked-data `xsd:anyURI` formatting without sentinel leakage.
+- single-statement Distillator cache writes through the real DSL fetch seam.
+- trace visibility, redirect/session trace persistence, and request-local helper cookies.
+- bulk refresh and immediate export-after-refresh behavior.
+
+Keep the existing skipped extraction-drift sentinel in `test/services/dsl_contract_test.rb` skipped until the underlying drift is fixed.
+
+## Current Sign-off
+
+- Compatibility complete:
+ - `json_post` native parity is covered.
+ - `use_phantomjs` compatibility behavior is preserved through explicit rendered-fetch services.
+ - iframe and missing-key fallback behavior are directly tested.
+- Native parity complete:
+ - GET, POST, and rendered compatibility fetches all write through `Distillator::FetchCacheStore`.
+- Operational scalability complete:
+ - cache summary and byte-sorting now rely on materialized cache-health fields and a rerunnable backfill task.
+- Deferred later-phase work:
+ - modern non-Phantom rendered fetch replacement
+ - Sidekiq-backed cache refresh orchestration
+ - cache mutation UI from the inspector
diff --git a/docs/distillator_phase_i_status.md b/docs/distillator_phase_i_status.md
new file mode 100644
index 00000000..56b75e25
--- /dev/null
+++ b/docs/distillator_phase_i_status.md
@@ -0,0 +1,16 @@
+# Archived Distillator Phase I Status
+
+This file is retained as project history only.
+
+Do not use it as the current operator workflow. Use these documents instead:
+
+- `docs/distillator_migration.md` for the production transition playbook
+- `docs/rollout_modes.md` for the current rollout glossary
+
+Current rollout contract:
+
+- operator-facing production states are `legacy`, `shadow`, and `active`
+- `internal` remains a private compatibility alias only where older runtime callers still need it
+- replay remains diagnostic-only and is not part of the production transition path
+
+Historical phase language is intentionally archived here and should not be copied into operator UI or transition runbooks.
diff --git a/docs/distillator_wringer_parity.md b/docs/distillator_wringer_parity.md
new file mode 100644
index 00000000..2fc8a8eb
--- /dev/null
+++ b/docs/distillator_wringer_parity.md
@@ -0,0 +1,93 @@
+# Distillator to Wringer Parity Matrix
+
+This matrix maps Wringer-facing behavior to Distillator coverage and highlights the few intentional legacy quirks we still preserve during migration.
+
+## URL Safety
+
+| Behavior | Distillator coverage | Notes |
+| --- | --- | --- |
+| `localhost` rejected | `test/services/distillator/fetch_guard_test.rb` | Matches Wringer block behavior. |
+| `127.0.0.1` rejected | `test/services/distillator/fetch_guard_test.rb`, `test/services/distillator/fetch_service_test.rb` | Blocked before native fetch. |
+| `10.0.0.1` rejected | `test/services/distillator/fetch_guard_test.rb` | Matches Wringer private IPv4 block. |
+| `169.254.169.254` rejected | `test/services/distillator/fetch_guard_test.rb` | Matches Wringer link-local block. |
+| private IPv6 rejected | `test/services/distillator/fetch_guard_test.rb` | Covers loopback, unique-local, and link-local IPv6. |
+| `file://` rejected | `test/services/distillator/fetch_guard_test.rb` | Matches Wringer non-HTTP block. |
+| public HTTP/HTTPS allowed | `test/services/distillator/fetch_guard_test.rb`, `test/services/distillator/url_safety_policy_test.rb` | Explicit public allow path. |
+| DNS resolves to private IP | `test/services/distillator/fetch_guard_test.rb`, `test/services/distillator/url_safety_policy_test.rb` | Distillator fails closed. |
+| DNS returns `[]` | `test/services/distillator/fetch_guard_test.rb`, `test/services/distillator/url_safety_policy_test.rb` | Intentional hardening over old Wringer allow-through. |
+| DNS raises timeout/error | `test/services/distillator/fetch_guard_test.rb`, `test/services/distillator/url_safety_policy_test.rb` | Intentional hardening over ambiguous legacy behavior. |
+| unsafe redirect target rejected | `test/services/distillator/fetch_guard_test.rb` | Redirect chain is rechecked. |
+| unsafe final URL rejected | `test/services/distillator/fetch_guard_test.rb`, `test/services/distillator/fetch_service_test.rb` | Final URL is rechecked after fetch. |
+| blocked reason visible in diagnostics | `test/services/distillator/cache_refresh_preview_test.rb`, `test/controllers/distillator/cache_controller_test.rb`, `test/services/distillator/fetch_service_test.rb` | Preview exposes `guard_reason` / `guard_error`; blocked fetch metadata keeps `guard_reason`. |
+
+## URL Key Generation
+
+| Behavior | Distillator coverage | Notes |
+| --- | --- | --- |
+| core Wringer examples | `test/services/distillator/wringer_url_key_test.rb` | Canonical parity cases. |
+| trailing slash normalization | `test/services/distillator/wringer_url_key_test.rb` | Preserved. |
+| fragment excluded by default | `test/services/distillator/wringer_url_key_test.rb` | Preserved. |
+| `include_fragment=true` | `test/services/distillator/wringer_url_key_test.rb` | Preserved. |
+| `include_fragment="false"` / `"true"` | `test/services/distillator/wringer_url_key_test.rb` | Preserved. |
+| malformed URL | `test/services/distillator/wringer_url_key_test.rb` | Raises for controller handling. |
+| blank URL | `test/services/distillator/wringer_url_key_test.rb` | Raises deterministically. |
+| already-escaped URL | `test/services/distillator/wringer_url_key_test.rb` | Preserves legacy `"Error: not a URI"` path. |
+| uppercase scheme quirk | `test/services/distillator/wringer_url_key_test.rb` | Intentional legacy compatibility quirk, not a normalization improvement. |
+
+## Wringer-Compatible JSON Contract
+
+| Behavior | Distillator coverage | Notes |
+| --- | --- | --- |
+| top-level JSON keys `html/signals/hints/final_url/redirect_chain/http_code` | `test/controllers/wringer_compat_controller_test.rb`, `test/controllers/distillator/cache_controller_test.rb` | Tests fail if keys are removed. |
+| redirect metadata survives replay | `test/controllers/wringer_compat_controller_test.rb`, `test/controllers/distillator/cache_controller_test.rb`, `test/services/distillator/fetch_response_contract_test.rb` | Covers `redirect_type`, `redirected`, and `final_url`. |
+| failure metadata survives replay | `test/controllers/wringer_compat_controller_test.rb`, `test/services/distillator/fetch_response_contract_test.rb` | Timeout / blocked / failure signals stay visible. |
+| fetch response canonical service contract | `test/services/distillator/fetch_response_contract_test.rb` | Covers legacy, internal, shadow, replay, and blocked responses. |
+
+## POST / `json_post`
+
+| Behavior | Distillator coverage | Notes |
+| --- | --- | --- |
+| POST command dispatch | `test/services/distillator/cache_fetch_command_test.rb` | `fetch_kind: "post"` maps to `json_post`. |
+| POST success metadata persisted | `test/services/distillator/fetch_cache_store_test.rb` | Captures body, status code, content type, and request method. |
+| POST cached replay contract | `test/controllers/wringer_compat_controller_test.rb` | Contract preserved for replayed JSON payloads. |
+| empty POST response hint | `test/controllers/wringer_compat_controller_test.rb`, `test/services/distillator/fetch_cache_store_test.rb` | `empty_body` preserved. |
+| failed POST response metadata | `test/services/distillator/fetch_cache_store_test.rb` | Deterministic failure metadata retained. |
+
+## Cache Refresh Semantics
+
+| Behavior | Distillator coverage | Notes |
+| --- | --- | --- |
+| cache miss refreshes | `test/services/distillator/cache_refresh_preview_test.rb`, `test/services/distillator/fetch_cache_store_test.rb` | Shared decision path. |
+| `force_scrape=true` | `test/services/distillator/cache_refresh_preview_test.rb`, `test/services/distillator/fetch_cache_store_test.rb` | Preview and fetch agree. |
+| `force_scrape_every_hrs=24` | `test/services/distillator/cache_refresh_preview_test.rb`, `test/services/distillator/fetch_cache_store_test.rb` | Shared stale decision. |
+| `force_scrape_every_hrs=0` | `test/services/distillator/cache_refresh_preview_test.rb`, `test/services/distillator/fetch_cache_store_test.rb`, `test/controllers/distillator/cache_controller_test.rb` | Explicit refresh in preview and fetch. |
+| very large `force_scrape_every_hrs` | `test/services/distillator/cache_refresh_preview_test.rb`, `test/controllers/distillator/cache_controller_test.rb` | Stays fresh. |
+| missing scrape timestamp | `test/services/distillator/cache_refresh_preview_test.rb`, `test/services/distillator/fetch_cache_store_test.rb` | Explicit `missing_scrape_date`. |
+| stale vs fresh diagnostics | `test/controllers/distillator/cache_controller_test.rb` | Preview and show page expose reason. |
+
+## Absolute `src` / `href` Rewriting
+
+| Behavior | Distillator coverage | Notes |
+| --- | --- | --- |
+| `/image.png` | `test/services/distillator/html_rewriter_test.rb` | Matches Wringer behavior. |
+| absolute external URL unchanged | `test/services/distillator/html_rewriter_test.rb` | Matches Wringer behavior. |
+| `../image.png` | `test/services/distillator/html_rewriter_test.rb` | Matches Wringer behavior. |
+| non-greedy additional attributes | `test/services/distillator/html_rewriter_test.rb` | Matches Wringer behavior. |
+| invalid path preserved safely | `test/services/distillator/html_rewriter_test.rb` | Fail-safe behavior. |
+| `href` and `src` support | `test/services/distillator/html_rewriter_test.rb` | Rewriter tested independently. |
+
+## Raw Cached HTML
+
+| Behavior | Distillator coverage | Notes |
+| --- | --- | --- |
+| raw cached body retrievable | `test/controllers/distillator/cache_controller_test.rb`, `test/controllers/wringer_compat_controller_test.rb` | Compatibility preserved. |
+| script-bearing raw HTML does not lose bytes | `test/controllers/distillator/cache_controller_test.rb` | Body still returned intact. |
+| raw endpoint hardened with CSP sandbox | `test/controllers/distillator/cache_controller_test.rb` | Reduces app-origin script execution risk. |
+| admin preview stays escaped and warns operators | `test/controllers/distillator/cache_controller_test.rb` | `raw_view` is operator-facing, not direct execution. |
+
+## Fetch-Service Boundary Audit
+
+| Behavior | Distillator coverage | Notes |
+| --- | --- | --- |
+| no private-method `send` for normal collaboration | source audit in `app/services/distillator/*.rb` | Replaced `FetchService.send(...)` cross-service calls with direct public helper calls. |
+
diff --git a/docs/migration/contract_template.md b/docs/migration/contract_template.md
new file mode 100644
index 00000000..5b56d5fe
--- /dev/null
+++ b/docs/migration/contract_template.md
@@ -0,0 +1,147 @@
+# Distillator Migration Contract Template
+
+## Site Identification
+- Site name:
+- Site ID (if applicable):
+- Source system(s): Wringer / Condenser / Console
+- Target system: Distillator
+- Contract version:
+- Contract author:
+- Date created:
+- Last updated:
+- Related links (pipeline config, issue, PR, dashboards):
+
+## Purpose of Contract
+- Define expected pipeline output quality and structure for this site during migration.
+- Establish comparison criteria between baseline output and Distillator output.
+- Ensure Artsdata input quality is preserved or improved.
+
+## Expected Output Characteristics
+- Output type(s) expected (for example: events):
+- Coverage scope (date range, locale, categories):
+- Output freshness expectation:
+- Deduplication expectation:
+- URL integrity expectation:
+- Language/content completeness expectation:
+
+## Event Count Expectations
+- Baseline event count (source system):
+- Distillator expected range:
+- Accepted variance tolerance (absolute and/or %):
+- Notes on seasonality/time-window effects:
+
+## Required Fields Checklist
+Mark each as `Present`, `Missing`, or `N/A`.
+
+| Field | Status | Notes |
+|---|---|---|
+| `id` | | |
+| `name` | | |
+| `startDate` | | |
+| `location` | | |
+| `url` | | |
+| `organizer` | | |
+| `description` | | |
+| `image` | | |
+| `offers` | | |
+| Other required field: | | |
+| Other required field: | | |
+
+## Field Quality Expectations
+Use concrete quality expectations per field.
+
+| Field | Quality expectation | Baseline quality notes | Distillator observed quality | Verdict |
+|---|---|---|---|---|
+| `name` | Accurate, human-readable title | | | |
+| `startDate` | Correct timezone and precision | | | |
+| `location` | Resolved venue and locality | | | |
+| `url` | Canonical, reachable, event-specific | | | |
+| `description` | Not empty, not boilerplate only | | | |
+| Other field: | | | | |
+
+## Known Edge Cases
+Document expected handling for site-specific edge cases.
+
+| Edge case | Expected behavior | Baseline behavior | Distillator behavior | Verdict |
+|---|---|---|---|---|
+| Missing start time | | | | |
+| Multi-day event | | | | |
+| Recurring listing | | | | |
+| Cancelled/postponed event | | | | |
+| Duplicate source pages | | | | |
+| Other edge case: | | | | |
+
+## Known Issues (Baseline)
+List current known defects in baseline output before migration.
+
+| Issue ID | Description | Severity | Baseline impact | Planned handling in migration |
+|---|---|---|---|---|
+| | | | | |
+| | | | | |
+
+## Allowed Differences
+Differences that may be accepted if final quality is preserved or improved.
+
+- [ ] Event count differs within defined tolerance.
+- [ ] Normalization differences (formatting/casing/whitespace only).
+- [ ] URL canonicalization improvements.
+- [ ] Better entity resolution (venue/locality/organizer).
+- [ ] Improved completeness for optional fields.
+- [ ] Other explicitly approved difference:
+
+## Forbidden Regressions
+Any item below is considered a failure unless explicitly approved.
+
+- [ ] Required field missing when previously present.
+- [ ] Broken, incorrect, or non-canonical event URLs.
+- [ ] Invalid event structure for Artsdata ingestion.
+- [ ] Significant unexplained event loss outside tolerance.
+- [ ] Date/time degradation (wrong day/time/timezone).
+- [ ] Location degradation (less specific or incorrect).
+- [ ] New duplicate events introduced.
+- [ ] Other site-specific blocker:
+
+## Comparison Rules
+Assign one status per evaluated item.
+
+- `MATCH`: Distillator output is equivalent to baseline for required quality.
+- `IMPROVED`: Distillator output is clearly better with no offsetting regression.
+- `ACCEPTABLE`: Minor difference, within allowed differences and tolerance.
+- `REGRESSION`: Quality or correctness declined versus baseline.
+- `BLOCKER`: Severe issue that invalidates migration readiness.
+
+### Decision Guidance
+- Overall result should be `MATCH`, `IMPROVED`, or explicitly accepted as `ACCEPTABLE` for approval.
+- Any unresolved `REGRESSION` requires remediation before approval.
+- Any `BLOCKER` stops approval.
+
+## Validation Checklist
+Complete before requesting approval.
+
+- [ ] Contract metadata completed.
+- [ ] Baseline sample/window documented.
+- [ ] Distillator sample/window documented.
+- [ ] Event count comparison completed.
+- [ ] Required fields checklist completed.
+- [ ] Field quality review completed.
+- [ ] Edge cases reviewed.
+- [ ] Baseline known issues acknowledged.
+- [ ] Allowed differences explicitly listed.
+- [ ] Forbidden regressions checked.
+- [ ] Comparison statuses assigned and justified.
+- [ ] Evidence links attached (queries, snapshots, diffs, logs).
+
+## Approval
+- Migration decision: `APPROVED` / `APPROVED WITH CONDITIONS` / `NOT APPROVED`
+- Decision date:
+- Approved by:
+- Reviewers:
+- Conditions (if any):
+- Follow-up actions and owners:
+- Re-validation due date (if applicable):
+
+## Evidence References
+- Baseline extraction/reference:
+- Distillator extraction/reference:
+- Comparison artifact(s):
+- Related issue(s)/PR(s):
diff --git a/docs/rollout_modes.md b/docs/rollout_modes.md
new file mode 100644
index 00000000..331bbafd
--- /dev/null
+++ b/docs/rollout_modes.md
@@ -0,0 +1,44 @@
+# Rollout Modes Glossary
+
+This document is the single terminology reference for the Wringer to Condenser rollout.
+
+## Terms
+
+| Internal term | Operator term | Meaning |
+| --- | --- | --- |
+| Wringer | Wringer | Legacy production fetch and cache system. |
+| Distillator | Condenser implementation | Internal migration namespace used in code while Condenser becomes the operator-facing system. |
+| Condenser | Condenser | Operator-facing new fetch and cache system. |
+| legacy | Legacy Wringer active | Wringer serves production results. |
+| shadow | Shadow comparison | Wringer serves production results; Condenser compares in the background. |
+| internal | Condenser active | Private compatibility alias for older runtime callers. |
+| active | Condenser active | Canonical public rollout and execution name. |
+| replay | Replay diagnostic | Replay-only diagnostic execution path. |
+
+## Resolution rules
+
+1. Website rollout state wins when a website or website id is present.
+2. Explicit diagnostic mode is allowed for operator and test workflows.
+3. Missing website context fails safe to legacy production behavior.
+4. `active` is the canonical public mode; `internal` remains a private compatibility alias where older code paths still need it.
+
+## Operator copy
+
+Use these phrases in operator-facing UI:
+
+- `Legacy Wringer active`
+- `Shadow comparison`
+- `Condenser active`
+- `Wringer remains the production fetch path.`
+- `Wringer serves production results; Condenser compares in the background.`
+- `Condenser serves fetch/cache results; legacy Wringer remains available for inspection.`
+- `Open active cache`
+- `Open Condenser cache`
+- `Inspect legacy Wringer`
+- `Compare Condenser vs Wringer`
+
+Avoid mixing operator copy with internal implementation terms such as `internal`, `Distillator active`, or `new cache`.
+
+## Scope note
+
+Legacy Apify scripts are external automation and are not part of the Condenser production transition. They will be reassessed separately.
diff --git a/latest.dump b/latest.dump
index 7d9067b1..bc4b9f24 100644
Binary files a/latest.dump and b/latest.dump differ
diff --git a/lib/tasks/distillator_cache.rake b/lib/tasks/distillator_cache.rake
new file mode 100644
index 00000000..b9df42ac
--- /dev/null
+++ b/lib/tasks/distillator_cache.rake
@@ -0,0 +1,23 @@
+namespace :distillator do
+ namespace :cache do
+ desc "Backfill materialized cache health fields"
+ task backfill_health: :environment do
+ total = Distillator::FetchCache.count
+ processed = 0
+
+ Distillator::FetchCache.find_each(batch_size: 1000) do |cache|
+ attrs = Distillator::CacheHealthMaterializer.attributes_for(cache)
+ cache.update_columns(attrs.merge(updated_at: Time.current))
+ processed += 1
+
+ next unless (processed % 1000).zero? || processed == total
+
+ Rails.logger.info(
+ event: "distillator.cache.backfill_health",
+ processed: processed,
+ total: total
+ )
+ end
+ end
+ end
+end
diff --git a/lib/tasks/distillator_fast_tests.rake b/lib/tasks/distillator_fast_tests.rake
new file mode 100644
index 00000000..2bae515c
--- /dev/null
+++ b/lib/tasks/distillator_fast_tests.rake
@@ -0,0 +1,18 @@
+namespace :test do
+ desc "Run fast Distillator/Wringer replacement unit tests"
+ task distillator_fast: :environment do
+ files = %w[
+ test/helpers/cc_wringer_helper_test.rb
+ test/services/distillator/fetch_mode_test.rb
+ test/services/distillator/fetch_eligibility_test.rb
+ test/services/distillator/fetch_service_test.rb
+ test/services/distillator/fetch_response_contract_test.rb
+ test/services/dsl/wringer_client_test.rb
+ test/services/dsl_algorithm_runner_test.rb
+ test/services/dsl_contract_test.rb
+ test/test_hygiene/unit_boundary_test.rb
+ ]
+
+ sh "bin/rails test #{files.join(' ')}"
+ end
+end
diff --git a/lib/tasks/distillator_migration.rake b/lib/tasks/distillator_migration.rake
new file mode 100644
index 00000000..2f3405b5
--- /dev/null
+++ b/lib/tasks/distillator_migration.rake
@@ -0,0 +1,16 @@
+namespace :distillator do
+ desc "Report Distillator migration readiness from local code and fixtures"
+ task migration_status: :environment do
+ status = Distillator::MigrationStatus.call
+
+ status.fetch(:checks).each do |label, passed|
+ puts "#{label}: #{passed ? 'pass' : 'fail'}"
+ end
+
+ bypasses = status.fetch(:legacy_bypasses)
+ puts "Known remaining legacy bypasses: #{bypasses[:count]}"
+ bypasses[:files].each do |file|
+ puts " - #{file}"
+ end
+ end
+end
diff --git a/lib/tasks/distillator_transition.rake b/lib/tasks/distillator_transition.rake
new file mode 100644
index 00000000..e27804f9
--- /dev/null
+++ b/lib/tasks/distillator_transition.rake
@@ -0,0 +1,50 @@
+namespace :distillator do
+ namespace :transition do
+ desc "Run second-production preflight checks (usage: bin/rails distillator:transition:preflight)"
+ task preflight: :environment do
+ result = Distillator::ProductionPreflight.call
+
+ result.entries.each do |entry|
+ puts entry.to_output
+ end
+
+ unless result.ok?
+ puts "Preflight: FAILED"
+ raise Distillator::ProductionPreflight::Failure, "Second-production preflight failed"
+ end
+ end
+
+ desc "Record Fetch, Statements, and Export checks for a website (usage: bin/rails distillator:transition:check[website_id])"
+ task :check, [:website_id] => :environment do |_task, args|
+ website_id = args[:website_id] || ENV["website_id"] || ENV["WEBSITE_ID"]
+ raise ArgumentError, "Missing website_id. Usage: bin/rails distillator:transition:check[website_id]" if website_id.blank?
+
+ result = Distillator::TransitionCheckRunner.call(website: website_id)
+ status = Distillator::TransitionCheck.call(website: result.website)
+
+ puts "Fetch: #{operator_check_label(status.fetch)}"
+ puts "Statements: #{operator_check_label(status.statements)}"
+ puts "Export: #{operator_check_label(status.export)}"
+ puts "Overall: #{operator_status_label(status.status)}"
+ puts "Open: /distillator/shadow_report/#{result.website.id}"
+ end
+ end
+end
+
+def operator_check_label(status)
+ {
+ passed: "Passed",
+ failed: "Failed",
+ missing: "Missing",
+ stale: "Stale"
+ }.fetch(status.to_sym, status.to_s.humanize)
+end
+
+def operator_status_label(status)
+ {
+ ready: "Ready",
+ review: "Needs review",
+ blocked: "Blocked",
+ not_checked: "Not checked"
+ }.fetch(status.to_sym, status.to_s.humanize)
+end
diff --git a/lib/tasks/export_artsdata.rake b/lib/tasks/export_artsdata.rake
new file mode 100644
index 00000000..787f4ef6
--- /dev/null
+++ b/lib/tasks/export_artsdata.rake
@@ -0,0 +1,23 @@
+require "fileutils"
+
+namespace :export do
+ desc "Export Artsdata JSON-LD baseline for a website (usage: rake export:artsdata seedurl=example.com)"
+ task artsdata: :environment do
+ seedurl = ENV["seedurl"] || ENV["SEEDURL"]
+ raise ArgumentError, "Missing seedurl. Usage: rake export:artsdata seedurl=example.com" if seedurl.blank?
+
+ started_at = Time.current if ENV["EXPORT_ARTSDATA_TIMING"].present?
+ dump = ExportArtsdataService.call(seedurl: seedurl)
+ puts "Export generation: #{(Time.current - started_at).round(3)}s" if started_at
+
+ sanitized_site = seedurl.downcase.gsub(/[^a-z0-9]+/, "_").gsub(/\A_+|_+\z/, "")
+ sanitized_site = "site" if sanitized_site.blank?
+
+ output_dir = Rails.root.join("data", "migration_baseline")
+ FileUtils.mkdir_p(output_dir)
+ output_path = output_dir.join("#{sanitized_site}.jsonld")
+ File.write(output_path, dump)
+
+ puts "Wrote #{output_path}"
+ end
+end
diff --git a/reload_db.sh b/reload_db.sh
index 5c6532e5..62dfd75a 100755
--- a/reload_db.sh
+++ b/reload_db.sh
@@ -1,4 +1,4 @@
#!/bin/sh
-sudo -u postgres bash restore_condenser.bash latest.dump condenser_upgrade
+sudo -u postgres bash restore_condenser.bash latest.dump footlight-condenser_development
diff --git a/restore_condenser.bash b/restore_condenser.bash
index fbb5c0b7..fed817dd 100755
--- a/restore_condenser.bash
+++ b/restore_condenser.bash
@@ -9,6 +9,8 @@ DUMP_FILE="$1"
DB_NAME="$2"
DB_OWNER="${3:-educa}"
+PG_RESTORE="/usr/lib/postgresql/17/bin/pg_restore"
+
if [[ -z "$DUMP_FILE" || -z "$DB_NAME" ]]; then
echo "Usage: sudo -u postgres bash restore_condenser.bash [db_owner]"
exit 1
@@ -30,7 +32,7 @@ echo ">> Creating schema heroku_ext (if needed)"
psql -d "$DB_NAME" -c "CREATE SCHEMA IF NOT EXISTS heroku_ext;"
echo ">> Restoring database from: $DUMP_FILE (as role: $DB_OWNER)"
-pg_restore --verbose --clean --no-owner --role="$DB_OWNER" --dbname="$DB_NAME" "$DUMP_FILE" | tee restore.log
+$PG_RESTORE --verbose --clean --no-owner --role="$DB_OWNER" --dbname="$DB_NAME" "$DUMP_FILE" | tee restore.log
echo ">> Checking for permission/ownership issues..."
grep -Ei "owner|denied|skipping|execute" restore.log || echo "✅ No permission issues found."
diff --git a/script/distillator_shadow_log_summary.rb b/script/distillator_shadow_log_summary.rb
new file mode 100644
index 00000000..eec47740
--- /dev/null
+++ b/script/distillator_shadow_log_summary.rb
@@ -0,0 +1,152 @@
+#!/usr/bin/env ruby
+# frozen_string_literal: true
+
+require "json"
+
+class DistillatorShadowLogSummary
+ EVENTS = {
+ compare: "distillator.fetch_shadow.compare",
+ shadow_error: "distillator.fetch_shadow.error",
+ ineligible: "distillator.fetch_mode.internal_ineligible",
+ blocked: "DistillatorFetchBlocked"
+ }.freeze
+
+ attr_reader :path
+
+ def self.call(path)
+ new(path).call
+ end
+
+ def initialize(path)
+ @path = path
+ end
+
+ def call
+ summary = empty_summary
+
+ File.foreach(path) do |line|
+ parsed = parse_line(line)
+ event = event_for(line, parsed)
+
+ case event
+ when EVENTS[:compare]
+ summary[:total_comparisons] += 1
+ if matched?(line, parsed)
+ summary[:matched_count] += 1
+ else
+ summary[:mismatch_count] += 1
+ end
+ mismatch_fields(line, parsed).each do |field|
+ summary[:mismatch_fields][field] += 1
+ end
+ when EVENTS[:shadow_error]
+ summary[:shadow_errors_count] += 1
+ when EVENTS[:ineligible]
+ summary[:internal_ineligible_count] += 1
+ end
+
+ summary[:blocked_fetch_count] += 1 if line.include?(EVENTS[:blocked])
+ end
+
+ summary
+ end
+
+ def report
+ summary = call
+ lines = []
+ lines << "Distillator shadow log summary"
+ lines << "File: #{path}"
+ lines << "Total comparisons: #{summary[:total_comparisons]}"
+ lines << "Matched: #{summary[:matched_count]}"
+ lines << "Mismatched: #{summary[:mismatch_count]}"
+ lines << "Shadow errors: #{summary[:shadow_errors_count]}"
+ lines << "Internal ineligible: #{summary[:internal_ineligible_count]}"
+ lines << "Blocked fetches: #{summary[:blocked_fetch_count]}"
+ lines << "Mismatch fields:"
+
+ if summary[:mismatch_fields].empty?
+ lines << " none"
+ else
+ summary[:mismatch_fields].sort.each do |field, count|
+ lines << " #{field}: #{count}"
+ end
+ end
+
+ lines.join("\n")
+ end
+
+ private
+
+ def empty_summary
+ {
+ total_comparisons: 0,
+ matched_count: 0,
+ mismatch_count: 0,
+ mismatch_fields: Hash.new(0),
+ shadow_errors_count: 0,
+ internal_ineligible_count: 0,
+ blocked_fetch_count: 0
+ }
+ end
+
+ def parse_line(line)
+ json_start = line.index("{")
+ return nil unless json_start
+
+ JSON.parse(line[json_start..])
+ rescue JSON::ParserError
+ nil
+ end
+
+ def event_for(line, parsed)
+ parsed_event = value_for(parsed, "event")
+ return parsed_event if parsed_event
+
+ EVENTS.values.find { |event| line.include?(event) }
+ end
+
+ def value_for(parsed, key)
+ return unless parsed.is_a?(Hash)
+
+ parsed[key] || parsed[key.to_sym]
+ end
+
+ def truthy?(value)
+ value == true || value.to_s == "true"
+ end
+
+ def matched?(line, parsed)
+ parsed_value = value_for(parsed, "matched")
+ return truthy?(parsed_value) unless parsed_value.nil?
+
+ line.match?(/:matched=>true\b/)
+ end
+
+ def mismatch_fields(line, parsed)
+ fields = fields_from_json(parsed)
+ return fields if fields.any?
+
+ line.scan(/:field=>(?:"([^"]+)"|:([a-zA-Z0-9_]+)|([a-zA-Z0-9_]+))/).flatten.compact
+ end
+
+ def fields_from_json(parsed)
+ mismatches = value_for(parsed, "mismatches")
+ return [] unless mismatches.is_a?(Array)
+
+ mismatches.filter_map do |mismatch|
+ next unless mismatch.is_a?(Hash)
+
+ value_for(mismatch, "field").to_s
+ end.reject(&:empty?)
+ end
+end
+
+if $PROGRAM_NAME == __FILE__
+ path = ARGV.first
+ unless path
+ warn "Usage: ruby script/distillator_shadow_log_summary.rb PATH"
+ exit 1
+ end
+
+ puts DistillatorShadowLogSummary.new(path).report
+end
diff --git a/shared/source_identity.rb b/shared/source_identity.rb
new file mode 100644
index 00000000..775e1bfd
--- /dev/null
+++ b/shared/source_identity.rb
@@ -0,0 +1,65 @@
+# IMPORTANT:
+# This file must remain identical across:
+# - Console
+# - Condenser
+# - Wringer
+# Do not modify locally — update all copies together.
+
+require "uri"
+require "cgi"
+
+class SourceIdentity
+ def self.from_url(url)
+ new(url)
+ end
+
+ def initialize(original_url)
+ @original_url = original_url.to_s
+ end
+
+ def to_seedurl
+ return nil if @original_url.strip.empty?
+
+ cleaned = @original_url.downcase.strip
+ cleaned = cleaned.gsub(%r{https?://}, "")
+ cleaned = cleaned.gsub("/", "")
+ cleaned = cleaned.gsub(".", "-")
+
+ cleaned
+ end
+
+ def to_canonical_url
+ return "" if @original_url.strip.empty?
+
+ uri = URI.parse(@original_url)
+
+ return "" unless uri.host
+
+ scheme = uri.scheme ? uri.scheme.downcase : "http"
+ host = uri.host.downcase
+ path = uri.path && !uri.path.empty? ? uri.path : "/"
+
+ "#{scheme}://#{host}#{path}"
+ rescue URI::InvalidURIError
+ ""
+ end
+
+ def to_wringer_key
+ return "" if @original_url.strip.empty?
+
+ uri = URI.parse(@original_url)
+
+ return "" unless uri.host
+
+ scheme = (uri.scheme || "http").downcase
+ host = uri.host.downcase
+ path = uri.path && !uri.path.empty? ? uri.path : "/"
+
+ url = "#{scheme}://#{host}#{path}"
+ url += "?#{uri.query}" if uri.query
+
+ CGI.escape(url)
+ rescue URI::InvalidURIError
+ ""
+ end
+end
diff --git a/test/controllers/batch_jobs_controller_test.rb b/test/controllers/batch_jobs_controller_test.rb
deleted file mode 100644
index da08ac67..00000000
--- a/test/controllers/batch_jobs_controller_test.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-require 'test_helper'
-
-class BatchJobsControllerTest < ActionDispatch::IntegrationTest
- # test "the truth" do
- # assert true
- # end
-end
diff --git a/test/controllers/concerns/harmonized_index_params_test.rb b/test/controllers/concerns/harmonized_index_params_test.rb
new file mode 100644
index 00000000..e39f67d4
--- /dev/null
+++ b/test/controllers/concerns/harmonized_index_params_test.rb
@@ -0,0 +1,225 @@
+require "test_helper"
+
+class HarmonizedIndexParamsTest < ActiveSupport::TestCase
+ class Harness
+ include HarmonizedIndexParams
+
+ attr_accessor :params
+
+ def initialize(params = {})
+ @params = ActionController::Parameters.new(params)
+ end
+ end
+
+ test "defaults page to 1" do
+ harness = Harness.new(page: "0")
+
+ assert_equal 1, harness.send(:harmonized_index_page)
+ end
+
+ test "caps per page" do
+ harness = Harness.new(per_page: "500")
+
+ assert_equal 100, harness.send(:harmonized_index_per_page, default: 25, max: 100)
+ end
+
+ test "allows only asc and desc" do
+ harness = Harness.new(direction: "asc")
+
+ assert_equal "asc", harness.send(:harmonized_index_direction)
+ end
+
+ test "falls back on invalid direction" do
+ harness = Harness.new(direction: "sideways")
+
+ assert_equal "desc", harness.send(:harmonized_index_direction, default: "desc")
+ end
+
+ test "allows only declared sort columns" do
+ harness = Harness.new(sort: "updated_at")
+
+ assert_equal "updated_at", harness.send(:harmonized_index_sort, allowed: %w[name updated_at], default: "name")
+ end
+
+ test "falls back on invalid sort" do
+ harness = Harness.new(sort: "drop table")
+
+ assert_equal "name", harness.send(:harmonized_index_sort, allowed: %w[name updated_at], default: "name")
+ end
+
+ test "drops blank filter params" do
+ harness = Harness.new(term: " ", language: "", website_id: nil)
+
+ assert_equal({}, harness.send(:harmonized_index_filters, allowed: %i[term language website_id]))
+ end
+
+ test "preserves valid filter params" do
+ harness = Harness.new(term: "needle", language: "en", website_id: "4")
+
+ assert_equal(
+ { term: "needle", language: "en", website_id: "4" },
+ harness.send(:harmonized_index_filters, allowed: %i[term language website_id])
+ )
+ end
+
+ test "builds normalized index params from declared keys" do
+ harness = Harness.new(
+ page: "-2",
+ per_page: "500",
+ sort: "updated_at",
+ direction: "sideways",
+ term: "needle",
+ language: "en",
+ website_id: ""
+ )
+
+ normalized = harness.send(
+ :harmonized_index_params,
+ allowed_filters: %i[term language website_id],
+ allowed_sorts: %w[name updated_at],
+ default_sort: "name",
+ default_direction: "desc",
+ default_per_page: 25,
+ max_per_page: 100
+ )
+
+ assert_equal(
+ {
+ filters: { term: "needle", language: "en" },
+ sort: "updated_at",
+ direction: "desc",
+ page: 1,
+ per_page: 100
+ },
+ normalized
+ )
+ end
+
+ test "canonical params preserve declared route params" do
+ harness = Harness.new
+ index_params = {
+ filters: { term: "needle" },
+ sort: "updated_at",
+ direction: "desc",
+ page: 2,
+ per_page: 50
+ }
+
+ canonical = harness.send(
+ :harmonized_index_canonical_params,
+ index_params,
+ default_sort: "name",
+ default_direction: "asc",
+ default_per_page: 25,
+ preserve: { seedurl: "one" }
+ )
+
+ assert_equal(
+ {
+ "seedurl" => "one",
+ "term" => "needle",
+ "sort" => "updated_at",
+ "direction" => "desc",
+ "page" => "2",
+ "per_page" => "50"
+ },
+ canonical
+ )
+ end
+
+ test "canonical params omit default sort direction page and per_page" do
+ harness = Harness.new
+ index_params = {
+ filters: { term: "needle" },
+ sort: "name",
+ direction: "asc",
+ page: 1,
+ per_page: 25
+ }
+
+ canonical = harness.send(
+ :harmonized_index_canonical_params,
+ index_params,
+ default_sort: "name",
+ default_direction: "asc",
+ default_per_page: 25
+ )
+
+ assert_equal({ "term" => "needle" }, canonical)
+ end
+
+ test "canonical params drop blank filters" do
+ harness = Harness.new
+ index_params = {
+ filters: { term: "needle", language: "", website_id: nil },
+ sort: "name",
+ direction: "asc",
+ page: 1,
+ per_page: 25
+ }
+
+ canonical = harness.send(
+ :harmonized_index_canonical_params,
+ index_params,
+ default_sort: "name",
+ default_direction: "asc",
+ default_per_page: 25
+ )
+
+ assert_equal({ "term" => "needle" }, canonical)
+ end
+
+ test "canonical params can exclude route scoped filters from query string" do
+ harness = Harness.new
+ index_params = {
+ filters: { seedurl: "one", term: "needle" },
+ sort: "name",
+ direction: "asc",
+ page: 1,
+ per_page: 25
+ }
+
+ canonical = harness.send(
+ :harmonized_index_canonical_params,
+ index_params,
+ default_sort: "name",
+ default_direction: "asc",
+ default_per_page: 25,
+ exclude_filters: [:seedurl]
+ )
+
+ assert_equal({ "term" => "needle" }, canonical)
+ end
+
+ test "raw params include only declared filters and preserved params" do
+ harness = Harness.new(
+ term: "needle",
+ language: "en",
+ ignored: "value",
+ page: "2",
+ per_page: "10",
+ seedurl: "one",
+ sort: "updated_at",
+ direction: "desc"
+ )
+
+ raw = harness.send(
+ :harmonized_index_raw_params,
+ allowed_filters: %i[term language],
+ preserve: %w[seedurl sort direction page per_page]
+ )
+
+ assert_equal(
+ {
+ "term" => "needle",
+ "language" => "en",
+ "seedurl" => "one",
+ "sort" => "updated_at",
+ "direction" => "desc",
+ "page" => "2",
+ "per_page" => "10"
+ },
+ raw
+ )
+ end
+end
diff --git a/test/controllers/distillator/cache_controller_test.rb b/test/controllers/distillator/cache_controller_test.rb
new file mode 100644
index 00000000..b77ae104
--- /dev/null
+++ b/test/controllers/distillator/cache_controller_test.rb
@@ -0,0 +1,1615 @@
+require "test_helper"
+
+class Distillator::CacheControllerTest < ActionDispatch::IntegrationTest
+ setup do
+ Distillator::FetchCache.delete_all
+ @old_refresh_ui = ENV["DISTILLATOR_CACHE_REFRESH_UI"]
+ @old_fetch_mode = ENV["DISTILLATOR_FETCH_MODE"]
+ end
+
+ teardown do
+ ENV["DISTILLATOR_CACHE_REFRESH_UI"] = @old_refresh_ui
+ ENV["DISTILLATOR_FETCH_MODE"] = @old_fetch_mode
+ end
+
+ test "index lists serialized cache rows without fetching" do
+ cache = create_cache
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache.json"
+
+ assert_response :success
+ payload = JSON.parse(@response.body)
+ assert_equal 1, payload.length
+ assert_equal cache.id, payload.first["id"]
+ assert_equal cache.uri_key, payload.first["uri"]
+ assert_equal "1", @response.headers["X-Page"]
+ assert_equal "50", @response.headers["X-Per-Page"]
+ end
+
+ test "friendly condenser cache index alias works like distillator cache index" do
+ create_cache
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/condenser/cache"
+
+ assert_response :success
+ assert_match "Condenser Cache", @response.body
+ end
+
+ test "friendly condenser cache compare alias works like distillator cache compare" do
+ Distillator::FetchCacheStore.expects(:fetch).never
+ Distillator::CacheCompare.expects(:call).with do |kwargs|
+ assert_equal "http://example.org/page", kwargs[:uri]
+ assert_nil kwargs[:include_fragment]
+ assert_nil kwargs[:comparison_policy]
+ true
+ end.returns(
+ {
+ uri: "http://example.org/page",
+ uri_key: "http%3A%2F%2Fexample.org%2Fpage",
+ comparison_policy: :operator,
+ legacy_cache: { html: "legacy" },
+ legacy_source: "injected_lookup",
+ legacy_lookup_error: nil,
+ condenser_cache: { html: "condenser" },
+ condenser_source: "local_fetch_cache",
+ distillator_cache: { html: "condenser" },
+ distillator_source: "local_fetch_cache",
+ diffs: { title: { same: true, classification: :metadata_only, legacy: "Title", condenser: "Title" } },
+ summary: { outcome: "pass", primary_reason: "Wringer and Condenser match on the compared fields." },
+ missing: { legacy: false, condenser: false, distillator: false }
+ }
+ )
+
+ get "/condenser/cache/compare", params: { uri: "http://example.org/page" }
+
+ assert_response :success
+ assert_match "Cache Comparison", @response.body
+ assert_match "Comparison policy", @response.body
+ assert_match "Detailed comparison", @response.body
+ assert_match "Raw payloads", @response.body
+ assert_match "Condenser source", @response.body
+ assert_match "Compare extracted statements", @response.body
+ assert_no_match "Distillator", visible_text(@response.body)
+ end
+
+ test "cache inspection mode keeps refresh ui disabled without explicit flag" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "internal"
+ ENV["DISTILLATOR_CACHE_REFRESH_UI"] = nil
+ create_cache
+ assert_read_only_page_does_not_fetch
+
+ get "/distillator/cache"
+
+ assert_response :success
+ assert_match "Condenser Cache", @response.body
+ assert_match "Scrape date", @response.body
+ assert_match "Successful refresh", @response.body
+ assert_match "Cache inspection mode. Refresh actions are not available in this environment.", @response.body
+ assert_no_match "Fetch URL", @response.body
+ assert_no_match %r{button[^>]*>Direct fetch}, @response.body
+ assert_match "Apply filters", @response.body
+ assert_match "Quick filters", @response.body
+ assert_match "Advanced filters", @response.body
+ assert_match "URI / Name", @response.body
+ assert_match "Health", @response.body
+ assert_select 'details[data-operator-context-card]', 0
+ end
+
+ test "distillator cache index does not fetch" do
+ create_cache
+ assert_read_only_page_does_not_fetch
+
+ get "/distillator/cache"
+
+ assert_response :success
+ end
+
+ test "cache index uses condenser cache demo-facing language" do
+ create_cache(uri: "https://fixtures.example/cache/simple-title")
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache"
+
+ assert_response :success
+ assert_match "Condenser Cache", visible_text(@response.body)
+ assert_no_match "Distillator", visible_text(@response.body)
+ assert_no_match "DISTILLATOR_CACHE_REFRESH_UI", visible_text(@response.body)
+ assert_no_match "/distillator/cache", visible_text(@response.body)
+ assert_no_match "distillator-dsl", visible_text(@response.body)
+ end
+
+ test "explicit refresh ui flag enables operational cache ui" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "internal"
+ ENV["DISTILLATOR_CACHE_REFRESH_UI"] = "true"
+ create_cache
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache"
+
+ assert_response :success
+ text = visible_text(@response.body)
+ assert_match "Condenser Cache", text
+ assert_match "Fetch URL", text
+ assert_match "Direct fetch", text
+ assert_match "Rendered fetch", text
+ assert_match "POST fetch", text
+ assert_no_match "Read-only cache inspection only", text
+ assert_no_match "Refresh actions are disabled", text
+ assert_no_match "DISTILLATOR_CACHE_REFRESH_UI", text
+ end
+
+ test "cache refresh ui is not enabled just because fetch mode current defaults to internal" do
+ ENV["DISTILLATOR_FETCH_MODE"] = nil
+ ENV["DISTILLATOR_CACHE_REFRESH_UI"] = nil
+ create_cache
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache"
+
+ assert_response :success
+ text = visible_text(@response.body)
+ assert_no_match "Fetch URL", text
+ assert_match "Cache inspection mode. Refresh actions are not available in this environment.", text
+ end
+
+ test "html index renders fetch url panel with one uri field and three actions" do
+ create_cache
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ ENV["DISTILLATOR_CACHE_REFRESH_UI"] = "true"
+ get "/distillator/cache"
+
+ assert_response :success
+ assert_select 'form[action="/distillator/cache/fetch"][method="post"] input[type="text"][name="uri"]', 1
+ assert_select 'form[action="/distillator/cache/fetch"][method="post"] button[name="fetch_kind"][value="normal"]', text: "Direct fetch"
+ assert_select 'form[action="/distillator/cache/fetch"][method="post"] button[name="fetch_kind"][value="rendered"]', text: "Rendered fetch"
+ assert_select 'form[action="/distillator/cache/fetch"][method="post"] button[name="fetch_kind"][value="post"]', text: "POST fetch"
+ end
+
+ test "compatibility api copy is operational and does not mention preview endpoint" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "internal"
+ create_cache
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache"
+
+ assert_response :success
+ text = visible_text(@response.body)
+ assert_match "Compatibility API", text
+ assert_match "Legacy-compatible endpoint", text
+ assert_match "Cached Wringer-compatible JSON", text
+ assert_match "Cache fetch actions", text
+ assert_no_match "Cache preview endpoint", text
+ end
+
+ test "index shows warning when http succeeded but content failed" do
+ create_cache(
+ uri: "https://www.ovation.ca/event",
+ html: "last good",
+ body: "last good",
+ signals: {
+ "network_status" => "ok",
+ "content_type" => "html",
+ "transport_success" => true,
+ "content_success" => false,
+ "primary_issue_key" => "redirect_to_listing",
+ "primary_issue_label" => "Redirect to listing",
+ "last_good_preserved_failure" => true
+ },
+ hints: ["redirect_to_listing", "last_good_preserved_failure"],
+ http_response_code: 200,
+ successful_refresh: 1.day.ago,
+ scrape_date: Time.zone.now
+ )
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache"
+
+ assert_response :success
+ text = visible_text(@response.body)
+ assert_select "span.cache-health-preserved", text: "Preserved"
+ assert_match "HTTP 200 but content failed: Redirect to listing", text
+ assert_match "Transport: success", text
+ assert_match "Content: failed", text
+ assert_match "Blocking issue: redirect_to_listing", text
+ assert_match "Last good content preserved: yes", text
+ end
+
+ test "index renders dominant operator health states across cache rows" do
+ create_cache(uri: "https://example.org/healthy")
+ create_cache(
+ uri: "https://example.org/preserved",
+ signals: {
+ "network_status" => "ok",
+ "content_type" => "html",
+ "transport_success" => true,
+ "content_success" => false,
+ "primary_issue_key" => "redirect_to_listing",
+ "primary_issue_label" => "Redirect to listing",
+ "last_good_preserved_failure" => true
+ },
+ hints: ["redirect_to_listing", "last_good_preserved_failure"],
+ http_response_code: 200,
+ successful_refresh: 1.day.ago,
+ scrape_date: Time.zone.now
+ )
+ create_cache(
+ uri: "https://example.org/warning",
+ signals: {
+ "network_status" => "ok",
+ "content_type" => "html",
+ "transport_success" => true,
+ "content_success" => false,
+ "primary_issue_key" => "queue_it",
+ "primary_issue_label" => "Queue-it waiting room"
+ },
+ hints: ["queue_it", "waiting_room"],
+ http_response_code: 200,
+ successful_refresh: Time.zone.now,
+ scrape_date: Time.zone.now
+ )
+ create_cache(
+ uri: "https://example.org/failed",
+ signals: { "network_status" => "failed", "content_type" => "html" },
+ http_response_code: nil
+ )
+ create_cache(
+ uri: "https://example.org/unknown",
+ html: nil,
+ body: nil,
+ signals: {},
+ http_response_code: nil,
+ scrape_date: nil,
+ successful_refresh: nil
+ )
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache"
+
+ assert_response :success
+ assert_select "span.cache-health-healthy", text: "Healthy"
+ assert_select "span.cache-health-preserved", text: "Preserved"
+ assert_select "span.cache-health-warning", text: "Warning"
+ assert_select "span.cache-health-failed", text: "Failed"
+ assert_select "span.cache-health-unknown", text: "Unknown"
+ end
+
+ test "show page surfaces transport and content diagnostics for failed content" do
+ cache = create_cache(
+ uri: "https://www.ovation.ca/event",
+ html: "last good",
+ body: "last good",
+ signals: {
+ "network_status" => "ok",
+ "content_type" => "html",
+ "transport_success" => true,
+ "content_success" => false,
+ "primary_issue_key" => "redirect_to_listing",
+ "primary_issue_label" => "Redirect to listing",
+ "last_good_preserved_failure" => true
+ },
+ hints: ["redirect_to_listing", "last_good_preserved_failure"],
+ http_response_code: 200,
+ successful_refresh: 1.day.ago,
+ scrape_date: Time.zone.now
+ )
+ assert_read_only_page_does_not_fetch
+
+ get "/distillator/cache/#{cache.id}"
+
+ assert_response :success
+ assert_select 'details[data-operator-context-card]'
+ assert_select 'details[data-context-domain="status"]'
+ assert_select 'details[data-context-domain="actions"]'
+ assert_select 'details[data-context-domain="details"]'
+ text = visible_text(@response.body)
+ assert_select "span.cache-health-preserved", text: "Preserved"
+ assert_match "Operator health:", text
+ assert_match "HTTP 200 but content failed: Redirect to listing", text
+ assert_match "Transport: success", text
+ assert_match "Content: failed", text
+ assert_match "Blocking issue: redirect_to_listing", text
+ assert_match "Last good preserved: yes", text
+ end
+
+ test "cache show keeps condenser cache demo-facing language" do
+ cache = create_cache(uri: "https://fixtures.example/cache/show-demo-facing")
+ assert_read_only_page_does_not_fetch
+
+ get "/distillator/cache/#{cache.id}"
+
+ assert_response :success
+ text = visible_text(@response.body)
+ assert_match "Condenser Cache", text
+ assert_no_match "new cache", text
+ assert_no_match "phase i", text.downcase
+ assert_no_match "preview only", text.downcase
+ assert_no_match "internal", text.downcase
+ end
+
+ test "show page renders failed operator health for blocked cache state" do
+ cache = create_cache(
+ uri: "https://example.org/blocked",
+ html: nil,
+ body: nil,
+ signals: {
+ "network_status" => "blocked",
+ "error_type" => "DistillatorFetchBlocked",
+ "content_type" => "html",
+ "transport_success" => false
+ },
+ hints: ["blocked"],
+ http_response_code: nil,
+ successful_refresh: nil,
+ scrape_date: Time.zone.now
+ )
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache/#{cache.id}"
+
+ assert_response :success
+ assert_select "span.cache-health-failed", text: "Failed"
+ end
+
+ test "show page links normalized url, keeps internal final url unlinked, and shows fetch rejection diagnostics" do
+ website = Website.create!(
+ name: "Cache & Safety ",
+ seedurl: "cache-safety",
+ graph_name: "https://example.org/cache-safety",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+ cache = create_cache(
+ uri: "https://centredecreationdiffusiondegaspe.com/programmation/koros-experiences-vr/",
+ html: nil,
+ body: nil,
+ signals: {
+ "network_status" => "ok",
+ "content_type" => "html",
+ "transport_success" => true,
+ "content_success" => false,
+ "policy_action" => "abort_update",
+ "content_rejected" => true,
+ "fetched_body_state" => "non_empty",
+ "fetched_body_bytes" => 2048,
+ "stored_body_state" => "not_stored",
+ "stored_body_bytes" => 0,
+ "storage_decision" => "abort_update",
+ "cache_body_empty_after_abort" => true,
+ "primary_issue_key" => "generic_error_text",
+ "primary_issue_label" => "Generic error text observed",
+ "primary_issue_match" => {
+ "source" => "body_text",
+ "pattern" => "Une erreur est survenue",
+ "snippet" => "Une erreur est survenue. Veuillez reessayer."
+ }
+ },
+ hints: ["generic_error_text"],
+ final_url: "http://localhost:3000/websites/wring.json?uri=https://centredecreationdiffusiondegaspe.com/programmation/koros-experiences-vr/",
+ http_response_code: 200,
+ successful_refresh: nil
+ )
+ assert_read_only_page_does_not_fetch
+ get website_url(website)
+ assert_response :success
+
+ assert_read_only_page_does_not_fetch
+ get "/distillator/cache/#{cache.id}"
+
+ assert_response :success
+ assert_match %r{all websites\s*\|\s*]+href="/websites/#{website.id}">Cache & Safety <Site> \s*\|\s*]+href="/webpages\?seedurl=#{Regexp.escape(website.seedurl)}"}, @response.body
+ assert_select "a[href='https://centredecreationdiffusiondegaspe.com/programmation/koros-experiences-vr/']", text: "https://centredecreationdiffusiondegaspe.com/programmation/koros-experiences-vr/"
+ assert_no_match %r{ ]+href="http://localhost:3000/websites/wring\.json\?uri=}m, @response.body
+ text = visible_text(@response.body)
+ assert_match "Content rejected by policy", text
+ assert_match "Fetched response: HTTP 200 HTML", text
+ assert_match "Fetched body: non-empty (2,048 bytes)", text
+ assert_match "Stored cache body: empty because the update was aborted", text
+ assert_match "Storage decision: abort_update", text
+ assert_match "Primary issue match: source: body_text | pattern: Une erreur est survenue | snippet: Une erreur est survenue. Veuillez reessayer.", text
+ assert_match "HTTP 200 returned HTML, but content was rejected before cache storage: Generic error text observed", text
+ end
+
+ test "pages without website context keep the default top nav" do
+ cache = create_cache(uri: "https://example.org/no-context")
+ assert_read_only_page_does_not_fetch
+
+ get "/distillator/cache/#{cache.id}"
+
+ assert_response :success
+ assert_match %r{all websites \s*\|\s*]+href="/webpages"}, @response.body
+ end
+
+ test "show page keeps non http normalized urls as escaped text instead of external links" do
+ cache = create_cache(uri: "footlight:event:123", final_url: "footlight:event:123")
+ assert_read_only_page_does_not_fetch
+
+ get "/distillator/cache/#{cache.id}"
+
+ assert_response :success
+ assert_match "footlight:event:123", @response.body
+ assert_no_match %r{ ]+href="footlight:event:123"}m, @response.body
+ end
+
+ test "compatibility view renders wringer style table headers and preserves post refresh actions" do
+ create_cache
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ ENV["DISTILLATOR_CACHE_REFRESH_UI"] = "true"
+ get "/distillator/cache", params: { view: "parity", term: "needle" }
+
+ assert_response :success
+ assert_match "Compatibility view", @response.body
+ assert_match "Uri", @response.body
+ assert_match "HTTP Response", @response.body
+ assert_match "Scrape Date", @response.body
+ assert_match "Last Successful Refresh", @response.body
+ assert_match "Page Name", @response.body
+ assert_match "Html", @response.body
+ assert_match "Operations", @response.body
+ assert_match "Fetch actions", @response.body
+ assert_select 'form[action="/distillator/cache/fetch"][method="post"]', minimum: 1
+ end
+
+ test "top level normal fetch calls fetch cache store through command semantics" do
+ ENV["DISTILLATOR_CACHE_REFRESH_UI"] = "true"
+
+ fetch_result = OpenStruct.new(normalized_url: "http://example.org/events", http_response_code: 200, cache_reason: "force_scrape", fetch_path: "legacy", html: "body", cache: nil)
+ Distillator::FetchCacheStore.expects(:fetch).with do |kwargs|
+ assert_equal "http://example.org/events", kwargs[:uri]
+ assert_equal true, kwargs[:force_scrape]
+ assert_equal false, kwargs[:use_phantomjs]
+ assert_equal false, kwargs[:json_post]
+ true
+ end.returns(fetch_result)
+
+ post "/distillator/cache/fetch", params: { uri: "http://example.org/events", fetch_kind: "normal" }
+
+ assert_redirected_to "/distillator/cache"
+ follow_redirect!
+ assert_match "Fetched Direct fetch for http://example.org/events", @response.body
+ assert_match "HTTP 200", @response.body
+ assert_match "path legacy", @response.body
+ assert_match "cache refresh forced", @response.body
+ assert_match "HTML written", @response.body
+ end
+
+ test "successful fetch shows structured last action result on index" do
+ ENV["DISTILLATOR_CACHE_REFRESH_UI"] = "true"
+
+ fetch_result = OpenStruct.new(
+ normalized_url: "http://example.org/events",
+ http_response_code: 200,
+ cache_reason: "force_scrape",
+ fetch_path: "legacy",
+ html: "body",
+ cache: nil
+ )
+
+ Distillator::FetchCacheStore.expects(:fetch).returns(fetch_result)
+
+ post "/distillator/cache/fetch", params: {
+ uri: "http://example.org/events",
+ fetch_kind: "normal"
+ }
+
+ assert_redirected_to "/distillator/cache"
+ follow_redirect!
+
+ assert_response :success
+ assert_match "Last Action Result", @response.body
+ assert_match "Fetch mode: Direct fetch", @response.body
+ assert_match "Normalized URL: http://example.org/events", @response.body
+ assert_match "HTTP response code: 200", @response.body
+ assert_match "Fetch path: legacy", @response.body
+ assert_match "Cache reason: force_scrape", @response.body
+ assert_match "HTML result: HTML written", @response.body
+ end
+
+ test "successful rendered fetch shows rendered fetch label in last action result" do
+ ENV["DISTILLATOR_CACHE_REFRESH_UI"] = "true"
+
+ Distillator::FetchCacheStore.expects(:fetch).returns(
+ OpenStruct.new(
+ normalized_url: "http://example.org/rendered",
+ http_response_code: 200,
+ cache_reason: "force_scrape",
+ fetch_path: "legacy",
+ html: "rendered",
+ cache: nil
+ )
+ )
+
+ post "/distillator/cache/fetch", params: {
+ uri: "http://example.org/rendered",
+ fetch_kind: "rendered"
+ }
+
+ follow_redirect!
+
+ assert_match "Last Action Result", @response.body
+ assert_match "Fetch mode: Rendered fetch", @response.body
+ end
+
+ test "top level fetch defaults missing fetch kind to normal" do
+ ENV["DISTILLATOR_CACHE_REFRESH_UI"] = "true"
+
+ Distillator::FetchCacheStore.expects(:fetch).with do |kwargs|
+ assert_equal false, kwargs[:use_phantomjs]
+ assert_equal false, kwargs[:json_post]
+ true
+ end.returns(OpenStruct.new(normalized_url: "http://example.org/default", http_response_code: 200, cache_reason: "force_scrape", fetch_path: "cache", html: "", cache: nil))
+
+ post "/distillator/cache/fetch", params: { uri: "http://example.org/default" }
+
+ assert_redirected_to "/distillator/cache"
+ end
+
+ test "top level rendered fetch calls phantomjs and include fragment" do
+ ENV["DISTILLATOR_CACHE_REFRESH_UI"] = "true"
+
+ Distillator::FetchCacheStore.expects(:fetch).with do |kwargs|
+ assert_equal "http://example.org/rendered", kwargs[:uri]
+ assert_equal true, kwargs[:force_scrape]
+ assert_equal true, kwargs[:use_phantomjs]
+ assert_equal true, kwargs[:include_fragment]
+ assert_equal false, kwargs[:json_post]
+ true
+ end.returns(OpenStruct.new(normalized_url: "http://example.org/rendered", cache: nil))
+
+ post "/distillator/cache/fetch", params: { uri: "http://example.org/rendered", fetch_kind: "rendered" }
+
+ assert_redirected_to "/distillator/cache"
+ end
+
+ test "top level post fetch calls json_post" do
+ ENV["DISTILLATOR_CACHE_REFRESH_UI"] = "true"
+
+ Distillator::FetchCacheStore.expects(:fetch).with do |kwargs|
+ assert_equal "http://example.org/post", kwargs[:uri]
+ assert_equal true, kwargs[:force_scrape]
+ assert_equal true, kwargs[:json_post]
+ true
+ end.returns(OpenStruct.new(normalized_url: "http://example.org/post", cache: nil))
+
+ post "/distillator/cache/fetch", params: { uri: "http://example.org/post", fetch_kind: "post" }
+
+ assert_redirected_to "/distillator/cache"
+ end
+
+ test "cache index does not expose preview first actions in operational mode" do
+ ENV["DISTILLATOR_CACHE_REFRESH_UI"] = "true"
+ cache = create_cache
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache"
+
+ assert_response :success
+ text = visible_text(@response.body)
+ assert_match "Compare", text
+ assert_match "Direct fetch", text
+ assert_match "Rendered fetch", text
+ assert_match "POST fetch", text
+ assert_match "View", text
+ assert_match "Diagnose", text
+ assert_match "Refresh", text
+ assert_no_match "Preview Direct fetch", text
+ assert_no_match "Preview Rendered fetch", text
+ assert_no_match "Preview POST", text
+ assert_no_match "Cache preview endpoint", text
+ assert_no_match "Preview JSON endpoint", text
+ assert_match "/distillator/cache/#{cache.id}", @response.body
+ end
+
+ test "show page does not expose preview first actions in operational mode" do
+ ENV["DISTILLATOR_CACHE_REFRESH_UI"] = "true"
+ cache = create_cache
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache/#{cache.id}"
+
+ assert_response :success
+ text = visible_text(@response.body)
+ assert_match "Condenser Cache Detail", text
+ assert_no_match "Preview Direct fetch", text
+ assert_no_match "Preview Rendered fetch", text
+ assert_no_match "Preview POST", text
+ assert_no_match "Cache preview endpoint", text
+ assert_select 'form[action="/distillator/cache/fetch"][method="post"] input[type="submit"][value="Direct fetch"]', 1
+ assert_select 'form[action="/distillator/cache/fetch"][method="post"] input[type="submit"][value="Rendered fetch"]', 1
+ assert_select 'form[action="/distillator/cache/fetch"][method="post"] input[type="submit"][value="POST fetch"]', 1
+ end
+
+ test "show page includes copyable compatibility links and omits edit destroy" do
+ cache = create_cache
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache/#{cache.id}"
+
+ assert_response :success
+ assert_match "Compatibility API", @response.body
+ assert_match "/websites/wring.json?uri=http%3A%2F%2Fexample.org%2Fcached", @response.body
+ assert_match "Cached Wringer-compatible JSON", @response.body
+ assert_match "Open raw cached HTML", @response.body
+ assert_match "Open cached Wringer-compatible JSON", @response.body
+ assert_match "Open cache comparison", @response.body
+ assert_match "data-copy-url", @response.body
+ assert_no_match ">Edit<", @response.body
+ assert_no_match ">Destroy<", @response.body
+ assert_match "Refresh instead of edit or destroy", @response.body
+ end
+
+ test "cache show page uses condenser cache demo-facing language" do
+ cache = create_cache(uri: "https://fixtures.example/cache/simple-title")
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache/#{cache.id}"
+
+ assert_response :success
+ assert_match "Condenser Cache Detail", visible_text(@response.body)
+ assert_no_match "Distillator", visible_text(@response.body)
+ assert_no_match "/distillator/cache", visible_text(@response.body)
+ end
+
+ test "row wring action preserves filter context and calls normal fetch" do
+ cache = create_cache(uri: "http://example.org/filtered")
+ ENV["DISTILLATOR_CACHE_REFRESH_UI"] = "true"
+
+ Distillator::FetchCacheStore.expects(:fetch).with do |kwargs|
+ assert_equal cache.normalized_url, kwargs[:uri]
+ assert_equal true, kwargs[:force_scrape]
+ assert_equal false, kwargs[:use_phantomjs]
+ assert_equal false, kwargs[:json_post]
+ true
+ end.returns(OpenStruct.new(normalized_url: cache.normalized_url, cache: nil))
+
+ post "/distillator/cache/fetch", params: {
+ uri: cache.normalized_url,
+ fetch_kind: "normal",
+ term: "needle",
+ health: "healthy",
+ status_group: "2xx",
+ has_html: "true",
+ content_type: "html",
+ network_status: "ok",
+ redirected: "false",
+ hint: "json",
+ sort: "updated_at",
+ direction: "desc",
+ page: "2",
+ per_page: "25"
+ }
+
+ assert_redirected_to "/distillator/cache?content_type=html&direction=desc&has_html=true&health=healthy&hint=json&network_status=ok&page=2&per_page=25&redirected=false&sort=updated_at&status_group=2xx&term=needle"
+ end
+
+ test "invalid fetch returns to filtered list with error and does not call fetch" do
+ ENV["DISTILLATOR_CACHE_REFRESH_UI"] = "true"
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ post "/distillator/cache/fetch", params: { uri: "", fetch_kind: "normal", term: "needle", page: "3" }
+
+ assert_redirected_to "/distillator/cache?page=3&term=needle"
+ follow_redirect!
+ assert_match "URI is invalid", @response.body
+ assert_match "Direct fetch for", @response.body
+ assert_match "No fetch performed", @response.body
+ assert_no_match "Last Action Result", @response.body
+ end
+
+ test "failed fetch does not show last action result" do
+ ENV["DISTILLATOR_CACHE_REFRESH_UI"] = "true"
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ post "/distillator/cache/fetch", params: {
+ uri: "",
+ fetch_kind: "normal"
+ }
+
+ follow_redirect!
+
+ assert_match "No fetch performed", @response.body
+ assert_no_match "Last Action Result", @response.body
+ end
+
+ test "successful fetch preserves filters and shows last action result" do
+ ENV["DISTILLATOR_CACHE_REFRESH_UI"] = "true"
+
+ Distillator::FetchCacheStore.expects(:fetch).returns(
+ OpenStruct.new(
+ normalized_url: "http://example.org/filtered",
+ http_response_code: 200,
+ cache_reason: "force_scrape",
+ fetch_path: "legacy",
+ html: "body",
+ cache: nil
+ )
+ )
+
+ post "/distillator/cache/fetch", params: {
+ uri: "http://example.org/filtered",
+ fetch_kind: "normal",
+ term: "needle",
+ health: "healthy",
+ page: "2",
+ per_page: "25"
+ }
+
+ assert_redirected_to "/distillator/cache?health=healthy&page=2&per_page=25&term=needle"
+ follow_redirect!
+
+ assert_match "Last Action Result", @response.body
+ assert_select 'input[name="term"][value=?]', "needle"
+ end
+
+ test "html index renders current filters without fetching" do
+ create_cache
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache", params: {
+ term: "needle",
+ http_response_code: "404",
+ has_html: "false",
+ network_status: "failed",
+ sort: "updated_at",
+ direction: "desc"
+ }
+
+ assert_redirected_to "/distillator/cache?has_html=false&http_response_code=404&network_status=failed&term=needle"
+
+ follow_redirect!
+ assert_response :success
+ assert_select 'input[name="term"][value=?]', "needle"
+ assert_select 'input[name="http_response_code"][value=?]', "404"
+ assert_select 'select[name="has_html"] option[selected="selected"][value=?]', "false"
+ assert_select 'input[name="network_status"][value=?]', "failed"
+ assert_select 'input[name="sort"][value=?]', "updated_at"
+ assert_select 'input[name="direction"][value=?]', "desc"
+ end
+
+ test "html index filter form intentionally preserves current sort and direction like websites" do
+ create_cache
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache", params: {
+ term: "needle",
+ sort: "http_response_code",
+ direction: "asc"
+ }
+
+ assert_response :success
+ assert_select 'form[action="/distillator/cache"][method="get"] input[type="hidden"][name="sort"][value=?]', "http_response_code"
+ assert_select 'form[action="/distillator/cache"][method="get"] input[type="hidden"][name="direction"][value=?]', "asc"
+ end
+
+ test "json index filters by term without fetching" do
+ matching = create_cache(uri: "http://example.org/needle", name: "Alpha")
+ create_cache(uri: "http://example.org/other", name: "Beta")
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache.json", params: { term: "needle" }
+
+ assert_response :success
+ payload = JSON.parse(@response.body)
+ assert_equal [matching.id], payload.map { |row| row["id"] }
+ end
+
+ test "json index paginates with conservative default without fetching" do
+ 55.times do |index|
+ create_cache(uri: "http://example.org/cache-#{index}")
+ end
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache.json"
+
+ assert_response :success
+ payload = JSON.parse(@response.body)
+ assert_equal 50, payload.length
+ assert_equal "1", @response.headers["X-Page"]
+ assert_equal "50", @response.headers["X-Per-Page"]
+ assert_equal "55", @response.headers["X-Total-Count"]
+ assert_equal "2", @response.headers["X-Total-Pages"]
+ end
+
+ test "json index respects sort and direction without fetching" do
+ low = create_cache(uri: "http://example.org/low", http_response_code: 200)
+ high = create_cache(uri: "http://example.org/high", http_response_code: 404)
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache.json", params: { sort: "http_response_code", direction: "asc" }
+
+ assert_response :success
+ payload = JSON.parse(@response.body)
+ assert_equal [low.id, high.id], payload.map { |row| row["id"] }
+ end
+
+ test "html index next page link preserves existing filters and per_page" do
+ 3.times do |index|
+ create_cache(
+ uri: "http://example.org/needle-#{index}",
+ http_response_code: 404,
+ signals: { "network_status" => "failed" }
+ )
+ end
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache", params: {
+ term: "needle",
+ http_response_code: "404",
+ has_html: "true",
+ network_status: "failed",
+ per_page: "2"
+ }
+
+ assert_response :success
+ assert_match '/distillator/cache?has_html=true&http_response_code=404&network_status=failed&page=2&per_page=2&term=needle', @response.body
+ end
+
+ test "html index previous page link preserves existing filters and per_page" do
+ 3.times do |index|
+ create_cache(
+ uri: "http://example.org/needle-prev-#{index}",
+ http_response_code: 404,
+ signals: { "network_status" => "failed" }
+ )
+ end
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache", params: {
+ term: "needle-prev",
+ http_response_code: "404",
+ has_html: "true",
+ network_status: "failed",
+ per_page: "2",
+ page: "2"
+ }
+
+ assert_response :success
+ assert_match '/distillator/cache?has_html=true&http_response_code=404&network_status=failed&page=1&per_page=2&term=needle-prev', @response.body
+ end
+
+ test "show returns serialized cache row without fetching" do
+ cache = create_cache
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache/#{cache.id}.json"
+
+ assert_response :success
+ payload = JSON.parse(@response.body)
+ assert_equal cache.id, payload["id"]
+ assert_equal cache.uri_key, payload["uri_key"]
+ end
+
+ test "show json can expose preview refresh diagnostics" do
+ cache = create_cache(signals: { "network_status" => "ok", "content_type" => "html", "redirect_type" => "none", "fetch_path" => "legacy", "native_ineligible_reason" => "json_post" })
+ Distillator::FetchCacheStore.expects(:fetch).never
+ Distillator::FetchGuard.stubs(:check_url).returns(Distillator::FetchGuard::Result.new(allowed: true))
+
+ get "/distillator/cache/#{cache.id}.json", params: { preview: "true", force_scrape_every_hrs: "0" }
+
+ assert_response :success
+ payload = JSON.parse(@response.body)
+ assert_equal "legacy", payload["fetch_path"]
+ assert_equal "json_post", payload["native_ineligible_reason"]
+ assert_equal true, payload["preview"]["would_refresh"]
+ assert_equal "stale_by_force_scrape_every_hrs", payload["preview"]["reason"]
+ end
+
+ test "html show renders operator cards and secondary diagnostics without fetching" do
+ cache = create_cache(signals: { "network_status" => "ok", "content_type" => "html", "redirect_type" => "none", "fetch_path" => "legacy", "native_ineligible_reason" => "json_post" })
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache/#{cache.id}"
+
+ assert_response :success
+ assert_match "Status card", @response.body
+ assert_match "Identity card", @response.body
+ assert_match "Last fetch card", @response.body
+ assert_match "Content card", @response.body
+ assert_match "Diagnostics card", @response.body
+ assert_match "Compatibility card", @response.body
+ assert_match "Operator health:", @response.body
+ assert_match "Fetch path:", @response.body
+ assert_match "Native ineligible reason:", @response.body
+ assert_match "Content sample:", @response.body
+ assert_match "/distillator/cache/#{cache.id}/raw_view", @response.body
+ assert_match "/distillator/cache/#{cache.id}/wring_json_view", @response.body
+ assert_match "/distillator/cache/compare?uri=", @response.body
+ assert_no_match "Preview Direct fetch", visible_text(@response.body)
+ end
+
+ test "html show shows row refresh actions only when feature flag is enabled" do
+ cache = create_cache
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ ENV["DISTILLATOR_CACHE_REFRESH_UI"] = "true"
+ get "/distillator/cache/#{cache.id}"
+
+ assert_response :success
+ assert_match "Direct fetch", @response.body
+ assert_match "Rendered fetch", @response.body
+ assert_match "POST fetch", @response.body
+ end
+
+ test "html show can render inline preview without fetching" do
+ cache = create_cache
+ Distillator::FetchCacheStore.expects(:fetch).never
+ Distillator::FetchGuard.stubs(:check_url).returns(Distillator::FetchGuard::Result.new(allowed: true))
+
+ get "/distillator/cache/#{cache.id}", params: { preview: "true", force_scrape_every_hrs: "0" }
+
+ assert_response :success
+ assert_match "Refresh Diagnostic", @response.body
+ assert_match "Would refresh? true", @response.body
+ assert_match "Reason: stale_by_force_scrape_every_hrs", @response.body
+ end
+
+ test "raw renders cached html only without fetching and applies safety headers" do
+ cache = create_cache(html: "cached raw")
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache/#{cache.id}/raw"
+
+ assert_response :success
+ assert_equal "cached raw", @response.body
+ assert_equal "text/html; charset=utf-8", @response.headers["Content-Type"]
+ assert_equal "nosniff", @response.headers["X-Content-Type-Options"]
+ assert_includes @response.headers["Content-Security-Policy"], "sandbox"
+ assert_nil @response.headers["X-Frame-Options"]
+ end
+
+ test "raw view page shows warning and timestamps without fetching" do
+ cache = create_cache(html: "cached raw")
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache/#{cache.id}/raw_view"
+
+ assert_response :success
+ assert_match "Raw Cached HTML", @response.body
+ assert_match "cached HTML only; no refresh performed.", @response.body
+ assert_match "sandboxed to reduce app-origin script execution", @response.body
+ assert_match "Scrape date:", @response.body
+ assert_match "Successful refresh:", @response.body
+ assert_match "/distillator/cache/#{cache.id}/raw", @response.body
+ assert_no_match "Distillator", visible_text(@response.body)
+ end
+
+ test "wring_json returns exact legacy keys from cache only" do
+ cache = create_cache(
+ html: "cached json",
+ signals: {
+ "network_status" => "ok",
+ "content_type" => "html",
+ "redirect_type" => "normal",
+ "redirected" => true,
+ "final_url" => "https://example.org/final"
+ },
+ hints: ["hint"],
+ final_url: "https://example.org/final",
+ redirect_chain: ["http://example.org/cached", "https://example.org/final"],
+ http_response_code: 404
+ )
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache/#{cache.id}/wring_json"
+
+ assert_response :success
+ payload = JSON.parse(@response.body)
+ assert_equal %w[final_url hints html http_code redirect_chain signals].sort, payload.keys.sort
+ assert_equal "cached json", payload["html"]
+ assert_equal "ok", payload["signals"]["network_status"]
+ assert_equal "html", payload["signals"]["content_type"]
+ assert_equal "normal", payload["signals"]["redirect_type"]
+ assert_equal true, payload["signals"]["redirected"]
+ assert_equal "https://example.org/final", payload["signals"]["final_url"]
+ assert_equal 404, payload["http_code"]
+ assert_nil @response.headers["X-Frame-Options"]
+ end
+
+ test "wring json view shows exact cached payload without fetching" do
+ cache = create_cache(
+ html: "cached json",
+ signals: { "network_status" => "ok" },
+ hints: ["hint"],
+ final_url: "https://example.org/final",
+ redirect_chain: ["http://example.org/cached", "https://example.org/final"],
+ http_response_code: 404
+ )
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache/#{cache.id}/wring_json_view"
+
+ assert_response :success
+ assert_match "Cached Wringer-compatible JSON", @response.body
+ assert_match ""html": "<html>cached json</html>"", @response.body
+ assert_match ""http_code": 404", @response.body
+ assert_match "/distillator/cache/#{cache.id}/wring_json", @response.body
+ assert_no_match "Distillator", visible_text(@response.body)
+ end
+
+ test "cache preview page uses neutral cache language" do
+ create_cache
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache/preview", params: {
+ uri: "https://example.org/events",
+ fetch_kind: "normal"
+ }
+
+ assert_response :success
+ assert_match "Cache Refresh Diagnostic", @response.body
+ assert_match "Diagnostic JSON", @response.body
+ assert_no_match "Distillator", visible_text(@response.body)
+ assert_no_match "/distillator/cache", visible_text(@response.body)
+ end
+
+ test "preview returns missing cache reason without fetching" do
+ Distillator::FetchCacheStore.expects(:fetch).never
+ Distillator::FetchGuard.stubs(:check_url).returns(Distillator::FetchGuard::Result.new(allowed: true))
+
+ get "/distillator/cache/preview.json", params: { uri: "http://example.org/missing" }
+
+ assert_response :success
+ payload = JSON.parse(@response.body)
+ assert_equal true, payload["would_refresh"]
+ assert_equal "missing_cache", payload["reason"]
+ end
+
+ test "preview returns fresh cache reason without fetching" do
+ cache = create_cache
+ Distillator::FetchCacheStore.expects(:fetch).never
+ Distillator::FetchGuard.stubs(:check_url).returns(Distillator::FetchGuard::Result.new(allowed: true))
+
+ get "/distillator/cache/preview.json", params: { uri: cache.normalized_url, force_scrape_every_hrs: "200000" }
+
+ assert_response :success
+ payload = JSON.parse(@response.body)
+ assert_equal false, payload["would_refresh"]
+ assert_equal "fresh_cache", payload["reason"]
+ end
+
+ test "preview returns force_scrape reason without fetching" do
+ cache = create_cache
+ Distillator::FetchCacheStore.expects(:fetch).never
+ Distillator::FetchGuard.stubs(:check_url).returns(Distillator::FetchGuard::Result.new(allowed: true))
+
+ get "/distillator/cache/preview.json", params: { uri: cache.normalized_url, force_scrape: "true" }
+
+ assert_response :success
+ payload = JSON.parse(@response.body)
+ assert_equal true, payload["would_refresh"]
+ assert_equal "force_scrape", payload["reason"]
+ end
+
+ test "preview returns threshold zero refresh reason without fetching" do
+ cache = create_cache
+ Distillator::FetchCacheStore.expects(:fetch).never
+ Distillator::FetchGuard.stubs(:check_url).returns(Distillator::FetchGuard::Result.new(allowed: true))
+
+ get "/distillator/cache/preview.json", params: { uri: cache.normalized_url, force_scrape_every_hrs: "0" }
+
+ assert_response :success
+ payload = JSON.parse(@response.body)
+ assert_equal true, payload["would_refresh"]
+ assert_equal "stale_by_force_scrape_every_hrs", payload["reason"]
+ end
+
+ test "preview returns invalid uri without fetching" do
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache/preview.json", params: { uri: "http://[invalid" }
+
+ assert_response :success
+ payload = JSON.parse(@response.body)
+ assert_equal false, payload["would_refresh"]
+ assert_equal "invalid_uri", payload["reason"]
+ end
+
+ test "preview returns invalid uri for missing uri without fetching" do
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache/preview.json"
+
+ assert_response :success
+ payload = JSON.parse(@response.body)
+ assert_equal false, payload["would_refresh"]
+ assert_equal "invalid_uri", payload["reason"]
+ end
+
+ test "preview returns blocked url without fetching" do
+ Distillator::FetchCacheStore.expects(:fetch).never
+ Distillator::FetchGuard.stubs(:check_url).returns(
+ Distillator::FetchGuard::Result.new(
+ allowed: false,
+ error: "Blocked URL host with no DNS resolution result: missing.example",
+ reason: :dns_resolution_failed
+ )
+ )
+
+ get "/distillator/cache/preview.json", params: { uri: "http://127.0.0.1" }
+
+ assert_response :success
+ payload = JSON.parse(@response.body)
+ assert_equal false, payload["would_refresh"]
+ assert_equal "blocked_url", payload["reason"]
+ assert_equal "dns_resolution_failed", payload["guard_reason"]
+ assert_includes payload["guard_error"], "no DNS resolution result"
+ end
+
+ test "fetch redirects back with notice after successful request" do
+ ENV["DISTILLATOR_CACHE_REFRESH_UI"] = "true"
+ Distillator::FetchCacheStore.expects(:fetch).with do |kwargs|
+ assert_equal "http://example.org/events", kwargs[:uri]
+ assert_equal true, kwargs[:include_fragment]
+ assert_equal true, kwargs[:force_scrape]
+ assert_equal "24", kwargs[:force_scrape_every_hrs]
+ assert_equal true, kwargs[:use_phantomjs]
+ assert_equal false, kwargs[:json_post]
+ assert_equal true, kwargs[:absolute_src]
+ true
+ end.returns(OpenStruct.new(normalized_url: "http://example.org/events", cache: nil))
+
+ post "/distillator/cache/fetch", params: {
+ uri: "http://example.org/events",
+ fetch_kind: "rendered",
+ include_fragment: "true",
+ force_scrape: "true",
+ force_scrape_every_hrs: "24",
+ absolute_src: "true"
+ }
+
+ assert_redirected_to "/distillator/cache"
+ end
+
+ test "fetch rejects invalid uri without fetching" do
+ ENV["DISTILLATOR_CACHE_REFRESH_UI"] = "true"
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ post "/distillator/cache/fetch", params: { uri: "http://[invalid", fetch_kind: "normal" }
+
+ assert_redirected_to "/distillator/cache"
+ end
+
+ test "fetch rejects blocked url without fetching" do
+ ENV["DISTILLATOR_CACHE_REFRESH_UI"] = "true"
+ Distillator::FetchCacheStore.expects(:fetch).never
+ Distillator::FetchGuard.stubs(:check_url).returns(Distillator::FetchGuard::Result.new(allowed: false, error: "blocked"))
+
+ post "/distillator/cache/fetch", params: { uri: "http://127.0.0.1/events", fetch_kind: "normal" }
+
+ assert_redirected_to "/distillator/cache"
+ end
+
+ test "diagnostic preview html still shows refresh decision without fetching" do
+ cache = create_cache
+ Distillator::FetchCacheStore.expects(:fetch).never
+ Distillator::FetchGuard.stubs(:check_url).returns(Distillator::FetchGuard::Result.new(allowed: true))
+
+ get "/distillator/cache/preview", params: { uri: cache.normalized_url, force_scrape: "true" }
+
+ assert_response :success
+ text = visible_text(@response.body)
+ assert_match "Cache Refresh Diagnostic", text
+ assert_match "No fetch is performed from this page", text
+ assert_match "Would refresh?", text
+ assert_match "force_scrape", text
+ end
+
+ test "preview html shows chosen fetch mode and threshold zero details without fetching" do
+ cache = create_cache
+ Distillator::FetchCacheStore.expects(:fetch).never
+ Distillator::FetchGuard.stubs(:check_url).returns(Distillator::FetchGuard::Result.new(allowed: true))
+
+ get "/distillator/cache/preview", params: { uri: cache.normalized_url, fetch_kind: "rendered", force_scrape_every_hrs: "0" }
+
+ assert_response :success
+ assert_match "Chosen fetch mode: Rendered fetch (rendered)", @response.body
+ assert_match "Would refresh? true", @response.body
+ assert_match "Reason: stale_by_force_scrape_every_hrs", @response.body
+ end
+
+ test "preview page includes final post action when refresh ui is enabled" do
+ cache = create_cache
+ Distillator::FetchCacheStore.expects(:fetch).never
+ Distillator::FetchGuard.stubs(:check_url).returns(Distillator::FetchGuard::Result.new(allowed: true))
+
+ ENV["DISTILLATOR_CACHE_REFRESH_UI"] = "true"
+ get "/distillator/cache/preview", params: { uri: cache.normalized_url, fetch_kind: "post", force_scrape_every_hrs: "0" }
+
+ assert_response :success
+ assert_select 'form[action="/distillator/cache/fetch"][method="post"] input[type="submit"][value="POST fetch"]', 1
+ end
+
+ test "preview html shows force scrape every hrs effects without fetching" do
+ cache = create_cache
+ Distillator::FetchCacheStore.expects(:fetch).never
+ Distillator::FetchGuard.stubs(:check_url).returns(Distillator::FetchGuard::Result.new(allowed: true))
+
+ get "/distillator/cache/preview", params: { uri: cache.normalized_url, force_scrape_every_hrs: "0" }
+
+ assert_response :success
+ assert_match "Would refresh? true", @response.body
+ assert_match "Reason: stale_by_force_scrape_every_hrs", @response.body
+ end
+
+ test "preview html with no uri renders form without crashing" do
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache/preview"
+
+ assert_response :success
+ assert_match "Cache Refresh Diagnostic", @response.body
+ assert_match "Run diagnostic", @response.body
+ assert_no_match "Diagnostic Result", @response.body
+ end
+
+ test "compare page rescue fallback shows source labels and error context" do
+ assert_read_only_page_does_not_fetch
+ Distillator::CacheCompare.stubs(:call).raises(StandardError, "boom")
+
+ get "/distillator/cache/compare", params: { uri: "http://example.org/failure" }
+
+ assert_response :unprocessable_entity
+ assert_match "Legacy source: unavailable", @response.body
+ assert_match "Legacy lookup error: Unexpected comparison failure", @response.body
+ assert_match "Condenser source: local_fetch_cache", @response.body
+ end
+
+ test "html index renders quick filters and badges without fetching" do
+ create_cache(uri: "http://example.org/network", signals: { "network_status" => "failed" })
+ create_cache(uri: "http://example.org/json", signals: { "content_type" => "json" })
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache"
+
+ assert_response :success
+ assert_match "Quick filters", @response.body
+ assert_match "Healthy", @response.body
+ assert_match "Needs review", @response.body
+ assert_match "Failed", @response.body
+ assert_match "Network failed", @response.body
+ assert_match "JSON", @response.body
+ assert_match "cache-badge-health", @response.body
+ assert_match "cache-badge-html", @response.body
+ end
+
+ test "html sortable links preserve filters" do
+ create_cache
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache", params: { term: "needle", health: "healthy", per_page: "25" }
+
+ assert_response :success
+ assert_match "term=needle", @response.body
+ assert_match "health=healthy", @response.body
+ assert_select 'input[type="hidden"][name="per_page"][value=?]', "25"
+ end
+
+ test "html index does not include hidden per_page when default pagination is active" do
+ create_cache
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache"
+
+ assert_response :success
+ assert_select 'input[type="hidden"][name="per_page"]', 0
+ end
+
+ test "html index uses advanced filters details and preserves filter controls" do
+ create_cache
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache"
+
+ assert_response :success
+ assert_harmonized_table_shell
+ assert_harmonized_filter_form(action: "/distillator/cache")
+ assert_harmonized_filter_shell
+ assert_harmonized_advanced_filters
+ assert_select "table thead tr", 1
+ assert_select "table thead tr th:nth-child(1)", text: /Health/
+ assert_harmonized_sortable_header(label: "URI / Name", sort_key: "normalized_url")
+ assert_harmonized_sortable_header(label: "HTTP", sort_key: "http_response_code")
+ assert_harmonized_apply_filters_button
+ assert_harmonized_reset_filters_link(params: { view: "rich" })
+ end
+
+ test "html index shows summary cards before advanced filters" do
+ create_cache
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache"
+
+ assert_response :success
+ assert_match "Healthy", @response.body
+ assert_match "Advanced filters", @response.body
+ assert_harmonized_summary_cards_before_filters
+ end
+
+ test "html sortable links toggle direction and preserve filters" do
+ create_cache
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache", params: { term: "needle", health: "healthy", http_response_code: "404", direction: "asc", sort: "http_response_code", per_page: "25" }
+
+ assert_response :success
+ assert_match /sort=http_response_code/, @response.body
+ assert_match /direction=desc/, @response.body
+ assert_sort_link_preserves_filters(
+ label: "HTTP",
+ sort_key: "http_response_code",
+ params: {
+ term: "needle",
+ health: "healthy",
+ http_response_code: "404",
+ per_page: "25"
+ }
+ )
+ end
+
+ test "show page matches harmonized record card contract" do
+ cache = create_cache
+ assert_read_only_page_does_not_fetch
+
+ get "/distillator/cache/#{cache.id}"
+
+ assert_response :success
+ assert_harmonized_record_card
+ assert_harmonized_card_action_bar
+ end
+
+ test "distillator cache show does not fetch" do
+ cache = create_cache
+ assert_read_only_page_does_not_fetch
+
+ get "/distillator/cache/#{cache.id}"
+
+ assert_response :success
+ end
+
+ test "html index canonical redirect strips blank filters" do
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache", params: { term: "", has_html: "", page: "0" }
+
+ assert_redirected_to "/distillator/cache"
+ end
+
+ test "html index canonical redirect normalizes invalid page and per_page" do
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache", params: { term: "needle", page: "-2", per_page: "500" }
+
+ assert_redirected_to "/distillator/cache?per_page=100&term=needle"
+ end
+
+ test "html index canonical redirect removes invalid sort and direction" do
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache", params: { term: "needle", sort: "bogus", direction: "sideways" }
+
+ assert_redirected_to "/distillator/cache?term=needle"
+ end
+
+ test "json index keeps array response and pagination headers with normalized params" do
+ create_cache
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache.json", params: { term: "", page: "0", per_page: "500", sort: "bogus", direction: "sideways" }
+
+ assert_response :success
+ assert_kind_of Array, JSON.parse(@response.body)
+ assert_equal "1", @response.headers["X-Page"]
+ assert_equal "100", @response.headers["X-Per-Page"]
+ assert_equal "1", @response.headers["X-Total-Count"]
+ assert_equal "1", @response.headers["X-Total-Pages"]
+ end
+
+ test "compare page renders differences without fetching" do
+ assert_read_only_page_does_not_fetch
+ Distillator::CacheCompare.expects(:call).returns(
+ {
+ uri: "http://example.org/page",
+ uri_key: CGI.escape("http://example.org/page"),
+ legacy_cache: { html: "legacy" },
+ legacy_source: "injected_lookup",
+ legacy_lookup_error: nil,
+ condenser_cache: { html: "condenser" },
+ condenser_source: "local_fetch_cache",
+ distillator_cache: { html: "condenser" },
+ distillator_source: "local_fetch_cache",
+ comparison_policy: :operator,
+ diffs: { html_sha256: { same: false, classification: :blocking_regression, legacy: "a", condenser: "b", distillator: "b" } },
+ missing: { legacy: false, condenser: false, distillator: false },
+ summary: {
+ outcome: "blocked",
+ primary_reason: "Safety blockers: html_sha256",
+ same: false,
+ promotable: false,
+ http_code_difference: false,
+ final_url_difference: false,
+ content_type_difference: false,
+ html_hash_difference: true,
+ body_byte_difference: false,
+ title_difference: false,
+ blocking_regressions: [:html_sha256],
+ improvements: [],
+ metadata_only_diffs: [],
+ unknown_diffs: []
+ }
+ }
+ )
+
+ get "/distillator/cache/compare", params: { uri: "http://example.org/page" }
+
+ assert_response :success
+ assert_match "Cache Comparison", @response.body
+ assert_match "Summary", @response.body
+ assert_match "Outcome:", @response.body
+ assert_match "Differences grouped by importance", @response.body
+ assert_match "Legacy source:", @response.body
+ assert_match "Condenser source:", @response.body
+ assert_match "Condenser cache missing:", @response.body
+ assert_match " Condenser ", @response.body
+ assert_match "No cache refresh was performed.", @response.body
+ assert_no_match "Distillator", visible_text(@response.body)
+ assert_match "html_sha256", @response.body
+ assert_match "blocking_regression", @response.body
+ assert_match "Raw payloads", @response.body
+ assert_no_match "New source", visible_text(@response.body)
+ assert_no_match "New cache", visible_text(@response.body)
+ assert_no_match "Distillator", visible_text(@response.body)
+ end
+
+ test "compare page shows ready with metadata notes without review copy" do
+ assert_read_only_page_does_not_fetch
+ Distillator::CacheCompare.expects(:call).returns(
+ {
+ uri: "http://example.org/page",
+ uri_key: CGI.escape("http://example.org/page"),
+ legacy_cache: { html: "same" },
+ legacy_source: "injected_lookup",
+ legacy_lookup_error: nil,
+ condenser_cache: { html: "same" },
+ condenser_source: "local_fetch_cache",
+ distillator_cache: { html: "same" },
+ distillator_source: "local_fetch_cache",
+ comparison_policy: :operator,
+ diffs: { hints: { same: false, classification: :metadata_only, legacy: [], condenser: ["condenser-extra-note"], distillator: ["condenser-extra-note"] } },
+ missing: { legacy: false, condenser: false, distillator: false },
+ summary: {
+ outcome: "ready_with_metadata_notes",
+ primary_reason: "Metadata notes: hints",
+ same: false,
+ promotable: true,
+ http_code_difference: false,
+ final_url_difference: false,
+ content_type_difference: false,
+ html_hash_difference: false,
+ body_byte_difference: false,
+ title_difference: false,
+ blocking_regressions: [],
+ improvements: [],
+ metadata_only_diffs: [:hints],
+ review_needed_diffs: [],
+ unknown_diffs: []
+ }
+ }
+ )
+
+ get "/distillator/cache/compare", params: { uri: "http://example.org/page" }
+
+ assert_response :success
+ assert_match "Ready with metadata notes", @response.body
+ assert_match "Metadata notes only. Activation can proceed when the rest of the evidence is ready.", @response.body
+ assert_no_match "Outcome: Needs review", @response.body
+ end
+
+ test "cache index stays operational and does not fetch under heavy filter and sort usage" do
+ 3.times do |index|
+ create_cache(
+ uri: "http://example.org/heavy-#{index}",
+ http_response_code: 404,
+ signals: { "network_status" => "failed", "content_type" => "json" },
+ hints: ["empty_body"],
+ final_url: "http://example.org/final-#{index}",
+ redirect_chain: ["http://example.org/heavy-#{index}", "http://example.org/final-#{index}"]
+ )
+ end
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get "/distillator/cache", params: {
+ term: "heavy",
+ http_response_code: "404",
+ has_html: "true",
+ network_status: "failed",
+ health: "network_failed",
+ status_group: "4xx",
+ content_type: "json",
+ hint: "empty_body",
+ redirected: "true",
+ last_attempt: "last_7d",
+ last_success: "last_7d",
+ sort: "http_response_code",
+ direction: "asc",
+ page: "1",
+ per_page: "2"
+ }
+
+ follow_redirect! if response.redirect?
+ assert_response :success
+ assert_match "Apply filters", @response.body
+ assert_match "Network failed", @response.body
+ assert_match "cache-badge-health", @response.body
+ assert_match "direction=desc", @response.body
+ assert_match "Show", @response.body
+ assert_match "Raw", @response.body
+ assert_match ".json", @response.body
+ assert_match "Size", @response.body
+ assert_match "Compare", @response.body
+
+ get "/distillator/cache.json", params: {
+ term: "heavy",
+ http_response_code: "404",
+ has_html: "true",
+ network_status: "failed",
+ health: "network_failed",
+ status_group: "4xx",
+ content_type: "json",
+ hint: "empty_body",
+ redirected: "true",
+ last_attempt: "last_7d",
+ last_success: "last_7d",
+ sort: "http_response_code",
+ direction: "asc",
+ page: "1",
+ per_page: "2"
+ }
+
+ assert_response :success
+ assert_kind_of Array, JSON.parse(@response.body)
+ assert_equal "1", @response.headers["X-Page"]
+ assert_equal "2", @response.headers["X-Per-Page"]
+ assert_equal "3", @response.headers["X-Total-Count"]
+ assert_equal "2", @response.headers["X-Total-Pages"]
+ end
+
+ private
+
+ def visible_text(html)
+ document = Nokogiri::HTML(html)
+ document.xpath("//script|//style").remove
+ document.text.squish
+ end
+
+ def assert_read_only_page_does_not_fetch
+ Distillator::FetchCacheStore.expects(:fetch).never
+ Distillator::FetchService.expects(:fetch).never
+ Distillator::NativeFetch.expects(:call).never
+ Distillator::FetchShadowComparator.expects(:call).never
+ end
+
+ def create_cache(uri: "http://example.org/cached", html: "cached", body: html, name: "Cached", signals: {}, hints: [], final_url: nil, redirect_chain: [], http_response_code: 200, scrape_date: Time.zone.now, successful_refresh: Time.zone.now, updated_at: Time.zone.now)
+ cache = Distillator::FetchCache.new(
+ uri_key: CGI.escape(uri),
+ normalized_url: uri,
+ html: html,
+ body: body,
+ name: name,
+ scrape_date: scrape_date,
+ successful_refresh: successful_refresh,
+ http_response_code: http_response_code,
+ headers: {},
+ signals: signals,
+ hints: hints,
+ final_url: final_url,
+ redirect_chain: redirect_chain
+ )
+ cache.created_at = updated_at
+ cache.updated_at = updated_at
+ cache.save!
+ cache
+ end
+end
diff --git a/test/controllers/distillator/cache_ui_parity_test.rb b/test/controllers/distillator/cache_ui_parity_test.rb
new file mode 100644
index 00000000..0c54d23f
--- /dev/null
+++ b/test/controllers/distillator/cache_ui_parity_test.rb
@@ -0,0 +1,167 @@
+require "test_helper"
+
+class Distillator::CacheUiParityTest < ActionDispatch::IntegrationTest
+ setup do
+ Distillator::FetchCache.delete_all
+ @old_refresh_ui = ENV["DISTILLATOR_CACHE_REFRESH_UI"]
+ @old_fetch_mode = ENV["DISTILLATOR_FETCH_MODE"]
+ end
+
+ teardown do
+ ENV["DISTILLATOR_CACHE_REFRESH_UI"] = @old_refresh_ui
+ ENV["DISTILLATOR_FETCH_MODE"] = @old_fetch_mode
+ end
+
+ test "cache refresh ui flag shows operational fetch parity matrix from the cache index" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "internal"
+ ENV["DISTILLATOR_CACHE_REFRESH_UI"] = "true"
+ cache = create_cache(
+ normalized_url: "https://example.org/event",
+ html: "Event Hello world preview snippet",
+ http_response_code: 200
+ )
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get distillator_cache_index_path
+
+ assert_response :success
+ assert_match "Fetch URL", @response.body
+ assert_match "Direct fetch", @response.body
+ assert_match "Rendered fetch", @response.body
+ assert_match "POST fetch", @response.body
+ assert_match "Direct fetch = normal cached fetch", @response.body
+ assert_match "Rendered fetch = JavaScript/rendered fetch with fragment support", @response.body
+ assert_match "POST fetch = JSON POST source fetch", @response.body
+ assert_match ".json", @response.body
+ assert_match "Raw", @response.body
+ assert_match "Show", @response.body
+ assert_match "Hello world preview snippet", @response.body
+ assert_match "Compatibility API", @response.body
+ assert_match "Cached Wringer-compatible JSON", @response.body
+ assert_match "Cache fetch actions", @response.body
+ assert_match "/websites/wring.json?uri=URL", @response.body
+ assert_match distillator_cache_path(cache), @response.body
+ assert_match raw_distillator_cache_path(cache), @response.body
+ assert_match wring_json_distillator_cache_path(cache), @response.body
+ assert_match "Compare", @response.body
+ assert_no_match "Distillator", visible_text(@response.body)
+ assert_no_match "DISTILLATOR_CACHE_REFRESH_UI", visible_text(@response.body)
+ assert_no_match "/distillator/cache", visible_text(@response.body)
+ assert_no_match "Preview Direct fetch", visible_text(@response.body)
+ assert_no_match "Preview Rendered fetch", visible_text(@response.body)
+ assert_no_match "Preview POST", visible_text(@response.body)
+ assert_no_match "Cache preview endpoint", visible_text(@response.body)
+ assert_no_match "Read-only cache inspection only", visible_text(@response.body)
+ assert_no_match "Refresh actions are disabled", visible_text(@response.body)
+ end
+
+ test "shadow mode keeps cache inspection fallback copy" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "shadow"
+ ENV["DISTILLATOR_CACHE_REFRESH_UI"] = nil
+ cache = create_cache(
+ normalized_url: "https://example.org/event",
+ html: "Event Hello world preview snippet",
+ http_response_code: 200
+ )
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get distillator_cache_index_path
+
+ assert_response :success
+ assert_match "Cache inspection mode. Refresh actions are not available in this environment.", @response.body
+ assert_match "Show", @response.body
+ assert_match "Raw", @response.body
+ assert_match ".json", @response.body
+ assert_match "Pretty JSON", @response.body
+ assert_match "Compare", @response.body
+ assert_no_match %r{button[^>]*>Direct fetch}, @response.body
+ assert_no_match %r{button[^>]*>Rendered fetch}, @response.body
+ assert_no_match %r{button[^>]*>POST fetch}, @response.body
+ assert_match distillator_cache_path(cache), @response.body
+ assert_no_match "Distillator", visible_text(@response.body)
+ assert_no_match "DISTILLATOR_CACHE_REFRESH_UI", visible_text(@response.body)
+ assert_no_match "/distillator/cache", visible_text(@response.body)
+ assert_no_match "Preview Direct fetch", visible_text(@response.body)
+ end
+
+ test "parity view shows semantic content failure warning alongside transport success" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "internal"
+ create_cache(
+ normalized_url: "https://www.ovation.ca/event",
+ html: "last good",
+ http_response_code: 200
+ ).update!(
+ signals: {
+ "network_status" => "ok",
+ "content_type" => "html",
+ "transport_success" => true,
+ "content_success" => false,
+ "primary_issue_key" => "redirect_to_listing",
+ "primary_issue_label" => "Redirect to listing",
+ "last_good_preserved_failure" => true
+ },
+ hints: ["redirect_to_listing", "last_good_preserved_failure"]
+ )
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get distillator_cache_index_path, params: { view: "parity" }
+
+ assert_response :success
+ text = visible_text(@response.body)
+ assert_match "HTTP 200 but content failed: Redirect to listing", text
+ assert_match "Transport: success", text
+ assert_match "Content: failed", text
+ assert_match "Blocking issue: redirect_to_listing", text
+ assert_match "Last good content preserved: yes", text
+ end
+
+ test "fetch route helper is semantic and stable" do
+ assert_equal "/distillator/cache/fetch", fetch_distillator_cache_index_path
+ assert_equal "/distillator/cache/preview", preview_distillator_cache_index_path
+ end
+
+ test "compatibility view toggle preserves context" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "internal"
+ ENV["DISTILLATOR_CACHE_REFRESH_UI"] = nil
+ create_cache(
+ normalized_url: "https://example.org/event",
+ html: "Hello",
+ http_response_code: 200
+ )
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get distillator_cache_index_path, params: { view: "parity", term: "event", page: "2" }
+
+ assert_response :success
+ assert_match "Compatibility view", @response.body
+ assert_match "Rich view", @response.body
+ assert_match "view=rich", @response.body
+ assert_match "view=parity", @response.body
+ assert_match "term=event", @response.body
+ end
+
+ private
+
+ def visible_text(html)
+ document = Nokogiri::HTML(html)
+ document.xpath("//script|//style").remove
+ document.text.squish
+ end
+
+ def create_cache(normalized_url:, html:, http_response_code:)
+ Distillator::FetchCache.create!(
+ uri_key: CGI.escape(normalized_url),
+ normalized_url: normalized_url,
+ html: html,
+ body: html,
+ name: "Cached",
+ scrape_date: Time.zone.now,
+ successful_refresh: Time.zone.now,
+ http_response_code: http_response_code,
+ headers: {},
+ signals: { "network_status" => "ok", "content_type" => "html" },
+ hints: [],
+ redirect_chain: []
+ )
+ end
+end
diff --git a/test/controllers/distillator/capabilities_controller_test.rb b/test/controllers/distillator/capabilities_controller_test.rb
new file mode 100644
index 00000000..26cc9472
--- /dev/null
+++ b/test/controllers/distillator/capabilities_controller_test.rb
@@ -0,0 +1,60 @@
+require "test_helper"
+
+class Distillator::CapabilitiesControllerTest < ActionDispatch::IntegrationTest
+ test "capabilities page renders read only without fetch refresh export or rollout writes" do
+ assert_read_only_capabilities_page
+
+ assert_no_difference(["Distillator::FetchCache.count", "Distillator::RolloutEvent.count"]) do
+ get distillator_capabilities_path
+ end
+
+ assert_response :success
+ assert_match "Condenser / Distillator System Map", @response.body
+ assert_match "This page is read-only", @response.body
+ assert_match "Activation readiness ladder", @response.body
+ end
+
+ test "capabilities page shows grouped feature matrix and key features" do
+ get distillator_capabilities_path
+
+ assert_response :success
+ assert_match "Fetch & Cache", @response.body
+ assert_match "Statement Extraction", @response.body
+ assert_match "Sources / DSL", @response.body
+ assert_match "JSON-LD Export", @response.body
+ assert_match "Transition / Rollout", @response.body
+ assert_match "Diagnostics / Reports", @response.body
+ assert_match "Operations / Safety", @response.body
+ assert_match "Legacy Wringer Compatibility", @response.body
+ assert_match "Cache compare", @response.body
+ assert_match "Extracted statement parity", @response.body
+ assert_match "Export graph diff", @response.body
+ assert_match "Transition report", @response.body
+ assert_match "Rollback path", @response.body
+ assert_match "Legacy Wringer compatibility endpoint", @response.body
+ assert_match "Implemented", @response.body
+ assert_match "Legacy-backed", @response.body
+ assert_match "Transition-only", @response.body
+ assert_match "Production-critical", @response.body
+ assert_match "Read-only", @response.body
+ assert_match "Writes data", @response.body
+ assert_match "Needs review", @response.body
+ assert_match "Deprecated", @response.body
+ end
+
+ private
+
+ def assert_read_only_capabilities_page
+ Distillator::FetchCacheStore.expects(:fetch).never
+ Distillator::CacheCompare.expects(:call).never
+ Distillator::CacheRefreshPreview.expects(:call).never
+ Distillator::FetchService.expects(:call).never
+ Distillator::RefreshRunner.expects(:call).never
+ Distillator::TransitionCheck.expects(:call).never
+ Distillator::TransitionCheckRunner.expects(:call).never
+ Distillator::RolloutTransition.expects(:call).never
+ ExportArtsdataService.expects(:call).never
+ ExportArtsdataService.expects(:production_equivalent).never
+ Statements::ExtractedParityComparisonService.expects(:call).never
+ end
+end
diff --git a/test/controllers/distillator/shadow_reports_controller_test.rb b/test/controllers/distillator/shadow_reports_controller_test.rb
new file mode 100644
index 00000000..7ede66cf
--- /dev/null
+++ b/test/controllers/distillator/shadow_reports_controller_test.rb
@@ -0,0 +1,1591 @@
+require "test_helper"
+
+class Distillator::ShadowReportsControllerTest < ActionDispatch::IntegrationTest
+ test "shadow report renders successfully without fetching" do
+ create_shadow_website(name: "Shadow alpha", seedurl: "shadow-alpha")
+ create_regular_website(name: "Legacy alpha", seedurl: "legacy-alpha", mode: "legacy")
+ create_regular_website(name: "Active alpha", seedurl: "active-alpha", mode: "active")
+
+ assert_read_only_page_does_not_fetch
+
+ get distillator_shadow_report_path
+
+ assert_response :success
+ assert_match "Transition Report", @response.body
+ assert_match "Legacy sites", @response.body
+ assert_match "Shadow sites", @response.body
+ assert_match "Active sites", @response.body
+ assert_match "Promotable sites", @response.body
+ assert_match "Top blockers", @response.body
+ assert_match "Failed fetch", @response.body
+ assert_includes @response.body, "Transition checks are run by batch job. This page displays the latest result for each website."
+ assert_no_cohort_source_requests
+ end
+
+ test "shadow report shows navigation link between cache and options" do
+ get distillator_shadow_report_path
+
+ assert_response :success
+ assert_match %r{/distillator/cache.*?/distillator/shadow_report.*?/options}m, @response.body
+ end
+
+ test "shadow report lists shadow sites with summary actions only on the index" do
+ website = create_shadow_website(name: "Shadow beta", seedurl: "shadow-beta")
+ create_cache_for(website, url: "https://shadow-beta.example/event")
+
+ assert_read_only_page_does_not_fetch
+
+ get distillator_shadow_report_path
+
+ assert_response :success
+ assert_match "Shadow beta", @response.body
+ assert_match "Production backend", @response.body
+ assert_match "Wringer", @response.body
+ assert_match "Readiness", @response.body
+ assert_match "Severity", @response.body
+ assert_match "Primary blocker", @response.body
+ assert_match "Next action", @response.body
+ assert_match "Latest report", @response.body
+ assert_no_match "Compare Condenser vs Wringer", @response.body
+ assert_no_match "Open active cache", @response.body
+ assert_no_match "Latest attempt", @response.body
+ assert_no_match "Latest successful refresh", @response.body
+ assert_no_cohort_source_requests
+ end
+
+ test "shadow report shows cohort column filter and lavitrine rows" do
+ website = create_shadow_website(name: "Hector Charland", seedurl: "hector-charland-com")
+ create_cache_for(
+ website,
+ url: "https://hector-charland-com.example/event",
+ signals: {
+ "transport_success" => true,
+ "content_success" => true,
+ "representative_urls_checked" => true,
+ "statement_count_delta_acceptable" => true
+ }
+ )
+
+ get distillator_shadow_report_path
+
+ assert_response :success
+ assert_match "Cohort", @response.body
+ assert_match "Current page", @response.body
+ assert_match "La Vitrine pipeline", @response.body
+ assert_includes @response.body, 'name="cohort"'
+ assert_no_cohort_source_requests
+ end
+
+ test "shadow report defaults to bounded shadow-only results" do
+ 30.times do |index|
+ create_shadow_website(name: format("Shadow %02d", index), seedurl: "shadow-only-#{index}")
+ end
+ create_regular_website(name: "Legacy site", seedurl: "legacy-shadow-report", mode: "legacy")
+ create_regular_website(name: "Active site", seedurl: "active-shadow-report", mode: "active")
+
+ get distillator_shadow_report_path
+
+ assert_response :success
+ assert_match "Shadow 00", @response.body
+ assert_no_match %r{.*Legacy site.* }m, @response.body
+ assert_no_match %r{.*Active site.* }m, @response.body
+ assert_match "30 sites matched.", @response.body
+ assert_includes @response.body, 'name="limit"'
+ end
+
+ test "shadow report supports filters sorting pagination and not checked visibility" do
+ review = create_shadow_website(name: "Review queue", seedurl: "review-queue")
+ blocked = create_shadow_website(name: "Blocked queue", seedurl: "blocked-queue")
+ create_shadow_website(name: "Unknown queue", seedurl: "unknown-queue")
+ create_regular_website(name: "Active queue", seedurl: "active-queue", mode: "active")
+
+ create_cache_for(
+ review,
+ url: "https://review-queue.example/event",
+ signals: { "transport_success" => true },
+ primary_issue_key: "queue_it",
+ primary_issue_label: "Queue-it waiting room",
+ primary_issue_severity: "warning",
+ health_status: "redirect_changed",
+ health_severity: "low",
+ redirected: true,
+ final_url: "https://review-queue.example/redirected"
+ )
+ create_cache_for(
+ blocked,
+ url: "https://blocked-queue.example/event",
+ signals: { "transport_success" => false, "content_success" => false, "primary_issue_key" => "timeout", "primary_issue_severity" => "failed" },
+ primary_issue_key: "timeout",
+ primary_issue_label: "Fetch timeout",
+ primary_issue_severity: "failed",
+ health_status: "attempt_failed",
+ health_severity: "high"
+ )
+
+ get distillator_shadow_report_path, params: { status: "review", term: "queue", sort: "website", direction: "asc", limit: "1" }
+ follow_redirect! if response.redirect?
+
+ assert_response :success
+ assert_match "Review queue", @response.body
+ assert_no_match "Blocked queue", @response.body
+ assert_no_match "Unknown queue", @response.body
+ assert_match "Apply filters", @response.body
+ assert_match "Reset filters", @response.body
+ assert_match(/name="limit"[^>]*value="1"/, @response.body)
+
+ get distillator_shadow_report_path, params: { status: "not_checked" }
+
+ assert_response :success
+ assert_match "Unknown queue", @response.body
+ assert_no_match "Active queue", @response.body
+ assert_match "Not checked", @response.body
+ end
+
+ test "shadow report detail page renders without fetching" do
+ website = create_shadow_website(name: "Detail site", seedurl: "detail-site")
+ create_cache_for(website, url: "https://detail-site.example/event")
+
+ assert_read_only_page_does_not_fetch
+ Distillator::TransitionCheckRunner.expects(:call).never
+ Distillator::FetchCacheStore.expects(:fetch).never
+ Distillator::RefreshRunner.expects(:call).never
+ ExportArtsdataService.expects(:call).never
+ ExportArtsdataService.expects(:production_equivalent).never
+
+ get distillator_shadow_report_site_path(website)
+
+ assert_response :success
+ assert_match "This page is read-only. Transition checks are run by batch job; this page displays the latest recorded result.", @response.body
+ assert_match "Summary", @response.body
+ assert_match "Decision", @response.body
+ assert_match "Statement equivalence", @response.body
+ assert_match "Fetch diagnostic", @response.body
+ assert_match "Sampled URL diagnostics", @response.body
+ assert_match "Checked scope", @response.body
+ assert_match "Audit", @response.body
+ assert_match "Raw legacy transition evidence", @response.body
+ assert_no_match "Queue transition check", @response.body
+ assert_match "Website batch jobs", @response.body
+ assert_match "Inspect publishable event pages", @response.body
+ assert_match "Open cache diagnostics", @response.body
+ assert_no_match "Representative URL Matrix", @response.body
+ assert_no_match "Fetch parity", @response.body
+ assert_no_match "Run transition check", @response.body
+ assert_match(/Decision.*Sampled URL diagnostics.*Checked scope/m, @response.body)
+ end
+
+ test "shadow report row shows run batch check for shadow sites with missing evidence" do
+ website = create_shadow_website(name: "Blocked evidence", seedurl: "blocked-evidence")
+ create_cache_for(website, url: "https://blocked-evidence.example/event")
+
+ get distillator_shadow_report_path
+
+ assert_response :success
+ assert_match "Run batch check", @response.body
+ assert_match "Statement check not yet recorded", @response.body
+ end
+
+ test "shadow report detail shows promote to active when evidence is fresh and passing" do
+ website = create_shadow_website(name: "Ready detail", seedurl: "ready-detail")
+ url = "https://ready-detail.example/event"
+ create_cache_for(
+ website,
+ url: url,
+ signals: {
+ "transport_success" => true,
+ "content_success" => true,
+ "statement_count_delta_acceptable" => true,
+ "export_diff_checked" => true
+ }
+ )
+ website.transition_evidences.create!(id: next_id, url: url, check_kind: "fetch_parity", status: "checked", checked_at: 1.hour.ago)
+ website.transition_evidences.create!(id: next_id, url: url, check_kind: "statement_delta", status: "checked", statement_count_delta_acceptable: true, checked_at: 1.hour.ago)
+ website.transition_evidences.create!(id: next_id, url: url, check_kind: "export_diff", status: "checked", export_diff_checked: true, checked_at: 1.hour.ago)
+
+ get distillator_shadow_report_site_path(website)
+
+ assert_response :success
+ assert_match "Promote to active", @response.body
+ assert_match "Safe to promote", @response.body
+ decision_html = @response.body[%r{}m]
+ assert_match "Export confirmation", decision_html
+ assert_match "Passed", decision_html
+ end
+
+ test "shadow report detail shows activate after review checklist for review-only parity differences" do
+ website = create_shadow_website(name: "Review detail", seedurl: "review-detail")
+ url = "https://review-detail.example/event"
+ create_cache_for(
+ website,
+ url: url,
+ signals: {
+ "transport_success" => true,
+ "content_success" => true,
+ "statement_count_delta_acceptable" => true,
+ "export_diff_checked" => true
+ }
+ )
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url,
+ check_kind: "fetch_parity",
+ status: "checked",
+ checked_at: 1.hour.ago,
+ details: {
+ reason: "review_needed_difference",
+ comparison_policy: "operator",
+ representative_webpages: [url],
+ representative_webpage_count: 1,
+ candidate_webpage_count: 1,
+ compare_summary: { review_needed_diffs: ["html_sha256"] }
+ }
+ )
+ website.transition_evidences.create!(id: next_id, url: url, check_kind: "statement_delta", status: "checked", statement_count_delta_acceptable: true, checked_at: 1.hour.ago)
+ website.transition_evidences.create!(id: next_id, url: url, check_kind: "export_diff", status: "checked", export_diff_checked: true, checked_at: 1.hour.ago)
+
+ get distillator_shadow_report_site_path(website)
+
+ assert_response :success
+ assert_match "Review before activating", @response.body
+ assert_match "Review checklist", @response.body
+ assert_match "Check statements page", @response.body
+ assert_match "Open compare page", @response.body
+ assert_select "form.website-transition-override-form textarea[name=?]", "reason", 0
+ assert_no_match "Promote to active", @response.body
+ assert_no_match "Activate anyway", @response.body
+ end
+
+ test "shadow report detail separates blocked activation from passing export and shows failed statement explanation" do
+ website = create_shadow_website(name: "Blocked detail", seedurl: "blocked-detail")
+ url = "https://blocked-detail.example/event"
+ create_cache_for(
+ website,
+ url: url,
+ signals: { "transport_success" => true, "content_success" => true }
+ )
+ webpage = website.webpages.find_by!(url: url)
+ source = Source.create!(
+ algorithm_value: "manual=Blocked detail dates",
+ selected: true,
+ selected_by: "test",
+ language: "fr",
+ render_js: false,
+ property: properties(:six),
+ website: website
+ )
+ statement = Statement.create!(
+ cache: "",
+ status: "problem",
+ status_origin: "shadow_reports_controller_test",
+ cache_refreshed: 1.hour.ago,
+ cache_changed: 1.hour.ago,
+ source: source,
+ webpage: webpage,
+ selected_individual: true
+ )
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url,
+ check_kind: "fetch_parity",
+ status: "checked",
+ checked_at: 1.hour.ago
+ )
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url,
+ check_kind: "statement_delta",
+ status: "failed",
+ statement_delta: 1,
+ statement_count_delta_acceptable: false,
+ checked_at: 1.hour.ago,
+ details: {
+ reason: "critical_statement_refresh_failed",
+ representative_webpages: [url],
+ representative_webpage_count: 1,
+ candidate_webpage_count: 4,
+ publishable_event_page_count: 4,
+ selection_rule: Distillator::TransitionCheck::SELECTION_RULE,
+ statements_refreshed_count: 1,
+ statements_failed_count: 1,
+ critical_statements_failed_count: 1,
+ optional_statements_failed_count: 0,
+ critical_failing_statement_ids: [statement.id],
+ critical_failing_statements: [{ id: statement.id, webpage_url: url, source: "Dates / fr", severity: "blocker" }],
+ refresh_errors: ["DSL returned blank result"]
+ }
+ )
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url,
+ check_kind: "export_diff",
+ status: "checked",
+ export_diff_checked: true,
+ checked_at: 1.hour.ago,
+ details: { export_compared: true, export_basis: "current export vs production-equivalent export" }
+ )
+
+ get distillator_shadow_report_site_path(website)
+
+ assert_response :success
+ assert_match "Blocked", @response.body
+ assert_match %r{Statement equivalence .*Failed}m, @response.body
+ assert_match %r{Fetch diagnostic .*Passed}m, @response.body
+ assert_no_match %r{Export confirmation }m, @response.body
+ assert_match "Critical statement refresh failed for 1 statement.", @response.body
+ visible_html = @response.body.split('Audit ').first
+ assert_includes visible_html, "Statement failure summary"
+ assert_includes visible_html, "Blocking statement failures"
+ assert_includes visible_html, "Blank DSL result - Dates / fr - 1 affected"
+ assert_not_includes visible_html, "Statement ID: #{statement.id}"
+ assert_match "Open the statement trace and fix the source before activating.", @response.body
+ assert_match "Checked 1 of 4 publishable event pages.", @response.body
+ assert_match "Publishable event pages: 4", @response.body
+ assert_match "Sampled count: 1", @response.body
+ assert_match Distillator::TransitionCheck::SELECTION_RULE, @response.body
+ assert_match "This check used a limited sample of representative webpages.", @response.body
+ assert_match "Do not activate yet", @response.body
+ end
+
+ test "shadow report detail shows optional statement warnings separately without blocking activation" do
+ website = create_shadow_website(name: "Optional statement warning detail", seedurl: "optional-statement-warning-detail")
+ url = "https://optional-statement-warning-detail.example/event"
+ create_cache_for(
+ website,
+ url: url,
+ signals: { "transport_success" => true, "content_success" => true }
+ )
+ webpage = website.webpages.find_by!(url: url)
+ statement = Statement.create!(
+ cache: "",
+ status: "problem",
+ status_origin: "shadow_reports_controller_test",
+ cache_refreshed: 1.hour.ago,
+ cache_changed: 1.hour.ago,
+ source: Source.create!(
+ algorithm_value: "manual=Optional warning description",
+ selected: true,
+ selected_by: "test",
+ language: "en",
+ render_js: false,
+ property: properties(:five),
+ website: website
+ ),
+ webpage: webpage,
+ selected_individual: true
+ )
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url,
+ check_kind: "fetch_parity",
+ status: "checked",
+ checked_at: 1.hour.ago
+ )
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url,
+ check_kind: "statement_delta",
+ status: "warning",
+ statement_delta: 0,
+ statement_count_delta_acceptable: true,
+ checked_at: 1.hour.ago,
+ details: {
+ reason: "optional_statement_refresh_warning",
+ representative_webpages: [url],
+ representative_webpage_count: 1,
+ candidate_webpage_count: 4,
+ publishable_event_page_count: 4,
+ selection_rule: Distillator::TransitionCheck::SELECTION_RULE,
+ statements_refreshed_count: 4,
+ statements_failed_count: 1,
+ critical_statements_failed_count: 0,
+ optional_statements_failed_count: 1,
+ optional_failing_statement_ids: [statement.id],
+ optional_failing_statements: [{ id: statement.id, webpage_url: url, source: "Description / en", severity: "warning" }],
+ refresh_errors: ["DSL returned blank result"]
+ }
+ )
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url,
+ check_kind: "export_diff",
+ status: "checked",
+ export_diff_checked: true,
+ checked_at: 1.hour.ago,
+ details: { export_compared: true, export_basis: "current export vs production-equivalent export" }
+ )
+
+ get distillator_shadow_report_site_path(website)
+
+ assert_response :success
+ assert_match "Review before activating", @response.body
+ assert_match "Critical statements passed; optional statement refresh warnings need review.", @response.body
+ assert_includes @response.body, "Optional statement warnings"
+ assert_includes @response.body, "Blank DSL result - Description / en - 1 affected"
+ assert_no_match "Critical statement refresh failed", @response.body
+ assert_no_match "Cannot activate yet: statements check failed.", @response.body
+ assert_no_match "Blocking statement failures", @response.body
+ assert_no_match "Do not activate yet", @response.body
+ end
+
+ test "shadow report detail shows legacy generic statement failure without fake split counts" do
+ website = create_shadow_website(name: "Legacy statement detail", seedurl: "legacy-statement-detail")
+ url = "https://legacy-statement-detail.example/event"
+ create_cache_for(
+ website,
+ url: url,
+ signals: { "transport_success" => true, "content_success" => true }
+ )
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url,
+ check_kind: "fetch_parity",
+ status: "checked",
+ checked_at: 1.hour.ago
+ )
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url,
+ check_kind: "statement_delta",
+ status: "failed",
+ statement_delta: 99,
+ statement_count_delta_acceptable: false,
+ checked_at: 1.hour.ago,
+ details: {
+ reason: "statement_refresh_failed",
+ representative_webpages: [url],
+ representative_webpage_count: 1,
+ statements_failed_count: 99,
+ refresh_errors: ["InvalidURL from json_url"]
+ }
+ )
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url,
+ check_kind: "export_diff",
+ status: "checked",
+ export_diff_checked: true,
+ checked_at: 1.hour.ago
+ )
+
+ get distillator_shadow_report_site_path(website)
+
+ assert_response :success
+ assert_match "Legacy statement check failed before critical/optional classification was available.", @response.body
+ assert_match "Rerun the transition batch check to record current statement evidence with critical/optional classification.", @response.body
+ assert_match "Legacy statement failures recorded: 99", @response.body
+ assert_no_match "Statement failures: 0", @response.body
+ assert_no_match "Critical statement failures: 0", @response.body
+ assert_no_match "Optional statement warnings: 99", @response.body
+ end
+
+ test "shadow report detail shows split optional warning evidence without blocked wording" do
+ website = create_shadow_website(name: "Split warning detail", seedurl: "split-warning-detail")
+ url = "https://split-warning-detail.example/event"
+ create_cache_for(
+ website,
+ url: url,
+ signals: { "transport_success" => true, "content_success" => true }
+ )
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url,
+ check_kind: "fetch_parity",
+ status: "checked",
+ checked_at: 1.hour.ago
+ )
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url,
+ check_kind: "statement_delta",
+ status: "warning",
+ statement_delta: 0,
+ statement_count_delta_acceptable: true,
+ checked_at: 1.hour.ago,
+ details: {
+ reason: "optional_statement_refresh_warning",
+ representative_webpages: [url],
+ representative_webpage_count: 1,
+ statements_failed_count: 99,
+ critical_statements_failed_count: 0,
+ optional_statements_failed_count: 99
+ }
+ )
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url,
+ check_kind: "export_diff",
+ status: "checked",
+ export_diff_checked: true,
+ checked_at: 1.hour.ago
+ )
+
+ get distillator_shadow_report_site_path(website)
+
+ assert_response :success
+ assert_match "Review before activating", @response.body
+ assert_match "Optional statement warnings: 99", @response.body
+ assert_no_match "Do not activate yet", @response.body
+ assert_no_match "Statement refresh found 99 failing statements.", @response.body
+ end
+
+ test "shadow report detail shows previous evidence warning when a newer batch check was requested" do
+ website = create_shadow_website(name: "Pending report detail", seedurl: "pending-report-detail")
+ url = "https://pending-report-detail.example/event"
+ create_cache_for(website, url: url)
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url,
+ check_kind: "fetch_parity",
+ status: "checked",
+ checked_at: Time.zone.parse("2026-05-25 12:00:00")
+ )
+ website.update!(transition_check_requested_at: Time.zone.parse("2026-05-25 12:15:00"))
+
+ get distillator_shadow_report_site_path(website)
+
+ assert_response :success
+ assert_match "Latest transition evidence:", @response.body
+ assert_match "New batch check requested; this report still shows previous evidence.", @response.body
+ end
+
+ test "shadow report detail shows statements not evaluated when fetch failed before statement refresh" do
+ website = create_shadow_website(name: "Fetch blocked detail", seedurl: "fetch-blocked-detail")
+ url = "https://fetch-blocked-detail.example/event"
+ create_cache_for(
+ website,
+ url: url,
+ signals: { "transport_success" => false, "content_success" => false },
+ health_status: "empty_body",
+ health_severity: "high",
+ primary_issue_key: "empty_body",
+ primary_issue_label: "Empty body",
+ primary_issue_severity: "failed"
+ )
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url,
+ check_kind: "statement_delta",
+ status: "pending",
+ checked_at: 1.hour.ago,
+ details: {
+ reason: "fetch_failed_before_statement_refresh",
+ representative_webpages: [url],
+ representative_webpage_count: 1,
+ candidate_webpage_count: 1,
+ selection_rule: Distillator::TransitionCheck::SELECTION_RULE,
+ statements_refreshed_count: 0,
+ statements_failed_count: 0
+ }
+ )
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url,
+ check_kind: "export_diff",
+ status: "checked",
+ export_diff_checked: true,
+ checked_at: 1.hour.ago,
+ details: { export_compared: true, export_basis: "current export vs production-equivalent export" }
+ )
+
+ get distillator_shadow_report_site_path(website)
+
+ assert_response :success
+ assert_match %r{Statement equivalence .*Not evaluated}m, @response.body
+ assert_match %r{Fetch diagnostic .*Failed}m, @response.body
+ assert_match "Fetch failed before statements could be refreshed.", @response.body
+ assert_match "Fix the fetch/cache failure first, then rerun the transition batch check.", @response.body
+ assert_no_match %r{Export confirmation }m, @response.body
+ assert_no_match "failed on 0 representative webpages", @response.body
+ assert_operator @response.body.scan("Cannot promote yet").count, :<=, 1
+ end
+
+ test "fetch blocker shows direct cache links and audit uses causal labels" do
+ Distillator::WringerEndpoint.stubs(:current).returns(
+ Distillator::WringerEndpoint::Result.new(
+ compatibility_base_url: "http://compat.example",
+ legacy_lookup_base_url: "http://wringer.example",
+ compatibility_source: "DISTILLATOR_COMPAT_BASE_URL",
+ state: :remote_configured,
+ status_label: "Current Wringer: Remote configured",
+ status_detail: "http://compat.example via DISTILLATOR_COMPAT_BASE_URL"
+ )
+ )
+ website = create_shadow_website(name: "Fetch links detail", seedurl: "fetch-links-detail")
+ url = "https://www.dansedanse.ca/fr/spectacles/message-in-a-bottle-sting-kate-prince"
+ cache = create_cache_for(
+ website,
+ url: url,
+ signals: {
+ "transport_success" => true,
+ "content_success" => false,
+ "policy_action" => "abort_update",
+ "content_rejected" => true,
+ "content_type" => "html",
+ "fetched_body_state" => "non_empty",
+ "fetched_body_bytes" => 2048,
+ "stored_body_state" => "not_stored",
+ "stored_body_bytes" => 0,
+ "storage_decision" => "abort_update",
+ "cache_body_empty_after_abort" => true,
+ "primary_issue_match" => {
+ "source" => "body_text",
+ "pattern" => "Une erreur est survenue",
+ "snippet" => "Une erreur est survenue. Veuillez reessayer."
+ }
+ },
+ health_status: "content_rejected",
+ health_severity: "high",
+ primary_issue_key: "generic_error_text",
+ primary_issue_label: "Generic error text observed",
+ primary_issue_severity: "failed"
+ )
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url,
+ check_kind: "fetch_parity",
+ status: "failed",
+ checked_at: 1.hour.ago,
+ primary_issue_key: "generic_error_text",
+ details: { reason: "cache_health_failed" }
+ )
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url,
+ check_kind: "statement_delta",
+ status: "pending",
+ checked_at: 1.hour.ago,
+ details: {
+ reason: "fetch_failed_before_statement_refresh",
+ representative_webpages: [url],
+ representative_webpage_count: 1,
+ candidate_webpage_count: 1,
+ selection_rule: Distillator::TransitionCheck::SELECTION_RULE,
+ statements_refreshed_count: 0,
+ statements_failed_count: 0
+ }
+ )
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url,
+ check_kind: "export_diff",
+ status: "pending",
+ checked_at: 1.hour.ago,
+ details: {
+ reason: "fetch_failed_before_export_comparison",
+ representative_webpages: [url],
+ representative_webpage_count: 1,
+ candidate_webpage_count: 1,
+ export_compared: false,
+ export_basis: "current export vs production-equivalent export"
+ }
+ )
+
+ get distillator_shadow_report_site_path(website)
+
+ assert_response :success
+ assert_match %r{all websites\s*\|\s*]+href="/websites/#{website.id}">#{Regexp.escape(website.name)} \s*\|\s*]+href="/webpages\?seedurl=#{Regexp.escape(website.seedurl)}"}, @response.body
+ assert_match "Condenser fetch/cache failed for one or more sampled URLs.", @response.body
+ assert_match "Main blocker", @response.body
+ assert_match "Failed layer: statements", @response.body
+ assert_match "Fetch failed before statements could be refreshed.", @response.body
+ assert_match "Fetch diagnostic", @response.body
+ assert_match "generic_error_text", @response.body
+ assert_match "High: Generic error text observed", @response.body
+ assert_match "Latest attempt", @response.body
+ assert_match "Latest successful refresh", @response.body
+ assert_match "Open failed cache result", @response.body
+ assert_match "Compare Condenser vs Wringer", @response.body
+ assert_match "Compare extracted statements", @response.body
+ assert_match "Open active Wringer cache", @response.body
+ assert_match "Open Condenser cache", @response.body
+ assert_match CGI.escape(url), @response.body
+ assert_match distillator_cache_path(cache), @response.body
+ assert_match "not evaluated", @response.body
+ assert_match "Blocked by fetch", @response.body
+ assert_no_match %r{Statements\s* missing }m, @response.body
+ assert_no_match %r{Export\s*missing }m, @response.body
+ end
+
+ test "shadow report detail distinguishes a latest condenser empty body failure" do
+ website = create_shadow_website(name: "Latest attempt failed", seedurl: "latest-attempt-failed")
+ url = "https://latest-attempt-failed.example/event"
+ create_cache_for(
+ website,
+ url: url,
+ signals: { "transport_success" => true, "content_success" => true },
+ health_status: "healthy",
+ health_severity: "ok"
+ )
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url,
+ check_kind: "fetch_parity",
+ status: "failed",
+ checked_at: Time.current,
+ primary_issue_key: "empty_body",
+ details: {
+ attempted_condenser_fetch: true,
+ comparison_performed: false,
+ representative_urls_checked: true,
+ reason: "empty_body"
+ }
+ )
+
+ get distillator_shadow_report_site_path(website)
+
+ assert_response :success
+ assert_match "Latest Condenser attempt failed: empty body.", @response.body
+ assert_match "Attempted Condenser fetch: yes", @response.body
+ end
+
+ test "shadow report detail distinguishes legacy lookup failure from condenser failure" do
+ website = create_shadow_website(name: "Legacy lookup failed", seedurl: "legacy-lookup-failed")
+ url = "https://legacy-lookup-failed.example/event"
+ create_cache_for(
+ website,
+ url: url,
+ signals: { "transport_success" => true, "content_success" => true },
+ health_status: "healthy",
+ health_severity: "ok"
+ )
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url,
+ check_kind: "fetch_parity",
+ status: "checked",
+ checked_at: Time.current,
+ details: {
+ attempted_condenser_fetch: true,
+ condenser_fetch_success: true,
+ comparison_performed: false,
+ legacy_lookup_status: "missing_config",
+ legacy_lookup_error: "missing_config",
+ representative_urls_checked: true,
+ reason: "legacy_lookup_missing_config"
+ }
+ )
+
+ get distillator_shadow_report_site_path(website)
+
+ assert_response :success
+ assert_match "Condenser fetch passed, but legacy Wringer lookup is missing staging configuration.", @response.body
+ assert_match "Condenser fetch: passed", @response.body
+ assert_match "Legacy lookup status: missing_config", @response.body
+ end
+
+ test "shadow report detail distinguishes unreachable legacy lookup from condenser failure" do
+ website = create_shadow_website(name: "Legacy lookup unreachable", seedurl: "legacy-lookup-unreachable")
+ url = "https://legacy-lookup-unreachable.example/event"
+ create_cache_for(
+ website,
+ url: url,
+ signals: { "transport_success" => true, "content_success" => true },
+ health_status: "healthy",
+ health_severity: "ok"
+ )
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url,
+ check_kind: "fetch_parity",
+ status: "checked",
+ checked_at: Time.current,
+ details: {
+ attempted_condenser_fetch: true,
+ condenser_fetch_success: true,
+ comparison_performed: false,
+ legacy_lookup_status: "unreachable",
+ legacy_lookup_error: "connection refused",
+ representative_urls_checked: true,
+ reason: "legacy_lookup_unreachable"
+ }
+ )
+
+ get distillator_shadow_report_site_path(website)
+
+ assert_response :success
+ assert_match "Condenser fetch passed, but legacy Wringer lookup failed.", @response.body
+ assert_match "Condenser fetch: passed", @response.body
+ assert_match "Legacy lookup status: unreachable", @response.body
+ assert_match "Legacy lookup error: connection refused", @response.body
+ end
+
+ test "shadow report detail distinguishes body omitted legacy lookup from condenser failure" do
+ website = create_shadow_website(name: "Legacy body omitted", seedurl: "legacy-body-omitted")
+ url = "https://legacy-body-omitted.example/event"
+ create_cache_for(
+ website,
+ url: url,
+ signals: { "transport_success" => true, "content_success" => true },
+ health_status: "healthy",
+ health_severity: "ok"
+ )
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url,
+ check_kind: "fetch_parity",
+ status: "checked",
+ checked_at: Time.current,
+ details: {
+ attempted_condenser_fetch: true,
+ condenser_fetch_success: true,
+ comparison_performed: false,
+ legacy_lookup_status: "body_omitted",
+ legacy_lookup_error: "legacy_body_omitted",
+ representative_urls_checked: true,
+ reason: "legacy_lookup_body_omitted"
+ }
+ )
+
+ get distillator_shadow_report_site_path(website)
+
+ assert_response :success
+ assert_match "Condenser fetch passed, but legacy Wringer body was omitted from the comparison endpoint.", @response.body
+ assert_match "Condenser fetch: passed", @response.body
+ assert_match "Legacy lookup status: body_omitted", @response.body
+ assert_match "Legacy lookup error: legacy_body_omitted", @response.body
+ assert_match "Verify the legacy Wringer body endpoint or compare using the legacy inspection link.", @response.body
+ assert_no_match "Activate after review", @response.body
+ assert_no_match "Review checklist", @response.body
+ assert_no_match "Promote to active", @response.body
+ end
+
+ test "shadow report detail shows every sampled representative url with cache links and distinguishes parity mismatch from fetch failure" do
+ Distillator::WringerEndpoint.stubs(:current).returns(
+ Distillator::WringerEndpoint::Result.new(
+ compatibility_base_url: "http://compat.example",
+ legacy_lookup_base_url: "http://wringer.example",
+ compatibility_source: "DISTILLATOR_COMPAT_BASE_URL",
+ state: :remote_configured,
+ status_label: "Current Wringer: Remote configured",
+ status_detail: "http://compat.example via DISTILLATOR_COMPAT_BASE_URL"
+ )
+ )
+ website = create_shadow_website(name: "Sample visibility detail", seedurl: "sample-visibility-detail")
+ primary_url = "https://sample-visibility-detail.example/future-one"
+ secondary_url = "https://sample-visibility-detail.example/future-two"
+ create_cache_for(
+ website,
+ url: primary_url,
+ html: "Shared title cached html differs",
+ body: "",
+ signals: {
+ "transport_success" => true,
+ "content_success" => true,
+ "content_type" => "html",
+ "stored_body_bytes" => 0,
+ "fetched_body_bytes" => 2048
+ },
+ health_status: "healthy",
+ health_severity: "ok"
+ )
+ website.transition_evidences.create!(
+ id: next_id,
+ url: primary_url,
+ check_kind: "fetch_parity",
+ status: "failed",
+ checked_at: 1.hour.ago,
+ details: {
+ reason: "cache_compare_blocking_regression",
+ representative_webpages: [primary_url, secondary_url],
+ representative_webpage_count: 2,
+ candidate_webpage_count: 4,
+ publishable_event_page_count: 4,
+ selection_rule: Distillator::TransitionCheck::SELECTION_RULE,
+ attempted_condenser_fetch: true,
+ condenser_fetch_success: true,
+ comparison_performed: true,
+ representative_url_results: [
+ {
+ url: primary_url,
+ webpage_id: website.webpages.find_by(url: primary_url)&.id,
+ fetch_status: "passed",
+ fetch_reason: "ok",
+ legacy_lookup_status: "ok",
+ comparison_status: "failed"
+ },
+ {
+ url: secondary_url,
+ fetch_status: "passed",
+ fetch_reason: "ok",
+ legacy_lookup_status: "ok",
+ comparison_status: "review"
+ }
+ ],
+ compare_summary: {
+ blocking_regressions: ["html_sha256"],
+ metadata_only_diffs: [],
+ review_needed_diffs: %w[content_type final_url transport_success content_success],
+ unknown_diffs: []
+ }
+ }
+ )
+
+ get distillator_shadow_report_site_path(website)
+
+ assert_response :success
+ assert_match "Main blocker", @response.body
+ assert_match "Failed layer: cache compare", @response.body
+ assert_match "Condenser and Wringer have a blocking parity mismatch.", @response.body
+ assert_no_match "Fetch/cache failed for the representative URL.", @response.body
+ assert_match "Sampled URL diagnostics", @response.body
+ assert_match primary_url, @response.body
+ assert_match secondary_url, @response.body
+ assert_operator @response.body.scan("Compare Condenser vs Wringer").count, :>=, 2
+ assert_operator @response.body.scan("Compare extracted statements").count, :>=, 2
+ assert_operator @response.body.scan("Open active Wringer cache").count, :>=, 2
+ assert_operator @response.body.scan("Open Condenser cache").count, :>=, 2
+ assert_match "Checked 2 of 4 publishable event pages.", @response.body
+ end
+
+ test "shadow report detail matrix renders three sampled urls with distinct outcomes and actions" do
+ Distillator::WringerEndpoint.stubs(:current).returns(
+ Distillator::WringerEndpoint::Result.new(
+ compatibility_base_url: "http://compat.example",
+ legacy_lookup_base_url: "http://wringer.example",
+ compatibility_source: "DISTILLATOR_COMPAT_BASE_URL",
+ state: :remote_configured,
+ status_label: "Current Wringer: Remote configured",
+ status_detail: "http://compat.example via DISTILLATOR_COMPAT_BASE_URL"
+ )
+ )
+ website = create_shadow_website(name: "Three row detail", seedurl: "three-row-detail")
+ url_one = "https://three-row-detail.example/one"
+ url_two = "https://three-row-detail.example/two"
+ url_three = "https://three-row-detail.example/three"
+ create_cache_for(website, url: url_one, signals: { "transport_success" => true, "content_success" => true }, health_status: "healthy", health_severity: "ok")
+ website.webpages.create!(id: next_id, url: url_two, language: "en", rdf_uri: "rdf:two", rdfs_class: rdfs_classes(:one))
+ website.webpages.create!(id: next_id, url: url_three, language: "en", rdf_uri: "rdf:three", rdfs_class: rdfs_classes(:one))
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url_one,
+ check_kind: "fetch_parity",
+ status: "checked",
+ checked_at: 1.hour.ago,
+ details: {
+ reason: "review_needed_difference",
+ failed_layer: "cache_compare",
+ affected_url_count: 2,
+ representative_webpages: [url_one, url_two, url_three],
+ representative_webpage_count: 3,
+ candidate_webpage_count: 3,
+ representative_url_results: [
+ { url: url_one, webpage_id: website.webpages.find_by(url: url_one).id, fetch_status: "passed", fetch_reason: "ok", legacy_lookup_status: "ok", comparison_status: "passed" },
+ { url: url_two, webpage_id: website.webpages.find_by(url: url_two).id, fetch_status: "passed", fetch_reason: "ok", legacy_lookup_status: "body_omitted", legacy_lookup_error: "legacy_body_omitted", comparison_status: "review" },
+ { url: url_three, webpage_id: website.webpages.find_by(url: url_three).id, fetch_status: "failed", fetch_reason: "cache_health_failed", legacy_lookup_status: nil, comparison_status: "unknown" }
+ ],
+ compare_summary: { blocking_regressions: [], metadata_only_diffs: [], review_needed_diffs: ["body"], unknown_diffs: [] }
+ }
+ )
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url_one,
+ check_kind: "statement_delta",
+ status: "failed",
+ checked_at: 1.hour.ago,
+ details: {
+ representative_webpages: [url_one, url_two, url_three],
+ representative_webpage_count: 3,
+ candidate_webpage_count: 3,
+ representative_url_statement_results: [
+ { url: url_one, status: "passed", reason: "ok" },
+ { url: url_two, status: "failed", reason: "statement_delta" },
+ { url: url_three, status: "blocked_by_fetch", reason: "cache_health_failed" }
+ ]
+ }
+ )
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url_one,
+ check_kind: "export_diff",
+ status: "failed",
+ export_diff_status: "failed",
+ checked_at: 1.hour.ago,
+ details: {
+ reason: "export_generation_failed",
+ representative_webpages: [url_one, url_two, url_three],
+ representative_webpage_count: 3,
+ candidate_webpage_count: 3,
+ representative_url_export_results: [
+ { url: url_one, status: "checked", reason: "ok" },
+ { url: url_two, status: "failed", reason: "export_generation_failed" },
+ { url: url_three, status: "blocked_by_fetch", reason: "cache_health_failed" }
+ ]
+ }
+ )
+
+ get distillator_shadow_report_site_path(website)
+
+ assert_response :success
+ assert_match "Sampled URL diagnostics", @response.body
+ assert_match url_one, @response.body
+ assert_match url_two, @response.body
+ assert_match url_three, @response.body
+ assert_match "Passed", @response.body
+ assert_match "Body omitted", @response.body
+ assert_match "Failed", @response.body
+ assert_operator @response.body.scan("Compare Condenser vs Wringer").count, :>=, 3
+ assert_operator @response.body.scan("Compare extracted statements").count, :>=, 3
+ assert_operator @response.body.scan("Open webpage record").count, :>=, 3
+ end
+
+ test "shadow report detail shows one failed representative fetch out of three without marking statements and export as fully passed" do
+ website = create_shadow_website(name: "Partial fetch detail", seedurl: "partial-fetch-detail")
+ url_one = "https://partial-fetch-detail.example/one"
+ url_two = "https://partial-fetch-detail.example/two"
+ url_three = "https://partial-fetch-detail.example/three"
+ create_cache_for(website, url: url_one, signals: { "transport_success" => true, "content_success" => true }, health_status: "healthy", health_severity: "ok")
+ website.webpages.create!(id: next_id, url: url_two, language: "en", rdf_uri: "rdf:two", rdfs_class: rdfs_classes(:one))
+ website.webpages.create!(id: next_id, url: url_three, language: "en", rdf_uri: "rdf:three", rdfs_class: rdfs_classes(:one))
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url_one,
+ check_kind: "fetch_parity",
+ status: "failed",
+ checked_at: 1.hour.ago,
+ details: {
+ reason: "cache_health_failed",
+ failed_layer: "fetch",
+ affected_url_count: 1,
+ representative_webpages: [url_one, url_two, url_three],
+ representative_webpage_count: 3,
+ candidate_webpage_count: 3,
+ representative_url_results: [
+ { url: url_one, webpage_id: website.webpages.find_by(url: url_one).id, fetch_status: "passed", fetch_reason: "ok", legacy_lookup_status: "ok", comparison_status: "passed" },
+ { url: url_two, webpage_id: website.webpages.find_by(url: url_two).id, fetch_status: "passed", fetch_reason: "ok", legacy_lookup_status: "ok", comparison_status: "passed" },
+ { url: url_three, webpage_id: website.webpages.find_by(url: url_three).id, fetch_status: "failed", fetch_reason: "cache_health_failed", legacy_lookup_status: nil, comparison_status: "unknown" }
+ ]
+ }
+ )
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url_one,
+ check_kind: "statement_delta",
+ status: "pending",
+ checked_at: 1.hour.ago,
+ details: {
+ reason: "partial_fetch_failed_before_statement_refresh",
+ representative_webpages: [url_one, url_two, url_three],
+ representative_webpage_count: 3,
+ candidate_webpage_count: 3,
+ representative_url_statement_results: [
+ { url: url_one, status: "passed", reason: "ok" },
+ { url: url_two, status: "passed", reason: "ok" },
+ { url: url_three, status: "blocked_by_fetch", reason: "cache_health_failed" }
+ ]
+ }
+ )
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url_one,
+ check_kind: "export_diff",
+ status: "pending",
+ export_diff_checked: false,
+ export_diff_status: "partial",
+ checked_at: 1.hour.ago,
+ details: {
+ reason: "partial_fetch_failed_before_export_comparison",
+ representative_webpages: [url_one, url_two, url_three],
+ representative_webpage_count: 3,
+ candidate_webpage_count: 3,
+ representative_url_export_results: [
+ { url: url_one, status: "checked", reason: "ok" },
+ { url: url_two, status: "checked", reason: "ok" },
+ { url: url_three, status: "blocked_by_fetch", reason: "cache_health_failed" }
+ ]
+ }
+ )
+
+ get distillator_shadow_report_site_path(website)
+
+ assert_response :success
+ assert_match "Failed layer: fetch", @response.body
+ assert_match "Reason: Cache health failed", @response.body
+ assert_match "Statement equivalence", @response.body
+ assert_match "Fetch diagnostic", @response.body
+ assert_match "Fetch diagnostic", @response.body
+ assert_match "Affected sampled URLs: 1", @response.body
+ assert_match url_one, @response.body
+ assert_match url_two, @response.body
+ assert_match url_three, @response.body
+ assert_match "Blocked by fetch", @response.body
+ assert_match "Inconclusive", @response.body
+ assert_no_match "Export comparison passed.", @response.body
+ assert_no_match "Statements check passed.", @response.body
+ end
+
+ test "shadow report detail does not block statements and export by fetch when fetch passed but comparison failed" do
+ website = create_shadow_website(name: "Actual failing layer detail", seedurl: "actual-failing-layer-detail")
+ url = "https://actual-failing-layer-detail.example/event"
+ create_cache_for(website, url: url, signals: { "transport_success" => true, "content_success" => true }, health_status: "healthy", health_severity: "ok")
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url,
+ check_kind: "fetch_parity",
+ status: "failed",
+ checked_at: 1.hour.ago,
+ details: {
+ reason: "cache_compare_blocking_regression",
+ failed_layer: "cache_compare",
+ affected_url_count: 1,
+ attempted_condenser_fetch: true,
+ condenser_fetch_success: true,
+ comparison_performed: true,
+ legacy_lookup_status: "ok",
+ representative_webpages: [url],
+ representative_webpage_count: 1,
+ candidate_webpage_count: 1,
+ representative_url_results: [
+ { url: url, webpage_id: website.webpages.find_by(url: url).id, fetch_status: "passed", fetch_reason: "ok", legacy_lookup_status: "ok", comparison_status: "failed" }
+ ],
+ compare_summary: { blocking_regressions: ["html_sha256"], metadata_only_diffs: [], review_needed_diffs: [], unknown_diffs: [] }
+ }
+ )
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url,
+ check_kind: "statement_delta",
+ status: "failed",
+ checked_at: 1.hour.ago,
+ details: {
+ reason: "statement_refresh_failed",
+ representative_webpages: [url],
+ representative_webpage_count: 1,
+ candidate_webpage_count: 1,
+ representative_url_statement_results: [{ url: url, status: "failed", reason: "statement_refresh_failed" }]
+ }
+ )
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url,
+ check_kind: "export_diff",
+ status: "failed",
+ export_diff_status: "failed",
+ checked_at: 1.hour.ago,
+ details: {
+ reason: "export_generation_failed",
+ representative_webpages: [url],
+ representative_webpage_count: 1,
+ candidate_webpage_count: 1,
+ representative_url_export_results: [{ url: url, status: "failed", reason: "export_generation_failed" }]
+ }
+ )
+
+ get distillator_shadow_report_site_path(website)
+
+ assert_response :success
+ assert_no_match "Fetch/cache failed for the representative URL.", @response.body
+ decision_html = @response.body[%r{}m]
+ assert_no_match "Blocked by fetch", decision_html
+ assert_no_match "blocked by fetch", decision_html
+ assert_match "Condenser and Wringer have a blocking parity mismatch.", @response.body
+ assert_match "statement refresh failed", @response.body.downcase
+ assert_match "export generation failed", @response.body.downcase
+ end
+
+ test "shadow report detail shows timeout budget as fetch root cause without claiming export passed" do
+ website = create_shadow_website(name: "Timeout detail", seedurl: "timeout-detail")
+ url_one = "https://timeout-detail.example/one"
+ url_two = "https://timeout-detail.example/two"
+ create_cache_for(website, url: url_one, signals: { "transport_success" => true, "content_success" => true }, health_status: "healthy", health_severity: "ok")
+ website.webpages.create!(id: next_id, url: url_two, language: "en", rdf_uri: "rdf:two", rdfs_class: rdfs_classes(:one))
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url_one,
+ check_kind: "fetch_parity",
+ status: "failed",
+ checked_at: 1.hour.ago,
+ details: {
+ reason: "transition_check_timeout_budget_exceeded",
+ failed_layer: "fetch",
+ affected_url_count: 1,
+ representative_webpages: [url_one, url_two],
+ representative_webpage_count: 2,
+ candidate_webpage_count: 2,
+ representative_url_results: [
+ { url: url_one, webpage_id: website.webpages.find_by(url: url_one).id, fetch_status: "passed", fetch_reason: "ok", legacy_lookup_status: "ok", comparison_status: "passed" },
+ { url: url_two, webpage_id: website.webpages.find_by(url: url_two).id, fetch_status: "failed", fetch_reason: "transition_check_timeout_budget_exceeded", legacy_lookup_status: nil, comparison_status: "not_performed" }
+ ]
+ }
+ )
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url_one,
+ check_kind: "statement_delta",
+ status: "pending",
+ checked_at: 1.hour.ago,
+ details: {
+ reason: "transition_check_timeout_budget_exceeded",
+ representative_webpages: [url_one, url_two],
+ representative_webpage_count: 2,
+ candidate_webpage_count: 2,
+ representative_url_statement_results: [
+ { url: url_one, status: "inconclusive", reason: "transition_check_timeout_budget_exceeded" },
+ { url: url_two, status: "blocked_by_fetch", reason: "transition_check_timeout_budget_exceeded" }
+ ]
+ }
+ )
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url_one,
+ check_kind: "export_diff",
+ status: "pending",
+ export_diff_checked: false,
+ export_diff_status: "pending",
+ checked_at: 1.hour.ago,
+ details: {
+ reason: "transition_check_timeout_budget_exceeded",
+ representative_webpages: [url_one, url_two],
+ representative_webpage_count: 2,
+ candidate_webpage_count: 2,
+ representative_url_export_results: [
+ { url: url_one, status: "inconclusive", reason: "transition_check_timeout_budget_exceeded" },
+ { url: url_two, status: "blocked_by_fetch", reason: "transition_check_timeout_budget_exceeded" }
+ ]
+ }
+ )
+
+ get distillator_shadow_report_site_path(website)
+
+ assert_response :success
+ assert_match "Failed layer: fetch", @response.body
+ assert_match "Reason: Transition check timeout budget exceeded", @response.body
+ assert_match "Statement equivalence", @response.body
+ assert_match "Fetch diagnostic", @response.body
+ assert_match "Fetch diagnostic", @response.body
+ assert_match "Affected sampled URLs: 1", @response.body
+ assert_match "Transition check reached its runtime budget before all sampled URLs were fetched.", @response.body
+ assert_match "Timed out", @response.body
+ assert_match "Blocked by fetch", @response.body
+ assert_no_match "Export comparison passed.", @response.body
+ end
+
+ test "shadow report detail keeps representative checked count honest across tiers" do
+ website = create_shadow_website(name: "Honest candidate count", seedurl: "honest-candidate-count")
+ url = "https://honest-candidate-count.example/future-one"
+ create_cache_for(
+ website,
+ url: url,
+ signals: { "transport_success" => true, "content_success" => true },
+ health_status: "healthy",
+ health_severity: "ok"
+ )
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url,
+ check_kind: "statement_delta",
+ status: "checked",
+ statement_count_delta_acceptable: true,
+ checked_at: 1.hour.ago,
+ details: {
+ representative_webpages: [
+ "https://honest-candidate-count.example/future-one",
+ "https://honest-candidate-count.example/future-two"
+ ],
+ representative_webpage_count: 2,
+ candidate_webpage_count: 9,
+ publishable_event_page_count: 9,
+ selected_candidate_tier_count: 9,
+ selection_rule: Distillator::TransitionCheck::SELECTION_RULE
+ }
+ )
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url,
+ check_kind: "export_diff",
+ status: "checked",
+ export_diff_checked: true,
+ checked_at: 1.hour.ago,
+ details: {
+ representative_webpages: [
+ "https://honest-candidate-count.example/future-one",
+ "https://honest-candidate-count.example/future-two"
+ ],
+ representative_webpage_count: 2,
+ candidate_webpage_count: 9,
+ publishable_event_page_count: 9,
+ selected_candidate_tier_count: 9,
+ export_compared: true,
+ export_basis: "current export vs production-equivalent export"
+ }
+ )
+
+ get distillator_shadow_report_site_path(website)
+
+ assert_response :success
+ assert_match "Checked 2 of 9 publishable event pages.", @response.body
+ assert_match "This check used a limited sample of representative webpages.", @response.body
+ end
+
+ test "diagnostics is read only and does not render transition check button or trailing separator" do
+ website = create_shadow_website(name: "Diagnostics detail", seedurl: "diagnostics-detail")
+ url = "https://diagnostics-detail.example/event"
+ create_cache_for(website, url: url)
+
+ get distillator_shadow_report_site_path(website)
+
+ assert_response :success
+ audit_html = @response.body[%r{Audit .*?}m]
+ assert_not_nil audit_html
+ assert_no_match "Queue transition check", audit_html
+ assert_no_match %r{\|\s*}m, audit_html
+ assert_equal 0, @response.body.scan("Queue transition check").size
+ end
+
+ test "transition detail suppresses empty operator context status and actions" do
+ website = create_shadow_website(name: "Context detail", seedurl: "context-detail")
+ create_cache_for(website, url: "https://context-detail.example/event")
+
+ get distillator_shadow_report_site_path(website)
+
+ assert_response :success
+ assert_no_match 'data-context-domain="status"', @response.body
+ assert_no_match 'data-context-domain="actions"', @response.body
+ end
+
+ test "shadow report detail keeps cache links in diagnostics and rollout events in audit" do
+ website = create_shadow_website(name: "IA detail", seedurl: "ia-detail")
+ url = "https://ia-detail.example/event"
+ create_cache_for(website, url: url)
+ website.rollout_events.create!(from_mode: "legacy", to_mode: "shadow", reason: "test", readiness_snapshot: { warnings: ["manual review"] })
+
+ get distillator_shadow_report_site_path(website)
+
+ assert_response :success
+ assert_match %r{Audit .*Cache links:}m, @response.body
+ assert_match %r{Audit .*Recent rollout events}m, @response.body
+ end
+
+ test "shadow report detail explains export generation failures and rdf diff counts" do
+ website = create_shadow_website(name: "Export detail", seedurl: "export-detail")
+ url = "https://export-detail.example/event"
+ create_cache_for(
+ website,
+ url: url,
+ signals: { "transport_success" => true, "content_success" => true }
+ )
+
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url,
+ check_kind: "statement_delta",
+ status: "checked",
+ statement_count_delta_acceptable: true,
+ checked_at: 1.hour.ago
+ )
+
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url,
+ check_kind: "export_diff",
+ status: "failed",
+ export_diff_status: "failed",
+ rdf_added_count: 2,
+ rdf_removed_count: 1,
+ checked_at: 1.hour.ago,
+ details: { reason: "export_generation_failed" }
+ )
+
+ get distillator_shadow_report_site_path(website)
+
+ assert_response :success
+ assert_match "Export could not be generated.", @response.body
+ assert_match "RDF added: 2", @response.body
+ assert_match "RDF removed: 1", @response.body
+ end
+
+ test "active transition report detail shows rollback guidance without fetching" do
+ website = create_regular_website(name: "Active detail", seedurl: "active-detail", mode: "active")
+
+ assert_read_only_page_does_not_fetch
+
+ get distillator_shadow_report_site_path(website)
+
+ assert_response :success
+ assert_match "Rollback path", @response.body
+ assert_match "Set rollout mode to Legacy in website options.", @response.body
+ assert_match "Wringer becomes production backend.", @response.body
+ assert_match "Condenser cache remains available for inspection.", @response.body
+ end
+
+ test "shadow report filters to la vitrine cohort and other cohort classes" do
+ lavitrine = create_shadow_website(name: "Hector Charland", seedurl: "hector-charland-com")
+ other = create_shadow_website(name: "Outside cohort", seedurl: "outside-cohort")
+
+ create_cache_for(
+ lavitrine,
+ url: "https://hector-charland-com.example/event",
+ signals: {
+ "transport_success" => true,
+ "content_success" => true,
+ "representative_urls_checked" => true,
+ "statement_count_delta_acceptable" => true
+ }
+ )
+ create_cache_for(
+ other,
+ url: "https://outside-cohort.example/event",
+ signals: {
+ "transport_success" => true,
+ "content_success" => true
+ }
+ )
+
+ get distillator_shadow_report_path, params: { cohort: "lavitrine_pipeline" }
+
+ assert_response :success
+ assert_match "Hector Charland", @response.body
+ assert_no_match "Outside cohort", @response.body
+
+ get distillator_shadow_report_path, params: { cohort: "other" }
+
+ assert_response :success
+ assert_match "Outside cohort", @response.body
+ assert_no_match "Hector Charland", @response.body
+ end
+
+ test "shadow report does not mutate rollout mode" do
+ website = create_shadow_website(name: "No mutation", seedurl: "no-mutation")
+
+ get distillator_shadow_report_path
+
+ assert_response :success
+ assert_equal "shadow", website.reload.distillator_mode
+ end
+
+ test "transition report row includes options link and priority subset card" do
+ website = create_shadow_website(name: "Priority shadow", seedurl: "hector-charland-com")
+ create_cache_for(website, url: "https://hector-charland-com.example/event")
+
+ get distillator_shadow_report_path
+
+ assert_response :success
+ assert_match "Priority sites", @response.body
+ assert_match options_path, @response.body
+ assert_match "Latest report", @response.body
+ assert_no_match "Compare Condenser vs Wringer", @response.body
+ assert_no_match "Open active cache", @response.body
+ end
+
+ test "shadow report shows staging warning and invalid on staging filter" do
+ ENV["DISTILLATOR_RUNTIME"] = "staging"
+ create_regular_website(name: "Legacy staging report", seedurl: "legacy-staging-report", mode: "legacy")
+ create_regular_website(name: "Shadow staging report", seedurl: "shadow-staging-report", mode: "shadow")
+
+ get distillator_shadow_report_path
+
+ assert_response :success
+ assert_includes @response.body, "Staging requires every website to be Shadow or Active."
+ assert_includes @response.body, "Invalid on staging"
+ ensure
+ ENV["DISTILLATOR_RUNTIME"] = nil
+ end
+
+ test "shadow report filters invalid on staging rows" do
+ ENV["DISTILLATOR_RUNTIME"] = "staging"
+ create_regular_website(name: "Legacy invalid report", seedurl: "legacy-invalid-report", mode: "legacy")
+ create_regular_website(name: "Active valid report", seedurl: "active-valid-report", mode: "active")
+
+ get distillator_shadow_report_path, params: { mode: "invalid_on_staging" }
+
+ assert_response :success
+ assert_includes @response.body, "Legacy invalid report"
+ assert_not_includes @response.body, "Active valid report"
+ ensure
+ ENV["DISTILLATOR_RUNTIME"] = nil
+ end
+
+ test "shadow report clamps the requested limit and keeps empty state rendering" do
+ 101.times do |index|
+ create_shadow_website(name: format("Clamp %03d", index), seedurl: "clamp-#{index}")
+ end
+
+ get distillator_shadow_report_path, params: { limit: "999", term: "not-a-real-site" }
+ follow_redirect! if response.redirect?
+
+ assert_response :success
+ assert_match(/name="limit"[^>]*value="100"/, @response.body)
+ assert_match "No sites found.", @response.body
+ end
+
+ test "transition report supports rollout mode filter" do
+ create_shadow_website(name: "Shadow site", seedurl: "shadow-site")
+ create_regular_website(name: "Active site", seedurl: "active-site", mode: "active")
+
+ get distillator_shadow_report_path, params: { mode: "active" }
+
+ assert_response :success
+ assert_includes @response.body, 'name="mode"'
+ assert_match "Active site", @response.body
+ assert_no_match %r{.*Shadow site.* }m, @response.body
+ end
+
+ private
+
+ def create_shadow_website(name:, seedurl:)
+ create_regular_website(name: name, seedurl: seedurl, mode: "shadow")
+ end
+
+ def create_regular_website(name:, seedurl:, mode:)
+ Website.create!(
+ id: next_id,
+ name: name,
+ seedurl: seedurl,
+ graph_name: "https://#{seedurl}.example/graph",
+ default_language: "en",
+ distillator_mode: mode
+ )
+ end
+
+ def create_cache_for(website, url:, signals: { "transport_success" => true, "content_success" => true }, primary_issue_key: nil, primary_issue_label: nil, primary_issue_severity: nil, health_status: "healthy", health_severity: "ok", redirected: false, final_url: nil, html: "cached", body: "cached")
+ website.webpages.create!(
+ id: next_id,
+ url: url,
+ language: "en",
+ rdf_uri: "adr:#{website.seedurl}",
+ rdfs_class: rdfs_classes(:one)
+ )
+
+ Distillator::FetchCache.create!(
+ id: next_id,
+ uri_key: CGI.escape(url),
+ normalized_url: url,
+ name: website.name,
+ html: html,
+ body: body,
+ http_response_code: 200,
+ scrape_date: 1.hour.ago,
+ successful_refresh: 1.hour.ago,
+ headers: {},
+ signals: signals.merge(
+ "primary_issue_key" => primary_issue_key,
+ "primary_issue_label" => primary_issue_label,
+ "primary_issue_severity" => primary_issue_severity
+ ).compact,
+ hints: Array(primary_issue_key).compact,
+ final_url: final_url || url,
+ redirect_chain: redirected ? [url, final_url || url] : [],
+ redirected: redirected,
+ health_status: health_status,
+ health_severity: health_severity,
+ primary_issue_key: primary_issue_key,
+ primary_issue_label: primary_issue_label,
+ primary_issue_severity: primary_issue_severity
+ )
+ end
+
+ def next_id
+ @next_id ||= 1_200_000_000 + ((Process.pid % 10_000) * 100_000)
+ @next_id += 1
+ end
+
+ def assert_read_only_page_does_not_fetch
+ Distillator::FetchCacheStore.expects(:fetch).never
+ Distillator::FetchService.expects(:fetch).never
+ Distillator::NativeFetch.expects(:call).never
+ Distillator::FetchShadowComparator.expects(:call).never
+ end
+
+ def assert_no_cohort_source_requests
+ WebMock.assert_not_requested(:any, Distillator::Cohorts::LavitrinePipeline.query_url)
+ end
+end
diff --git a/test/controllers/distillator/transition_checks_controller_test.rb b/test/controllers/distillator/transition_checks_controller_test.rb
new file mode 100644
index 00000000..c3ea32d7
--- /dev/null
+++ b/test/controllers/distillator/transition_checks_controller_test.rb
@@ -0,0 +1,96 @@
+require "test_helper"
+
+class Distillator::TransitionChecksControllerTest < ActionDispatch::IntegrationTest
+ setup do
+ @previous_queue_adapter = ActiveJob::Base.queue_adapter
+ ActiveJob::Base.queue_adapter = :test
+ ActiveJob::Base.queue_adapter.enqueued_jobs.clear
+ end
+
+ teardown do
+ ActiveJob::Base.queue_adapter.enqueued_jobs.clear
+ ActiveJob::Base.queue_adapter = @previous_queue_adapter
+ end
+
+ test "transition check queues a job for one website only and does not change rollout mode" do
+ target = Website.create!(
+ name: "Transition target",
+ seedurl: "transition-target",
+ graph_name: "https://example.org/transition-target",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+ other = Website.create!(
+ name: "Transition other",
+ seedurl: "transition-other",
+ graph_name: "https://example.org/transition-other",
+ default_language: "en",
+ distillator_mode: "legacy"
+ )
+
+ Distillator::TransitionCheckRunner.expects(:call).never
+
+ assert_difference -> { ActiveJob::Base.queue_adapter.enqueued_jobs.count { |job| job[:job] == Distillator::TransitionCheckJob } }, 1 do
+ post distillator_transition_checks_path, params: { website_id: target.id }
+ end
+
+ job = ActiveJob::Base.queue_adapter.enqueued_jobs.find { |entry| entry[:job] == Distillator::TransitionCheckJob }
+ assert_equal [target.id], job[:args]
+ assert_redirected_to distillator_shadow_report_site_path(target)
+ assert_equal "shadow", target.reload.distillator_mode
+ assert_equal "legacy", other.reload.distillator_mode
+ assert_not_nil target.reload.transition_check_requested_at
+ assert_equal 0, target.transition_evidences.count
+ assert_equal 0, other.transition_evidences.count
+ end
+
+ test "transition check redirects back to website transition section when return_to is provided" do
+ target = Website.create!(
+ name: "Transition target",
+ seedurl: "transition-target-return",
+ graph_name: "https://example.org/transition-target-return",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+
+ Distillator::TransitionCheckRunner.expects(:call).never
+
+ post distillator_transition_checks_path, params: { website_id: target.id, return_to: website_path(target, anchor: "website-transition") }
+
+ assert_redirected_to website_path(target, anchor: "website-transition")
+ assert_equal "Transition batch check queued. The latest transition report will update as evidence is recorded.", flash[:notice]
+ end
+
+ test "transition check allows deliberate relative webpages return_to" do
+ target = Website.create!(
+ name: "Transition target",
+ seedurl: "transition-target-webpages",
+ graph_name: "https://example.org/transition-target-webpages",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+
+ Distillator::TransitionCheckRunner.expects(:call).never
+
+ return_to = "/webpages?seedurl=#{target.seedurl}"
+ post distillator_transition_checks_path, params: { website_id: target.id, return_to: return_to }
+
+ assert_redirected_to return_to
+ end
+
+ test "transition check rejects unsafe external return_to values" do
+ target = Website.create!(
+ name: "Transition target",
+ seedurl: "transition-target-unsafe",
+ graph_name: "https://example.org/transition-target-unsafe",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+
+ Distillator::TransitionCheckRunner.expects(:call).never
+
+ post distillator_transition_checks_path, params: { website_id: target.id, return_to: "https://evil.example/steal" }
+
+ assert_redirected_to distillator_shadow_report_site_path(target)
+ end
+end
diff --git a/test/controllers/events_controller_test.rb b/test/controllers/events_controller_test.rb
index f04a94dc..d26ff875 100644
--- a/test/controllers/events_controller_test.rb
+++ b/test/controllers/events_controller_test.rb
@@ -1,12 +1,138 @@
require 'test_helper'
class EventsControllerTest < ActionDispatch::IntegrationTest
+ test "events index does not fetch" do
+ assert_read_only_page_does_not_fetch
+ get website_events_path(seedurl: "one")
+
+ assert_response :success
+ end
test "should get index for upcoming" do
get website_events_path(seedurl: "one", format: :json)
assert_response :success
end
+ test "events json index preserves event row contract" do
+ get website_events_path(seedurl: "one", format: :json)
+
+ assert_response :success
+ payload = JSON.parse(@response.body)
+ assert_equal "one", payload["seedurl"]
+ assert_includes payload.keys, "total_events"
+ assert_includes payload.keys, "events"
+ assert payload["events"].is_a?(Array)
+ end
+
+ test "events index renders harmonized table shell filters and sortable headers" do
+ get website_events_path(seedurl: "one")
+
+ assert_response :success
+ assert_select ".harmonized-table-shell", 1
+ assert_select ".harmonized-table-filters", 1
+ assert_select 'form[action="/websites/one/events"][method="get"]', 1
+ assert_select 'input[type="submit"][value="Apply filters"]', 1
+ assert_select 'a', text: "Reset filters"
+ assert_select 'th a[href*="sort=title"]'
+ assert_select 'th a[href*="sort=archive_date"]'
+ end
+
+ test "events index preserves active filters in sort links" do
+ get website_events_path(seedurl: "one"), params: { startDate: "2018-01-01", endDate: "2030-01-01", per_page: "10" }
+
+ assert_response :success
+ assert_sort_link_preserves_filters(
+ label: "Title",
+ sort_key: "title",
+ params: {
+ startDate: "2018-01-01",
+ endDate: "2030-01-01",
+ per_page: "10"
+ }
+ )
+ end
+
+ test "events index falls back safely for invalid sort and direction" do
+ get website_events_path(seedurl: "one"), params: { sort: "bogus", direction: "sideways" }
+
+ assert_response :redirect
+ assert_redirected_to website_events_path(seedurl: "one")
+ end
+
+ test "events nested canonical redirect does not duplicate seedurl query param" do
+ get website_events_path(seedurl: "one"), params: {
+ sort: "bogus",
+ direction: "sideways",
+ seedurl: "one"
+ }
+
+ assert_response :redirect
+ assert_redirected_to website_events_path(seedurl: "one")
+ assert_not_includes response.location, "?seedurl=one"
+ end
+
+ test "events index renders empty state" do
+ get website_events_path(seedurl: "one"), params: { startDate: "2100-01-01", endDate: "2100-12-31" }
+
+ assert_response :success
+ assert_select ".harmonized-table-empty-state", 1
+ end
+
+ test "pipeline health endpoint returns contract structure" do
+ get event_pipeline_health_path(id: "uri1", format: :json)
+ assert_response :success
+
+ payload = JSON.parse(response.body)
+ assert_equal "uri1", payload["event_id"]
+
+ pipeline = payload["pipeline"]
+ assert pipeline.is_a?(Hash)
+ assert_includes pipeline.keys, "status"
+ assert_includes pipeline.keys, "category"
+ assert_includes pipeline.keys, "message"
+ assert_includes pipeline.keys, "suggested_action"
+ assert_includes pipeline.keys, "metrics"
+ assert_includes pipeline.keys, "details"
+ assert pipeline["metrics"].is_a?(Hash)
+ assert pipeline["details"].is_a?(Hash)
+ end
+
+ test "pipeline health diagnosis mapping matches evaluator output" do
+ expected = Dsl::PipelineEvaluator.evaluate(event: "uri1")
+
+ get event_pipeline_health_path(id: "uri1", format: :json)
+ assert_response :success
+
+ pipeline = JSON.parse(response.body).fetch("pipeline")
+ assert_equal expected[:diagnosis][:status].to_s, pipeline["status"]
+ assert_equal expected[:diagnosis][:category].to_s, pipeline["category"]
+ assert_equal expected[:diagnosis][:suggested_action].to_s, pipeline["suggested_action"]
+ end
+
+ test "pipeline health handles missing pipeline data gracefully" do
+ get event_pipeline_health_path(id: "missing-uri", format: :json)
+ assert_response :success
+
+ pipeline = JSON.parse(response.body).fetch("pipeline")
+ assert pipeline["metrics"].is_a?(Hash)
+ assert_equal 0, pipeline["metrics"]["steps_count"]
+ end
+
+ test "pipeline health endpoint does not execute dsl runner" do
+ Dsl::Core::AlgorithmRunner.expects(:new).never
+
+ get event_pipeline_health_path(id: "uri1", format: :json)
+ assert_response :success
+ end
+
+ private
+
+ def assert_read_only_page_does_not_fetch
+ Distillator::FetchCacheStore.expects(:fetch).never
+ Distillator::FetchService.expects(:fetch).never
+ Distillator::NativeFetch.expects(:call).never
+ Distillator::FetchShadowComparator.expects(:call).never
+ end
end
diff --git a/test/controllers/graphs_controller_test.rb b/test/controllers/graphs_controller_test.rb
deleted file mode 100644
index 8a6b475d..00000000
--- a/test/controllers/graphs_controller_test.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-require 'test_helper'
-
-class GraphsControllerTest < ActionDispatch::IntegrationTest
- # test "the truth" do
- # assert true
- # end
-
-
-end
diff --git a/test/controllers/lists_controller_test.rb b/test/controllers/lists_controller_test.rb
deleted file mode 100644
index 92d6f18d..00000000
--- a/test/controllers/lists_controller_test.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-require 'test_helper'
-
-class ListsControllerTest < ActionDispatch::IntegrationTest
- # test "the truth" do
- # assert true
- # end
-end
diff --git a/test/controllers/options_controller_test.rb b/test/controllers/options_controller_test.rb
new file mode 100644
index 00000000..e86a718e
--- /dev/null
+++ b/test/controllers/options_controller_test.rb
@@ -0,0 +1,131 @@
+require "test_helper"
+
+class OptionsControllerTest < ActionDispatch::IntegrationTest
+ setup do
+ Website.update_all(distillator_mode: "shadow")
+ end
+
+ test "options page shows transition report link between cache and options in internal navigation" do
+ get options_path
+
+ assert_response :success
+ assert_match %r{/distillator/cache.*?/distillator/shadow_report.*?/options}m, @response.body
+ assert_match '>cache<', @response.body
+ assert_match '>Transition report<', @response.body
+ assert_match '>options<', @response.body
+ end
+
+ test "options page does not render transition check queue button" do
+ get options_path
+
+ assert_response :success
+ assert_not_includes @response.body, "Queue transition check"
+ end
+
+ test "options page shows preflight pass fail summary and staging rollout policy failure" do
+ ENV["DISTILLATOR_RUNTIME"] = "staging"
+ Website.create!(
+ name: "Legacy options website",
+ seedurl: "legacy-options-website",
+ graph_name: "https://legacy-options-website.example/graph",
+ default_language: "en",
+ distillator_mode: "legacy"
+ )
+ ApplicationController.helpers.stubs(:get_wringer_url_per_environment).returns("http://wringer.example")
+
+ get options_path
+
+ assert_response :success
+ assert_includes @response.body, "Status:"
+ assert_includes @response.body, "FAIL"
+ assert_includes @response.body, "Staging requires every website to be Shadow or Active."
+ ensure
+ ENV["DISTILLATOR_RUNTIME"] = nil
+ end
+
+ test "options page shows staging rollout repair dry run and apply button on staging" do
+ ENV["DISTILLATOR_RUNTIME"] = "staging"
+ website = Website.create!(
+ name: "Legacy repair options website",
+ seedurl: "legacy-repair-options-website",
+ graph_name: "https://legacy-repair-options-website.example/graph",
+ default_language: "en",
+ distillator_mode: "legacy"
+ )
+ ApplicationController.helpers.stubs(:get_wringer_url_per_environment).returns("http://wringer.example")
+
+ get options_path
+
+ assert_response :success
+ assert_includes @response.body, "Invalid staging websites:"
+ assert_includes @response.body, "Dry run"
+ assert_includes @response.body, website.name
+ assert_includes @response.body, "This action only runs on staging."
+ assert_includes @response.body, "It records rollout events."
+ assert_includes @response.body, "It changes only invalid modes to Shadow; Active and Shadow are unchanged."
+ assert_includes @response.body, "Move invalid staging websites to Shadow"
+ assert_includes @response.body, "Move all invalid staging websites to Shadow?"
+ ensure
+ ENV["DISTILLATOR_RUNTIME"] = nil
+ end
+
+ test "options apply staging rollout repair moves invalid sites to shadow" do
+ ENV["DISTILLATOR_RUNTIME"] = "staging"
+ website = Website.create!(
+ name: "Legacy repair apply options website",
+ seedurl: "legacy-repair-apply-options-website",
+ graph_name: "https://legacy-repair-apply-options-website.example/graph",
+ default_language: "en",
+ distillator_mode: "legacy"
+ )
+
+ post repair_staging_rollout_options_path
+
+ assert_redirected_to options_path
+ assert_equal "shadow", website.reload.distillator_mode
+ follow_redirect!
+ assert_includes @response.body, "Staging rollout repair moved 1 websites to Shadow. 0 unrepaired."
+ ensure
+ ENV["DISTILLATOR_RUNTIME"] = nil
+ end
+
+ test "options apply staging rollout repair outside staging redirects with alert and changes nothing" do
+ ENV["DISTILLATOR_RUNTIME"] = "production"
+ website = Website.create!(
+ name: "Legacy repair blocked options website",
+ seedurl: "legacy-repair-blocked-options-website",
+ graph_name: "https://legacy-repair-blocked-options-website.example/graph",
+ default_language: "en",
+ distillator_mode: "legacy"
+ )
+ Distillator::StagingRolloutRepair.expects(:call).never
+
+ post repair_staging_rollout_options_path
+
+ assert_redirected_to options_path
+ assert_equal "legacy", website.reload.distillator_mode
+ assert_equal "Staging rollout repair can only run on staging.", flash[:alert]
+ ensure
+ ENV["DISTILLATOR_RUNTIME"] = nil
+ end
+
+ test "options page hides staging rollout repair button when invalid count is zero" do
+ ENV["DISTILLATOR_RUNTIME"] = "staging"
+ ApplicationController.helpers.stubs(:get_wringer_url_per_environment).returns("http://wringer.example")
+
+ get options_path
+
+ assert_response :success
+ assert_includes @response.body, "Invalid staging websites:"
+ assert_not_includes @response.body, "Move invalid staging websites to Shadow"
+ ensure
+ ENV["DISTILLATOR_RUNTIME"] = nil
+ end
+
+ test "set_trace_view_mode stores cookie and redirects back" do
+ post set_trace_view_mode_path(2), headers: { "HTTP_REFERER" => options_url }
+
+ assert_redirected_to options_url
+ assert_equal "2", cookies[:trace_view_mode]
+ end
+end
diff --git a/test/controllers/places_controller_test.rb b/test/controllers/places_controller_test.rb
index 6fa39544..a4b408a8 100644
--- a/test/controllers/places_controller_test.rb
+++ b/test/controllers/places_controller_test.rb
@@ -1,7 +1,71 @@
require 'test_helper'
class PlacesControllerTest < ActionDispatch::IntegrationTest
- # test "the truth" do
- # assert true
- # end
+ test "places controller renders all supported legacy place cache shapes" do
+ Places::IndexQuery.stubs(:call).returns(
+ [
+ { rdf_uri: "legacy:simple", based_on: "http://example.com/legacy/simple", place_class: "Place", linked_name: "Wednesday @ Salle Andre-Mathieu", linked_uri: "adr:salle-andre-mathieu" },
+ { rdf_uri: "legacy:nested-a", based_on: "http://example.com/legacy/nested", place_class: "Place", linked_name: "Saturday @ Theatre des Muses", linked_uri: "http://example.com/muses" },
+ { rdf_uri: "legacy:nested-b", based_on: "http://example.com/legacy/nested", place_class: "Place", linked_name: "Monday @ Theatre des Muses", linked_uri: "http://example.com/muses" },
+ { rdf_uri: "legacy:json-string", based_on: "http://example.com/legacy/json-string", place_class: "Place", linked_name: "Main Hall", linked_uri: "" },
+ { rdf_uri: "legacy:raw-string", based_on: "http://example.com/legacy/raw-string", place_class: "Place", linked_name: "Fallback Hall", linked_uri: "" }
+ ]
+ )
+
+ get places_url(seedurl: websites(:one).seedurl)
+
+ assert_response :success
+ assert_includes @response.body, "Wednesday @ Salle Andre-Mathieu"
+ assert_includes @response.body, "Saturday @ Theatre des Muses"
+ assert_includes @response.body, "Monday @ Theatre des Muses"
+ assert_includes @response.body, "Main Hall"
+ assert_includes @response.body, "Fallback Hall"
+ end
+
+ test "places index renders harmonized table shell and sortable headers" do
+ get places_url(seedurl: websites(:one).seedurl)
+
+ assert_response :success
+ assert_select ".harmonized-table-shell", 1
+ assert_select 'th a[href*="sort=rdf_uri"]'
+ end
+
+ test "places index falls back safely for invalid sort and direction" do
+ get places_url(seedurl: websites(:one).seedurl, sort: "bogus", direction: "sideways")
+
+ assert_response :redirect
+ assert_redirected_to places_url(seedurl: websites(:one).seedurl)
+ end
+
+ test "places index renders empty state" do
+ website = Website.create!(
+ name: "empty place website",
+ seedurl: "empty-place-website",
+ graph_name: "http://example.com/empty-place-website",
+ default_language: "en",
+ distillator_mode: "legacy"
+ )
+
+ get places_url(seedurl: website.seedurl)
+
+ assert_response :success
+ assert_select ".harmonized-table-empty-state", 1
+ end
+
+ test "places index does not fetch" do
+ assert_read_only_page_does_not_fetch
+
+ get places_url(seedurl: websites(:one).seedurl)
+
+ assert_response :success
+ end
+
+ private
+
+ def assert_read_only_page_does_not_fetch
+ Distillator::FetchCacheStore.expects(:fetch).never
+ Distillator::FetchService.expects(:fetch).never
+ Distillator::NativeFetch.expects(:call).never
+ Distillator::FetchShadowComparator.expects(:call).never
+ end
end
diff --git a/test/controllers/properties_controller_test.rb b/test/controllers/properties_controller_test.rb
index 9e61288d..7ace9733 100644
--- a/test/controllers/properties_controller_test.rb
+++ b/test/controllers/properties_controller_test.rb
@@ -10,6 +10,30 @@ class PropertiesControllerTest < ActionDispatch::IntegrationTest
assert_response :success
end
+ test "properties index renders harmonized table shell and sortable headers" do
+ get properties_url
+
+ assert_response :success
+ assert_select ".harmonized-table-shell", 1
+ assert_select 'th a[href*="sort=label"]'
+ assert_select 'th a[href*="sort=uri"]'
+ end
+
+ test "properties index falls back safely for invalid sort and direction" do
+ get properties_url, params: { sort: "bogus", direction: "sideways" }
+
+ assert_response :redirect
+ assert_redirected_to properties_url
+ end
+
+ test "properties index renders empty state" do
+ get properties_url, params: { term: "no-such-property-filter" }
+
+ follow_redirect! if response.redirect?
+ assert_response :success
+ assert_select ".harmonized-table-empty-state", 1
+ end
+
test "should get new" do
get new_property_url
assert_response :success
@@ -28,6 +52,17 @@ class PropertiesControllerTest < ActionDispatch::IntegrationTest
assert_response :success
end
+ test "property show renders harmonized card hooks" do
+ get property_url(@property)
+
+ assert_response :success
+ assert_select ".harmonized-card-grid", minimum: 1
+ assert_select ".harmonized-card", minimum: 1
+ assert_select ".harmonized-card-title", minimum: 1
+ assert_select ".harmonized-card-value", minimum: 1
+ assert_select ".harmonized-card-actions", minimum: 1
+ end
+
test "should get edit" do
get edit_property_url(@property)
assert_response :success
diff --git a/test/controllers/rdfs_classes_controller_test.rb b/test/controllers/rdfs_classes_controller_test.rb
index 38d5bda1..ef0540f6 100644
--- a/test/controllers/rdfs_classes_controller_test.rb
+++ b/test/controllers/rdfs_classes_controller_test.rb
@@ -10,6 +10,29 @@ class RdfsClassesControllerTest < ActionDispatch::IntegrationTest
assert_response :success
end
+ test "rdfs classes index renders harmonized table shell and sortable headers" do
+ get rdfs_classes_url
+
+ assert_response :success
+ assert_select ".harmonized-table-shell", 1
+ assert_select 'th a[href*="sort=name"]'
+ end
+
+ test "rdfs classes index falls back safely for invalid sort and direction" do
+ get rdfs_classes_url, params: { sort: "bogus", direction: "sideways" }
+
+ assert_response :redirect
+ assert_redirected_to rdfs_classes_url
+ end
+
+ test "rdfs classes index renders empty state" do
+ get rdfs_classes_url, params: { term: "no-such-rdfs-class-filter" }
+
+ follow_redirect! if response.redirect?
+ assert_response :success
+ assert_select ".harmonized-table-empty-state", 1
+ end
+
test "should get new" do
get new_rdfs_class_url
assert_response :success
@@ -28,6 +51,17 @@ class RdfsClassesControllerTest < ActionDispatch::IntegrationTest
assert_response :success
end
+ test "rdfs class show renders harmonized card hooks" do
+ get rdfs_class_url(@rdfs_class)
+
+ assert_response :success
+ assert_select ".harmonized-card-grid", minimum: 1
+ assert_select ".harmonized-card", minimum: 1
+ assert_select ".harmonized-card-title", minimum: 1
+ assert_select ".harmonized-card-value", minimum: 1
+ assert_select ".harmonized-card-actions", minimum: 1
+ end
+
test "should get edit" do
get edit_rdfs_class_url(@rdfs_class)
assert_response :success
diff --git a/test/controllers/reports_controller_test.rb b/test/controllers/reports_controller_test.rb
index 1c1cc332..e79bdb0f 100644
--- a/test/controllers/reports_controller_test.rb
+++ b/test/controllers/reports_controller_test.rb
@@ -1,7 +1,100 @@
require 'test_helper'
class ReportsControllerTest < ActionDispatch::IntegrationTest
- # test "the truth" do
- # assert true
- # end
+ test "reports source does not fetch" do
+ assert_read_only_page_does_not_fetch
+
+ get source_reports_url(source_id: sources(:one).id)
+
+ assert_response :success
+ end
+
+ test "reports source renders harmonized table shell filters and sortable headers" do
+ get source_reports_url(source_id: sources(:one).id)
+
+ assert_response :success
+ assert_select ".harmonized-table-shell", 1
+ assert_select ".harmonized-table-filters", 1
+ assert_select 'form[action="/reports/source"][method="get"]', 1
+ assert_select 'input[type="submit"][value="Apply filters"]', 1
+ assert_select 'a', text: "Reset filters"
+ assert_select 'th a[href*="sort=event_title"]'
+ assert_select 'th a[href*="sort=archive_date"]'
+ end
+
+ test "reports source preserves active filters in sort links" do
+ get source_reports_url(source_id: sources(:one).id), params: { startDate: "2018-01-01", endDate: "2030-01-01", per_page: "10" }
+
+ assert_response :success
+ assert_sort_link_preserves_filters(
+ label: "Event title",
+ sort_key: "event_title",
+ params: {
+ startDate: "2018-01-01",
+ endDate: "2030-01-01",
+ per_page: "10"
+ }
+ )
+ end
+
+ test "reports source falls back safely for invalid sort and direction" do
+ get source_reports_url(source_id: sources(:one).id), params: { sort: "bogus", direction: "sideways" }
+
+ assert_response :redirect
+ assert_redirected_to source_reports_url(source_id: sources(:one).id)
+ end
+
+ test "reports source renders empty state" do
+ source = Source.create!(
+ algorithm_value: "report-empty",
+ selected: true,
+ selected_by: "Distillator",
+ language: "en",
+ render_js: false,
+ property: properties(:one),
+ website: websites(:one)
+ )
+
+ get source_reports_url(source_id: source.id)
+
+ assert_response :success
+ assert_select ".harmonized-table-empty-state", 1
+ end
+
+ test "reports source view renders rows from report_rows" do
+ get source_reports_url(source_id: sources(:one).id)
+
+ assert_response :success
+ row = Reports::IndexQuery.call(
+ filters: { source_id: sources(:one).id.to_s },
+ sort: Reports::IndexQuery::DEFAULT_SORT,
+ direction: Reports::IndexQuery::DEFAULT_DIRECTION,
+ page: 1,
+ per_page: Reports::IndexQuery::DEFAULT_PER_PAGE
+ ).first
+
+ assert row.present?
+ assert_includes @response.body, row[:event_title].to_s if row[:event_title].present?
+ assert_includes @response.body, row[:cache].to_s
+ assert_includes @response.body, row[:rdf_uri].to_s
+ end
+
+ test "reports source does not assign legacy filtered hashes" do
+ get source_reports_url(source_id: sources(:one).id)
+
+ assert_response :success
+ assert_nil assigns(:filtered_event_titles)
+ assert_nil assigns(:filtered_archive_dates)
+ assert_nil assigns(:filtered_event_uris)
+ assert_nil assigns(:statements)
+ end
+
+ private
+
+ def assert_read_only_page_does_not_fetch
+ Distillator::FetchCacheStore.expects(:fetch).never
+ Distillator::FetchService.expects(:fetch).never
+ Distillator::NativeFetch.expects(:call).never
+ Distillator::FetchShadowComparator.expects(:call).never
+ end
end
diff --git a/test/controllers/resources_controller_test.rb b/test/controllers/resources_controller_test.rb
index bf95a294..ff2f4aa6 100644
--- a/test/controllers/resources_controller_test.rb
+++ b/test/controllers/resources_controller_test.rb
@@ -27,6 +27,69 @@ class ResourcesControllerTest < ActionDispatch::IntegrationTest
assert_includes @response.body, "place"
end
+ test "resources json index preserves grouped resource contract" do
+ get website_all_resources_path(seedurl: websites(:one).seedurl, format: :json)
+
+ assert_response :success
+ payload = JSON.parse(@response.body)
+ assert payload.is_a?(Hash)
+ assert_includes payload.keys, "event"
+ assert_includes payload.keys, "place"
+ end
+
+ test "resources index renders harmonized table shell and sortable headers" do
+ assert_read_only_page_does_not_fetch
+ get website_all_resources_path(seedurl: websites(:one).seedurl)
+
+ assert_response :success
+ assert_select ".harmonized-table-shell", 1
+ assert_select 'th a[href*="sort=rdf_uri"]'
+ assert_select 'th a[href*="sort=rdfs_class_name"]'
+ end
+
+ test "resources index falls back safely for invalid sort and direction" do
+ get website_all_resources_path(seedurl: websites(:one).seedurl, sort: "bogus", direction: "sideways")
+
+ assert_response :redirect
+ assert_redirected_to website_all_resources_path(seedurl: websites(:one).seedurl)
+ end
+
+ test "resources nested canonical redirect does not duplicate seedurl query param" do
+ get website_all_resources_path(seedurl: "one"), params: {
+ sort: "bogus",
+ direction: "sideways",
+ seedurl: "one"
+ }
+
+ assert_response :redirect
+ assert_redirected_to website_all_resources_path(seedurl: "one")
+ assert_not_includes response.location, "?seedurl=one"
+ end
+
+ test "resources index renders empty state when website has no resources" do
+ assert_read_only_page_does_not_fetch
+ website = Website.create!(
+ name: "empty resource website",
+ seedurl: "empty-resource-website",
+ graph_name: "http://example.com/empty-resource-website",
+ default_language: "en",
+ distillator_mode: "legacy"
+ )
+
+ get website_all_resources_path(seedurl: website.seedurl)
+
+ assert_response :success
+ assert_select ".harmonized-table-empty-state", 1
+ end
+
+ test "resources index does not fetch" do
+ assert_read_only_page_does_not_fetch
+
+ get website_all_resources_path(seedurl: websites(:one).seedurl)
+
+ assert_response :success
+ end
+
test "index returns 404 if seedurl missing" do
get "/websites//resources.json"
assert_response :not_found
@@ -40,6 +103,16 @@ class ResourcesControllerTest < ActionDispatch::IntegrationTest
assert_match webpages(:one).rdf_uri, @response.body
end
+ test "resource uri page renders harmonized card hooks" do
+ get uri_resources_path(uri: webpages(:one).rdf_uri)
+
+ assert_response :success
+ assert_select ".harmonized-card-grid", minimum: 1
+ assert_select ".harmonized-card", minimum: 1
+ assert_select ".harmonized-card-title", minimum: 1
+ assert_select ".harmonized-card-value", minimum: 1
+ end
+
test "uri returns 400 if no param given" do
get uri_resources_path
assert_response :bad_request
@@ -169,4 +242,13 @@ class ResourcesControllerTest < ActionDispatch::IntegrationTest
get "/resources/unknown-uri/webpage_urls", as: :json
assert_response :not_found
end
+
+ private
+
+ def assert_read_only_page_does_not_fetch
+ Distillator::FetchCacheStore.expects(:fetch).never
+ Distillator::FetchService.expects(:fetch).never
+ Distillator::NativeFetch.expects(:call).never
+ Distillator::FetchShadowComparator.expects(:call).never
+ end
end
diff --git a/test/controllers/sources_controller_test.rb b/test/controllers/sources_controller_test.rb
index 8bef6caf..6d5e6f2c 100644
--- a/test/controllers/sources_controller_test.rb
+++ b/test/controllers/sources_controller_test.rb
@@ -6,19 +6,199 @@ class SourcesControllerTest < ActionDispatch::IntegrationTest
end
test "should get index" do
+ Distillator::FetchService.expects(:fetch).never
+
+ get sources_url
+
+ assert_response :success
+ assert_match "Quick filters", @response.body
+ assert_match "Advanced filters", @response.body
+ assert_match "name=\"term\"", @response.body
+ assert_match "name=\"property_id\"", @response.body
+ assert_match "name=\"language\"", @response.body
+ assert_match "name=\"selected\"", @response.body
+ assert_match "name=\"auto_review\"", @response.body
+ assert_match "name=\"render_js\"", @response.body
+
+ assert_operator @response.body.index("Quick filters"), :<, @response.body.index("Advanced filters")
+ assert_operator @response.body.index("Advanced filters"), :<, @response.body.index("sort=property_id")
+ assert_select "th a", text: /ID/
+ assert_select "th a", text: /Property/
+ assert_select "th a", text: /Label/
+ assert_select "th a", text: /Algorithm value/
+ assert_select "th a", text: /Selected/
+ assert_select "th a", text: /Render JS/
+ assert_select "th a", text: /Auto review/
+ assert_select "th a", text: /Last test/
+ assert_select "th", text: "Actions"
+ end
+
+ test "sources index renders harmonized table shell and filters" do
+ Distillator::FetchService.expects(:fetch).never
+
+ get sources_url
+
+ assert_response :success
+ assert_select ".harmonized-table-shell", 1
+ assert_select ".harmonized-table-filters", 1
+ assert_select 'form[action="/sources"][method="get"]', 1
+ assert_select 'input[type="submit"][value="Apply filters"]', 1
+ assert_select 'a', text: "Reset filters"
+ end
+
+ test "sources index renders sortable headers" do
+ Distillator::FetchService.expects(:fetch).never
+
get sources_url
+
+ assert_response :success
+ assert_select 'th a[href*="sort=id"]', text: /ID/
+ assert_select 'th a[href*="sort=property_id"]', text: /Property/
+ assert_select 'th a[href*="sort=algorithm_value"]', text: /Algorithm value/
+ assert_select 'th a[href*="sort=render_js"]', text: /Render JS/
+ assert_select 'th a[href*="sort=updated_at"]', text: /Last test/
+ end
+
+ test "sources index preserves active filters in sort links" do
+ Distillator::FetchService.expects(:fetch).never
+
+ get sources_url, params: { term: "query", selected: "true", language: "en", per_page: "10" }
+
+ assert_response :success
+ assert_sort_link_preserves_filters(
+ label: "Algorithm value",
+ sort_key: "algorithm_value",
+ params: {
+ term: "query",
+ selected: "true",
+ language: "en",
+ per_page: "10"
+ }
+ )
+ end
+
+ test "sources index filters through controller params using sources index query" do
+ Distillator::FetchService.expects(:fetch).never
+ matching = Source.create!(
+ algorithm_value: "controller query match",
+ selected: true,
+ selected_by: "Operator",
+ language: "fr",
+ render_js: true,
+ property: properties(:one),
+ website: websites(:one),
+ auto_review: true
+ )
+ non_matching = Source.create!(
+ algorithm_value: "controller query miss",
+ selected: false,
+ selected_by: "Operator",
+ language: "en",
+ render_js: false,
+ property: properties(:two),
+ website: websites(:two),
+ auto_review: false
+ )
+
+ get sources_url, params: { term: "controller query", language: "fr", render_js: "true" }
+
+ assert_response :success
+ assert_includes @response.body, matching.algorithm_value
+ assert_not_includes @response.body, non_matching.algorithm_value
+ end
+
+ test "sources index renders property label and visible dsl in main table" do
+ Distillator::FetchService.expects(:fetch).never
+ website = websites(:one)
+ property = properties(:one)
+
+ xpath_source = Source.create!(
+ algorithm_value: "xpath=//div[@class='event-title']",
+ label: "XPath title",
+ selected: true,
+ selected_by: "Operator",
+ language: "en",
+ render_js: false,
+ property: property,
+ website: website,
+ auto_review: false
+ )
+ ruby_source = Source.create!(
+ algorithm_value: "ruby=nodes.map(&:text)",
+ label: "Ruby title",
+ selected: false,
+ selected_by: "Operator",
+ language: "fr",
+ render_js: false,
+ property: property,
+ website: website,
+ auto_review: false
+ )
+ json_source = Source.create!(
+ algorithm_value: "json_url=https://example.test/events.json",
+ label: "JSON title",
+ selected: false,
+ selected_by: "Operator",
+ language: "",
+ render_js: true,
+ property: property,
+ website: website,
+ auto_review: true
+ )
+ manual_source = Source.create!(
+ algorithm_value: "manual=Festival Example",
+ label: "Manual title",
+ selected: false,
+ selected_by: "Operator",
+ language: "",
+ render_js: false,
+ property: property,
+ website: website,
+ auto_review: false
+ )
+
+ get sources_url, params: { seedurl: website.seedurl }
+
assert_response :success
+ assert_select "th a", text: /Property/
+ assert_select "th a", text: /Label/
+ assert_select "td.source-algorithm-cell pre", text: /xpath=/
+ assert_select "td.source-algorithm-cell pre", text: /ruby=/
+ assert_select "td.source-algorithm-cell pre", text: /json_url=/
+ assert_select "td.source-algorithm-cell pre", text: /manual=/
+ assert_includes @response.body, property.label
+ assert_includes @response.body, xpath_source.label
+ assert_includes @response.body, ruby_source.label
+ assert_includes @response.body, json_source.label
+ assert_includes @response.body, manual_source.label
end
- # test "should get new" do
- # get new_source_url
- # assert_response :success
- # end
- #
- # test "should get new of specific class rdfs_class :one" do
- # get new_source_url(rdfs_class_id: :one)
- # assert_response :success
- # end
+ test "sources index falls back safely for invalid sort and direction" do
+ Distillator::FetchService.expects(:fetch).never
+
+ get sources_url, params: { sort: "bogus", direction: "sideways" }
+
+ assert_response :redirect
+ assert_redirected_to "/sources"
+ end
+
+ test "sources index renders empty state" do
+ Distillator::FetchService.expects(:fetch).never
+
+ get sources_url, params: { term: "no-such-source-filter" }
+
+ follow_redirect! if response.redirect?
+ assert_response :success
+ assert_select ".harmonized-table-empty-state", 1
+ end
+
+ test "sources index does not fetch" do
+ assert_read_only_page_does_not_fetch
+
+ get sources_url
+
+ assert_response :success
+ end
test "should create source" do
assert_difference('Source.count') do
@@ -29,13 +209,106 @@ class SourcesControllerTest < ActionDispatch::IntegrationTest
end
test "should show source" do
+ assert_read_only_page_does_not_fetch
+ @source.website.update!(distillator_mode: "active")
+
get source_url(@source)
+
assert_response :success
+ assert_select 'details[data-operator-context-card]'
+ assert_select 'details[data-context-domain="status"]'
+ assert_select 'details[data-context-domain="actions"]'
+ assert_select 'details[data-context-domain="details"]'
+ assert_includes @response.body, "Active"
+ assert_includes @response.body, "Condenser serves production while Wringer stays available for diagnostics."
+ assert_includes @response.body, "Production: Condenser"
+ assert_match "Latest result", @response.body
+ assert_match "Primary actions", @response.body
+ assert_match "Compatibility / rollout", @response.body
+ assert_match "Diagnostics", @response.body
+ assert_match "Danger zone", @response.body
+ assert_match "This source is the website default for this property/language.", @response.body
+ assert_match "Raw extraction DSL", @response.body
+ assert_match "Latest statement cache / value", @response.body
+ assert_match "Test / Refresh latest statement", @response.body
+ assert_select "section.source-latest-result", 1
+ assert_select "section.source-latest-result pre.sources-show-pre", text: /MyString/
+ assert_select "section.source-algorithm-dsl pre.sources-show-pre", text: /MyString/
+ assert_select "details.source-show-details summary", text: "Diagnostics"
+ assert_select "details.source-show-details summary", text: "Compatibility / rollout"
+ assert_select "details.source-danger-zone summary", text: "Danger zone"
+ assert_operator @response.body.index("Latest result"), :<, @response.body.index("Compatibility / rollout")
+ assert_operator @response.body.index("Latest statement cache / value"), :<, @response.body.index("Compatibility / rollout")
+ assert_operator @response.body.index("Raw extraction DSL"), :<, @response.body.index("Danger zone")
+ end
+
+ test "show source explains activation impact for alternative source" do
+ assert_read_only_page_does_not_fetch
+
+ get source_url(sources(:two))
+
+ assert_response :success
+ assert_match "Activating this source will make it the website default for this property/language and replace the current default source for that scope.", @response.body
+ end
+
+ test "website view uses operator table without fetching" do
+ Distillator::FetchService.expects(:fetch).never
+ @source.website.update!(distillator_mode: "shadow")
+
+ get website_sources_url(id: @source.website_id)
+
+ assert_response :success
+ assert_includes @response.body, "Shadow"
+ assert_includes @response.body, "Wringer serves production while Condenser is checked in the background."
+ assert_includes @response.body, "Production: Wringer"
+ assert_match "Quick filters", @response.body
+ assert_match "Advanced filters", @response.body
+ assert_match "More", @response.body
+ assert_match "Primary", @response.body
+ assert_match "Diagnostics", @response.body
+ assert_match "Danger zone", @response.body
+ assert_match "Open active cache", @response.body
+ assert_match "Open Condenser cache", @response.body
+ assert_match "Inspect legacy Wringer", @response.body
+ assert_no_match "new cache", @response.body
end
test "should get edit" do
+ Distillator::FetchService.expects(:fetch).never
+
get edit_source_url(@source)
+
assert_response :success
+ assert_select ".source-form-cards .source-algorithm-card", 1
+ assert_select ".source-form-cards .source-diagnostics-card", 1
+ assert_match "Identity", @response.body
+ assert_match "Activation", @response.body
+ assert_match "Fetch strategy", @response.body
+ assert_match "Extraction algorithm", @response.body
+ assert_match "Diagnostics", @response.body
+ assert_match "Website default", @response.body
+ assert_match "Raw extraction DSL", @response.body
+ assert_match "Use pipeline steps such as", @response.body
+ assert_operator @response.body.index("source-algorithm-card"), :<, @response.body.index("source-diagnostics-card")
+ end
+
+ test "index exposes new source entry points and edit form contains grouped operator sections" do
+ Distillator::FetchService.expects(:fetch).never
+
+ get sources_url
+
+ assert_response :success
+ assert_match "New Event Source", @response.body
+
+ Distillator::FetchService.expects(:fetch).never
+ get edit_source_url(@source)
+ assert_response :success
+ assert_match "Identity", @response.body
+ assert_match "Activation", @response.body
+ assert_match "Fetch strategy", @response.body
+ assert_match "Extraction algorithm", @response.body
+ assert_match "Diagnostics", @response.body
+ assert_match "Raw extraction DSL", @response.body
end
test "should update source" do
@@ -50,4 +323,13 @@ class SourcesControllerTest < ActionDispatch::IntegrationTest
assert_redirected_to sources_url
end
+
+ private
+
+ def assert_read_only_page_does_not_fetch
+ Distillator::FetchCacheStore.expects(:fetch).never
+ Distillator::FetchService.expects(:fetch).never
+ Distillator::NativeFetch.expects(:call).never
+ Distillator::FetchShadowComparator.expects(:call).never
+ end
end
diff --git a/test/controllers/statements_controller_test.rb b/test/controllers/statements_controller_test.rb
index 8d390834..c38f402e 100644
--- a/test/controllers/statements_controller_test.rb
+++ b/test/controllers/statements_controller_test.rb
@@ -3,6 +3,16 @@
class StatementsControllerTest < ActionDispatch::IntegrationTest
setup do
@statement = statements(:one)
+ Distillator::WringerEndpoint.stubs(:current).returns(
+ Distillator::WringerEndpoint::Result.new(
+ compatibility_base_url: "https://wringer.example",
+ legacy_lookup_base_url: "https://wringer.example",
+ compatibility_source: Distillator::WringerEndpoint::CANONICAL_COMPATIBILITY_ENV,
+ state: :remote_configured,
+ status_label: "Current Wringer: Remote configured",
+ status_detail: "https://wringer.example"
+ )
+ )
end
test "should get index" do
@@ -10,11 +20,175 @@ class StatementsControllerTest < ActionDispatch::IntegrationTest
assert_response :success
end
+ test "statements index renders harmonized table shell and filters" do
+ get statements_url
+
+ assert_response :success
+ assert_select "details.statements-batch-actions", 1
+ assert_select ".harmonized-table-shell", 1
+ assert_select ".harmonized-table-filters", 1
+ assert_select 'form[action="/statements"][method="get"]', 1
+ assert_select 'input[type="submit"][value="Apply filters"]', 1
+ assert_select 'a', text: "Reset filters"
+ end
+
+ test "statements index renders batch actions above harmonized table" do
+ get statements_url
+
+ assert_response :success
+ assert_operator @response.body.index("statements-batch-actions"), :<, @response.body.index("harmonized-table-shell")
+ end
+
+ test "statements batch actions are inside collapsible operator section" do
+ get statements_url
+
+ assert_response :success
+ assert_select "details.statements-batch-actions", 1
+ assert_select "details.statements-batch-actions summary", text: "Batch actions"
+ assert_select "details.statements-batch-actions form[action='/statements/batch_update']", 1
+ assert_select "details.statements-batch-actions input[type='submit'][value='View']", 0
+ assert_select ".statements-batch-actions input[type='submit'][value='Update']", 1
+ assert_select ".statements-batch-actions input[type='submit'][value='Review all listed']", 1
+ assert_select ".statements-batch-actions input[type='submit'][value='Refresh all listed']", 1
+ end
+
+ test "statements batch actions do not render redundant view submit" do
+ get statements_url
+
+ assert_response :success
+ assert_select "details.statements-batch-actions input[type='submit'][value='View']", 0
+ assert_select ".harmonized-table-filters input[type='submit'][value='Apply filters']", 1
+ assert_select ".statements-batch-actions input[type='submit'][value='Update']", 1
+ assert_select ".statements-batch-actions input[type='submit'][value='Review all listed']", 1
+ assert_select ".statements-batch-actions input[type='submit'][value='Refresh all listed']", 1
+ end
+
+ test "statements batch actions explain they apply to current listed scope" do
+ get statements_url
+
+ assert_response :success
+ assert_select "details.statements-batch-actions .operator-note", text: /These actions apply to the current listed statements after filters and pagination\./
+ end
+
+ test "statements index renders sortable headers" do
+ get statements_url
+
+ assert_response :success
+ assert_select 'th a[href*="sort=id"]'
+ assert_select 'th a[href*="sort=cache"]'
+ assert_select 'th a[href*="sort=status"]'
+ assert_select 'th a[href*="sort=updated_at"]'
+ end
+
+ test "statements index preserves active filters in sort links" do
+ get statements_url, params: { cache: "MyString", status: "initial", manual: "false", per_page: "10" }
+
+ assert_response :success
+ assert_sort_link_preserves_filters(
+ label: "Cache",
+ sort_key: "cache",
+ params: {
+ cache: "MyString",
+ status: "initial",
+ manual: "false",
+ per_page: "10"
+ }
+ )
+ end
+
+ test "statements index falls back safely for invalid sort and direction" do
+ get statements_url, params: { sort: "bogus", direction: "sideways" }
+
+ assert_response :redirect
+ assert_redirected_to statements_url
+ end
+
+ test "statements index renders empty state" do
+ get statements_url, params: { cache: "no-such-statement-cache-value" }
+
+ follow_redirect! if response.redirect?
+ assert_response :success
+ assert_select ".harmonized-table-empty-state", 1
+ end
+
+ test "statements index does not fetch" do
+ assert_read_only_page_does_not_fetch
+
+ get statements_url
+
+ assert_response :success
+ end
+
test "should get new" do
get new_statement_url
assert_response :success
end
+ test "search_name propagates linked-data abort payload when cckg lookup fails" do
+ webpage = webpages(:one)
+ helper_proxy = mock("search_name_helpers")
+ helper_proxy.expects(:search_everywhere).with("Broken Venue", "Place", webpage).returns(
+ ["abort_update", {
+ error: "No server running at http://example.invalid/reconcile",
+ error_type: "LinkedDataLookupError",
+ source: "search_cckg",
+ query: "Broken Venue",
+ expected_class: "Place"
+ }]
+ )
+ StatementsController.any_instance.stubs(:helpers).returns(helper_proxy)
+
+ get "/statements/search_name.json", params: {
+ str: "Broken Venue",
+ expected_class: "Place",
+ webpage_id: webpage.id
+ }
+
+ assert_response :success
+ payload = JSON.parse(@response.body)
+ assert_equal "abort_update", payload.first
+ assert_equal "LinkedDataLookupError", payload.last["error_type"]
+ assert_equal "search_cckg", payload.last["source"]
+ end
+
+ test "compare_extracted renders read only parity diagnostics without persisting records" do
+ website, webpage = build_statement_compare_page_records
+ legacy_html, condenser_html = statement_compare_html_pair
+
+ Distillator::CacheCompare.expects(:call).with(uri: webpage.url).returns(
+ legacy_cache: { html: legacy_html },
+ condenser_cache: { html: condenser_html }
+ )
+
+ assert_no_difference("Statement.count") do
+ assert_no_difference("Source.count") do
+ assert_no_difference("Webpage.count") do
+ assert_no_difference("Distillator::FetchCache.count") do
+ assert_no_difference("Distillator::TransitionEvidence.count") do
+ assert_no_difference("Distillator::RolloutEvent.count") do
+ get compare_extracted_statements_url, params: { url: webpage.url, website_id: website.id }
+ end
+ end
+ end
+ end
+ end
+ end
+
+ assert_response :success
+ assert_includes @response.body, "Extracted Statement Comparison"
+ assert_includes @response.body, "Read-only diagnostic only"
+ assert_includes @response.body, "limited to essential properties 1, 3, 5, and 13"
+ assert_includes @response.body, "Same statements"
+ assert_includes @response.body, "Removed statements"
+ assert_includes @response.body, "Changed statements"
+ assert_includes @response.body, "Extraction errors"
+ assert_includes @response.body, "Compare extracted statements"
+ assert_includes @response.body, "Shared Title"
+ assert_includes @response.body, "Condenser description"
+ assert_includes @response.body, "Legacy note"
+ assert_not_includes @response.body, "Non essential photo"
+ end
+
test "should create statement" do
assert_difference('Statement.count') do
#use different combination of webpage_id and source_id
@@ -32,8 +206,259 @@ class StatementsControllerTest < ActionDispatch::IntegrationTest
end
test "should show statement" do
+ assert_read_only_page_does_not_fetch
+ get statement_url(@statement)
+ assert_response :success
+ end
+
+ test "statement show renders harmonized card hooks and preserves operator context" do
+ assert_read_only_page_does_not_fetch
+
+ get statement_url(@statement)
+
+ assert_response :success
+ assert_select ".harmonized-card-grid", minimum: 1
+ assert_select ".harmonized-card", minimum: 1
+ assert_select ".harmonized-card-title", minimum: 1
+ assert_select ".harmonized-card-value", minimum: 1
+ assert_select ".harmonized-card-actions", minimum: 1
+ assert_select 'details[data-operator-context-card]'
+ end
+
+ test "statement pages render cache links without fetching" do
+ assert_read_only_page_does_not_fetch
+ website = Website.create!(
+ name: "statement rollout active",
+ seedurl: "statement-rollout-active",
+ graph_name: "http://example.com/statement-rollout-active",
+ default_language: "en",
+ distillator_mode: "active"
+ )
+ webpage = Webpage.create!(
+ url: "http://example.com/statement-rollout-active",
+ language: "en",
+ rdf_uri: "rdf:statement-rollout-active",
+ rdfs_class: rdfs_classes(:one),
+ website: website
+ )
+ source = Source.create!(
+ algorithm_value: "xpath=//title/text()",
+ selected: true,
+ selected_by: "Distillator",
+ language: "en",
+ render_js: false,
+ property: properties(:one),
+ website: website
+ )
+ statement = Statement.create!(
+ cache: "Active rollout cache",
+ source: source,
+ webpage: webpage,
+ status: "ok",
+ status_origin: "condenser_refresh"
+ )
+
+ get statement_url(statement)
+ assert_response :success
+ assert_select 'details[data-operator-context-card]'
+ assert_select 'details[data-context-domain="status"]'
+ assert_select 'details[data-context-domain="actions"]'
+ assert_select 'details[data-context-domain="details"]'
+ assert_includes @response.body, "Active"
+ assert_includes @response.body, "Condenser serves production while Wringer stays available for diagnostics."
+ assert_includes @response.body, "Open active cache"
+ assert_includes @response.body, "Production: Condenser"
+ assert_includes @response.body, "Inspect legacy Wringer"
+ assert_operator @response.body.scan("Inspect legacy Wringer").size, :>=, 2
+ assert_includes @response.body, "Diagnose refresh"
+
+ get webpage_statements_url(url: webpage.url)
+ assert_response :success
+ assert_includes @response.body, "Active"
+ assert_includes @response.body, "Open active cache"
+ assert_includes @response.body, "Production: Condenser"
+ end
+
+ test "trace-step active cache link follows shadow mode" do
+ previous_mode = ENV["DISTILLATOR_FETCH_MODE"]
+ helper_proxy = mock("helper_proxy")
+ @statement.webpage.website.update!(distillator_mode: "shadow")
+ helper_proxy.expects(:refresh_statement_helper).with(@statement).returns(
+ data: ["value"],
+ trace: [
+ {
+ step: 1,
+ type: "url",
+ code: "url='http://example.org/page'",
+ input: [],
+ output: [],
+ url_before: "http://example.org/page",
+ url_after: "http://example.org/page",
+ website_id: @statement.webpage.website_id,
+ wringer: { signals: { network_status: "ok" } }
+ }
+ ],
+ errors: []
+ )
+ StatementsController.any_instance.stubs(:statement_refresh_helper_proxy).returns(helper_proxy)
+
+ ENV["DISTILLATOR_FETCH_MODE"] = "shadow"
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ assert_redirected_to statement_url(@statement)
+ follow_redirect_with_trace_visibility("always", "3")
+
+ assert_response :success
+ assert_includes @response.body, "/condenser/cache/compare?uri="
+ assert_includes @response.body, "Compare Condenser vs Wringer"
+ assert_includes @response.body, "Production: Wringer"
+ ensure
+ ENV["DISTILLATOR_FETCH_MODE"] = previous_mode
+ end
+
+ test "legacy statement page shows legacy warning" do
+ assert_read_only_page_does_not_fetch
+ @statement.webpage.website.update!(distillator_mode: "legacy")
+
+ get statement_url(@statement)
+
+ assert_response :success
+ assert_includes @response.body, "Legacy"
+ assert_includes @response.body, "Wringer serves production."
+ assert_includes @response.body, "Production: Wringer"
+ end
+
+ test "show does not execute trace rendering even when dsl_trace cookie is set" do
+ assert_read_only_page_does_not_fetch
+ get statement_url(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ assert_response :success
+ assert_no_match(/Algorithm Trace/, response.body)
+ end
+
+ test "trace persists across refresh" do
+ @statement.source.update!(algorithm_value: "manual=Traceable value")
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ assert_redirected_to statement_url(@statement)
+ assert session[:dsl_trace].present?
+ cookies[:trace_visibility] = "always"
+ cookies[:trace_view_mode] = "3"
+
+ get statement_url(@statement)
+ assert_response :success
+ assert assigns(:trace).present?
+ assert session[:dsl_trace].present?
+
+ get statement_url(@statement)
+ assert_response :success
+ assert assigns(:trace).present?
+ assert session[:dsl_trace].present?
+ end
+
+ test "trace is scoped to the refreshed statement" do
+ other_statement = statements(:two)
+ @statement.source.update!(algorithm_value: "manual=Traceable value")
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ assert_redirected_to statement_url(@statement)
+ assert_equal @statement.id, session[:dsl_trace].with_indifferent_access[:statement_id]
+
+ get statement_url(other_statement), headers: { "Cookie" => "trace_visibility=always; trace_view_mode=3" }
+
+ assert_response :success
+ assert_equal [], assigns(:trace)
+ assert_no_match(/Algorithm Trace/, response.body)
+ assert_nil session[:dsl_trace]
+ end
+
+ test "trace is not mutated between requests" do
+ @statement.source.update!(algorithm_value: "manual=Traceable value")
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ assert_redirected_to statement_url(@statement)
+ original = Marshal.load(Marshal.dump(session[:dsl_trace]))
+ cookies[:trace_visibility] = "always"
+ cookies[:trace_view_mode] = "3"
+
+ get statement_url(@statement)
+ trace_for_view = assigns(:trace)
+ steps = trace_for_view[:steps] || trace_for_view["steps"] || []
+ steps << { "step" => "mutated" }
+ assert_equal original.deep_stringify_keys, session[:dsl_trace].deep_stringify_keys
+ end
+
+ test "nested trace structures are not mutated" do
+ helper_proxy = mock("helper_proxy")
+ helper_proxy.expects(:refresh_statement_helper).with(@statement).returns(
+ data: ["value"],
+ trace: [
+ {
+ step: 1,
+ type: "url",
+ code: "url='http://example.com'",
+ input: [],
+ output: [],
+ probe: { result: { status: "ok", xpath: "//title", output: ["value"] } },
+ wringer: { signals: { network_status: "ok" } }
+ }
+ ],
+ errors: []
+ )
+ StatementsController.any_instance.stubs(:statement_refresh_helper_proxy).returns(helper_proxy)
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ assert_redirected_to statement_url(@statement)
+ original = Marshal.load(Marshal.dump(session[:dsl_trace]))
+ cookies[:trace_visibility] = "always"
+ cookies[:trace_view_mode] = "3"
get statement_url(@statement)
+
+ trace = assigns(:trace)
+ mutate_nested = lambda do |obj|
+ case obj
+ when Hash
+ obj.each do |k, v|
+ if v.is_a?(Hash) || v.is_a?(Array)
+ return true if mutate_nested.call(v)
+ elsif v.is_a?(String)
+ obj[k] = "changed"
+ return true
+ end
+ end
+ when Array
+ obj.each_with_index do |v, i|
+ if v.is_a?(Hash) || v.is_a?(Array)
+ return true if mutate_nested.call(v)
+ elsif v.is_a?(String)
+ obj[i] = "changed"
+ return true
+ end
+ end
+ end
+ false
+ end
+
+ mutated = mutate_nested.call(trace)
+
+ assert mutated, "Expected to find mutable nested trace signals in assigns(:trace)"
+ assert_equal original.deep_stringify_keys, session[:dsl_trace].deep_stringify_keys
+ end
+
+ test "trace defaults to empty array when not present" do
+ get statement_url(@statement)
+ session[:dsl_trace] = nil
+
+ get statement_url(@statement)
+
+ assert_equal [], assigns(:trace)
+ end
+
+ test "refresh + show uses real DSL pipeline" do
+ @statement.source.update!(algorithm_value: "manual=Traceable value")
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+
+ follow_redirect_with_trace_visibility("always", "3")
+
assert_response :success
+ assert_match(/Algorithm Trace/, response.body)
end
test "should get edit" do
@@ -59,6 +484,1440 @@ class StatementsControllerTest < ActionDispatch::IntegrationTest
assert_redirected_to webpage_statements_path(url: webpages(:six).url)
end
+ test "refresh_webpage keeps html notice compact for nested distillator abort payloads" do
+ nested_errors = [
+ {
+ "Property id 123" => [
+ {
+ error_type: "phantomjs_unavailable",
+ error: "Legacy PhantomJS renderer is unavailable",
+ signals: {
+ blocking_issue_key: "phantomjs_unavailable",
+ renderer_fallback: "direct_url",
+ primary_issue_category: "renderer",
+ phantomjs_iframe_extraction: false
+ }
+ }
+ ]
+ },
+ {
+ "Property id 456" => [
+ {
+ error_type: "redirect_to_listing",
+ error: "Fetch content blocked by redirect_to_listing",
+ signals: {
+ blocking_issue_key: "redirect_to_listing",
+ renderer_fallback: "direct_url"
+ }
+ }
+ ]
+ },
+ {
+ "Property id 789" => [
+ {
+ error_type: "timeout",
+ error: "execution expired",
+ signals: { blocking_issue_key: "timeout" }
+ }
+ ]
+ }
+ ]
+
+ StatementsController.any_instance.expects(:refresh_webpage_statements).returns(nested_errors)
+
+ assert_nothing_raised do
+ patch refresh_webpage_statements_path(url: webpages(:six).url)
+ end
+
+ assert_redirected_to webpage_statements_path(url: webpages(:six).url)
+ assert_match(/Refresh completed with 3 errors\./, flash[:notice].to_s)
+ assert_match(/phantomjs_unavailable/, flash[:notice].to_s)
+ assert_no_match(/renderer_fallback/, flash[:notice].to_s)
+ assert_no_match(/primary_issue_category/, flash[:notice].to_s)
+ assert_operator Marshal.dump(session.to_hash).bytesize, :<, 3000
+ end
+
+ test "refresh_webpage returns explicit missing webpage errors" do
+ patch refresh_webpage_statements_path(format: :json), params: { url: "https://example.org/missing" }
+
+ assert_response :not_found
+ payload = JSON.parse(response.body)
+ assert_equal "Webpage not found for URL: https://example.org/missing", payload["error"]
+ assert_equal "https://example.org/missing", payload["url"]
+
+ patch refresh_webpage_statements_path(url: "https://example.org/missing")
+
+ assert_redirected_to webpage_statements_path(url: "https://example.org/missing")
+ follow_redirect!
+ assert_response :not_found
+ assert_match(/Webpage not found for URL: https:\/\/example.org\/missing/, response.body)
+ end
+
+ test "refresh_rdf_uri forwards force_scrape_every_hrs 1 and reports per-webpage errors in json" do
+ rdf_uri = webpages(:one).rdf_uri
+ expected_options = { force_scrape_every_hrs: "1" }
+ expected_error = [{ "Property id 123" => { cache: ["failed refresh"] } }]
+
+ StatementsController.any_instance.expects(:refresh_webpage_statements).with do |webpage, default_language, scrape_options|
+ assert_equal rdf_uri, webpage.rdf_uri
+ assert_equal webpage.website.default_language, default_language
+ assert_equal expected_options, scrape_options
+ true
+ end.at_least_once.returns(expected_error)
+
+ patch refresh_rdf_uri_statements_path(format: :json), params: {
+ rdf_uri: rdf_uri,
+ force_scrape_every_hrs: "1"
+ }
+
+ assert_response :success
+ payload = JSON.parse(response.body)
+ assert_includes payload["message"], "Webpage id:"
+ assert_includes payload["message"], "failed refresh"
+ end
+
+ test "refresh_rdf_uri keeps html notice compact for nested distillator abort payloads" do
+ rdf_uri = webpages(:one).rdf_uri
+ nested_errors = [
+ {
+ "Property id 123" => [
+ {
+ error_type: "phantomjs_unavailable",
+ error: "Legacy PhantomJS renderer is unavailable",
+ signals: {
+ blocking_issue_key: "phantomjs_unavailable",
+ renderer_fallback: "direct_url",
+ primary_issue_category: "renderer"
+ }
+ }
+ ]
+ }
+ ]
+
+ StatementsController.any_instance.expects(:refresh_webpage_statements).at_least_once.returns(nested_errors)
+
+ assert_nothing_raised do
+ patch refresh_rdf_uri_statements_path, params: { rdf_uri: rdf_uri }
+ end
+
+ assert_redirected_to statements_path(rdf_uri: rdf_uri)
+ assert_match(/URI refreshed with \d+ webpage errors\./, flash[:notice].to_s)
+ assert_match(/phantomjs_unavailable/, flash[:notice].to_s)
+ assert_no_match(/renderer_fallback/, flash[:notice].to_s)
+ assert_no_match(/primary_issue_category/, flash[:notice].to_s)
+ assert_operator Marshal.dump(session.to_hash).bytesize, :<, 3000
+ end
+
+ test "refresh_rdf_uri forwards force_scrape_every_hrs 0 to statement refresh service" do
+ rdf_uri = webpages(:one).rdf_uri
+
+ StatementsController.any_instance.expects(:refresh_webpage_statements).with do |_webpage, _default_language, scrape_options|
+ assert_equal({ force_scrape_every_hrs: "0" }, scrape_options)
+ true
+ end.at_least_once.returns([])
+
+ patch refresh_rdf_uri_statements_path(format: :json), params: {
+ rdf_uri: rdf_uri,
+ force_scrape_every_hrs: "0"
+ }
+
+ assert_response :success
+ payload = JSON.parse(response.body)
+ assert_includes payload["message"], "URI refreshed."
+ end
+
+ test "refresh_rdf_uri returns explicit missing rdf_uri errors" do
+ patch refresh_rdf_uri_statements_path(format: :json), params: { rdf_uri: "footlight:missing" }
+
+ assert_response :not_found
+ payload = JSON.parse(response.body)
+ assert_equal "No webpages found for RDF URI: footlight:missing", payload["error"]
+ assert_equal "footlight:missing", payload["rdf_uri"]
+
+ patch refresh_rdf_uri_statements_path(rdf_uri: "footlight:missing")
+
+ assert_redirected_to statements_path(rdf_uri: "footlight:missing")
+ follow_redirect!
+ assert_match(/No webpages found for RDF URI: footlight:missing/, response.body)
+ end
+
+ test "refresh_statement populates distillator fetch cache through the real dsl fetch seam" do
+ previous_mode = ENV["DISTILLATOR_FETCH_MODE"]
+ ENV["DISTILLATOR_FETCH_MODE"] = "internal"
+ Distillator::FetchCache.delete_all
+ website = Website.create!(
+ name: "Controller Refresh Fixture",
+ seedurl: "controller-refresh-fixture",
+ graph_name: "https://fixtures.example/controller-refresh",
+ default_language: "en",
+ distillator_mode: "active"
+ )
+ rdfs_class = RdfsClass.create!(name: "ControllerRefreshClass")
+ property = Property.create!(
+ label: "Controller Refresh Title",
+ value_datatype: "MyString",
+ uri: "http://schema.org/name",
+ rdfs_class: rdfs_class
+ )
+ webpage = Webpage.create!(
+ url: "https://www.culture3r.com/evenements/gabrielle-caron-rodage/",
+ language: "en",
+ rdf_uri: "http://example.org/rdf/culture3r",
+ rdfs_class: rdfs_class,
+ website: website
+ )
+ source_one = Source.create!(
+ algorithm_value: "xpath=//h1/text()",
+ selected: true,
+ selected_by: "test",
+ language: "en",
+ render_js: false,
+ property: property,
+ website: website
+ )
+ stat_one = Statement.create!(
+ cache: "old one",
+ status: "initial",
+ status_origin: "test",
+ cache_refreshed: 1.day.ago,
+ cache_changed: 1.day.ago,
+ source: source_one,
+ webpage: webpage,
+ selected_individual: false
+ )
+ html = "Gabrielle Caron "
+
+ Distillator::FetchGuard.stubs(:check_url).returns(Distillator::FetchGuard::Result.new(allowed: true))
+ Distillator::FetchGuard.stubs(:check_response).returns(Distillator::FetchGuard::Result.new(allowed: true))
+ Distillator::NativeFetch.expects(:call).once.returns(
+ status: :ok,
+ body: html,
+ raw_body: html,
+ headers: { content_type: "text/html" },
+ final_url: webpage.url,
+ redirect_chain: [webpage.url],
+ wringer: { signals: {}, hints: [] },
+ http_code: 200
+ )
+
+ patch refresh_statement_path(stat_one)
+
+ assert_redirected_to statement_url(stat_one)
+ cache = Distillator::FetchCache.find_by(normalized_url: webpage.url)
+ assert cache
+ assert_equal webpage.url, cache.normalized_url
+ assert_equal 200, cache.http_response_code
+ assert_equal html, cache.html
+ assert_equal html, cache.body
+ assert_equal({ "content_type" => "text/html" }, cache.headers)
+ assert_equal [webpage.url], cache.redirect_chain
+ assert cache.signals.present?
+ assert cache.hints.is_a?(Array)
+ assert cache.scrape_date.present?
+ assert cache.successful_refresh.present?
+ assert_equal "Gabrielle Caron", stat_one.reload.cache
+
+ get "/distillator/cache.json", params: { term: "culture3r" }
+ assert_response :success
+ payload = JSON.parse(response.body)
+ assert_equal [cache.id], payload.map { |row| row["id"] }
+ ensure
+ ENV["DISTILLATOR_FETCH_MODE"] = previous_mode
+ end
+
+ test "refresh helper proxy does not leak trace cookies across requests" do
+ @statement.source.update!(algorithm_value: "manual=Traceable value")
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+
+ assert_redirected_to statement_url(@statement)
+ assert_session_trace_present_and_structured
+
+ patch refresh_statement_path(@statement)
+
+ assert_redirected_to statement_url(@statement)
+ assert_nil session[:dsl_trace]
+ end
+
+ test "refresh json success returns structured ok payload without redirect" do
+ helper_proxy = mock("helper_proxy")
+ helper_proxy.expects(:refresh_statement_helper).with(@statement).returns(
+ data: ["value"],
+ trace: nil,
+ errors: []
+ )
+ StatementsController.any_instance.stubs(:statement_refresh_helper_proxy).returns(helper_proxy)
+
+ patch refresh_statement_path(@statement, format: :json)
+
+ assert_response :success
+ assert_not response.redirect?
+ body = JSON.parse(response.body).with_indifferent_access
+ assert_equal "ok", body[:status]
+ assert_equal @statement.id, body[:statement_id]
+ assert_equal true, body[:result_present]
+ assert_equal false, body[:trace_present]
+ end
+
+ test "refresh json abort returns structured error payload with 422 and no redirect" do
+ helper_proxy = mock("helper_proxy")
+ helper_proxy.expects(:refresh_statement_helper).with(@statement).returns(
+ data: ["abort_update", { error: "Invalid URL resolved from nil", error_type: "InvalidURL", step: "url", source: "dsl_runner" }],
+ trace: nil,
+ errors: ["Scrape aborted (InvalidURL): Invalid URL resolved from nil"]
+ )
+ StatementsController.any_instance.stubs(:statement_refresh_helper_proxy).returns(helper_proxy)
+
+ patch refresh_statement_path(@statement, format: :json)
+
+ assert_response :unprocessable_entity
+ assert_not response.redirect?
+ body = JSON.parse(response.body).with_indifferent_access
+ assert_equal "error", body[:status]
+ assert_equal "dsl_abort", body[:kind]
+ assert_equal "Invalid URL resolved from nil", body[:error]
+ assert_equal "InvalidURL", body[:error_type]
+ assert_equal "url", body[:step]
+ assert_equal "dsl_runner", body[:source]
+ end
+
+ test "refresh json non-abort error returns refresh_error kind with 422 and no redirect" do
+ helper_proxy = mock("helper_proxy")
+ helper_proxy.expects(:refresh_statement_helper).with(@statement).returns(
+ data: nil,
+ trace: nil,
+ errors: ["DSL returned blank result (possible parsing failure)"]
+ )
+ StatementsController.any_instance.stubs(:statement_refresh_helper_proxy).returns(helper_proxy)
+
+ patch refresh_statement_path(@statement, format: :json)
+
+ assert_response :unprocessable_entity
+ assert_not response.redirect?
+ body = JSON.parse(response.body).with_indifferent_access
+ assert_equal "error", body[:status]
+ assert_equal "refresh_error", body[:kind]
+ assert_equal "DSL returned blank result (possible parsing failure)", body[:error]
+ assert_equal "RefreshError", body[:error_type]
+ assert_nil body[:step]
+ assert_equal "statements_controller", body[:source]
+ end
+
+ test "refresh json malformed abort payload is explicit invalid abort payload error" do
+ helper_proxy = mock("helper_proxy")
+ helper_proxy.expects(:refresh_statement_helper).with(@statement).returns(
+ data: ["abort_update", "invalid_payload"],
+ trace: nil,
+ errors: ["Scrape aborted"]
+ )
+ StatementsController.any_instance.stubs(:statement_refresh_helper_proxy).returns(helper_proxy)
+
+ patch refresh_statement_path(@statement, format: :json)
+
+ assert_response :unprocessable_entity
+ assert_not response.redirect?
+ body = JSON.parse(response.body).with_indifferent_access
+ assert_equal "error", body[:status]
+ assert_equal "dsl_abort", body[:kind]
+ assert_equal "InvalidAbortPayload", body[:error_type]
+ assert_equal "Malformed abort payload", body[:error]
+ assert_equal "statements_controller", body[:source]
+ end
+
+ test "refresh html success redirects with notice" do
+ helper_proxy = mock("helper_proxy")
+ helper_proxy.expects(:refresh_statement_helper).with(@statement).returns(
+ data: ["value"],
+ trace: nil,
+ errors: []
+ )
+ StatementsController.any_instance.stubs(:statement_refresh_helper_proxy).returns(helper_proxy)
+
+ patch refresh_statement_path(@statement)
+
+ assert_redirected_to statement_url(@statement)
+ assert_equal "Statement was successfully refreshed.", flash[:notice]
+ assert_nil flash[:alert]
+ end
+
+ test "refresh html abort redirects with alert and without success notice" do
+ helper_proxy = mock("helper_proxy")
+ helper_proxy.expects(:refresh_statement_helper).with(@statement).returns(
+ data: ["abort_update", { error: "Invalid URL resolved from nil", error_type: "InvalidURL", step: "url", source: "dsl_runner" }],
+ trace: nil,
+ errors: ["Scrape aborted (InvalidURL): Invalid URL resolved from nil"]
+ )
+ StatementsController.any_instance.stubs(:statement_refresh_helper_proxy).returns(helper_proxy)
+
+ patch refresh_statement_path(@statement)
+
+ assert_redirected_to statement_url(@statement)
+ assert_match(/Statement Error:/, flash[:alert].to_s)
+ assert_match(/InvalidURL/, flash[:alert].to_s)
+ assert_nil flash[:notice]
+ end
+
+ test "refresh html abort keeps flash compact and session under threshold" do
+ helper_proxy = mock("helper_proxy")
+ helper_proxy.expects(:refresh_statement_helper).with(@statement).returns(
+ data: ["abort_update", {
+ error: "Legacy PhantomJS renderer is unavailable",
+ error_type: "phantomjs_unavailable",
+ step: "url",
+ signals: {
+ blocking_issue_key: "phantomjs_unavailable",
+ renderer_fallback: "direct_url",
+ primary_issue_category: "renderer",
+ phantomjs_iframe_extraction: false
+ },
+ hints: ["legacy_phantomjs", "phantomjs_unavailable"]
+ }],
+ trace: build_large_realistic_trace(20),
+ errors: ["Scrape aborted (phantomjs_unavailable): issue=phantomjs_unavailable: Legacy PhantomJS renderer is unavailable"]
+ )
+ StatementsController.any_instance.stubs(:statement_refresh_helper_proxy).returns(helper_proxy)
+
+ assert_nothing_raised do
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ end
+
+ assert_redirected_to statement_url(@statement)
+ assert_match(/phantomjs_unavailable/, flash[:alert].to_s)
+ assert_no_match(/renderer_fallback/, flash[:alert].to_s)
+ assert_no_match(/primary_issue_category/, flash[:alert].to_s)
+ assert_no_match(/phantomjs_iframe_extraction/, flash[:alert].to_s)
+ assert_operator Marshal.dump(session.to_hash).bytesize, :<, 3000
+ end
+
+ test "success with trace shows notice and trace on redirected show page" do
+ @statement.source.update!(algorithm_value: "manual=Traceable value")
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ assert_redirected_to statement_url(@statement)
+ assert_session_trace_present_and_structured
+
+ follow_redirect_with_trace_visibility
+ assert_response :success
+ assert_match(/Statement was successfully refreshed\./, response.body)
+ assert_no_match(/Statement Error:/, response.body)
+ assert_match(/Algorithm Trace/, response.body)
+ assert_match(/Step 1/, response.body)
+ assert_match(/Step 1 — manual/, response.body)
+ assert_match(/Traceable value/, response.body)
+ assert_not_nil session[:dsl_trace]
+ end
+
+ test "no error + auto trace visibility hides trace" do
+ @statement.source.update!(algorithm_value: "manual=Traceable value")
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=auto" }
+ assert_redirected_to statement_url(@statement)
+ assert_session_trace_present_and_structured
+
+ follow_redirect_with_trace_visibility("auto")
+ assert_response :success
+ assert_no_match(/Algorithm Trace/, response.body)
+ assert_not_nil session[:dsl_trace]
+ end
+
+ test "error + auto trace visibility shows trace" do
+ @statement.source.update!(algorithm_value: "ruby=$array.each {|a| a")
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=auto" }
+ assert_redirected_to statement_url(@statement)
+ assert_session_trace_present_and_structured
+
+ follow_redirect_with_trace_visibility("auto")
+ assert_response :success
+ assert_match(/Algorithm Trace/, response.body)
+ assert_not_nil session[:dsl_trace]
+ end
+
+ test "trace view labels wringer fetch failures as fetch error" do
+ helper_proxy = mock("helper_proxy")
+ helper_proxy.expects(:refresh_statement_helper).with(@statement).returns(
+ data: nil,
+ trace: [
+ {
+ step: 1,
+ type: "url",
+ code: "url='http://example.com'",
+ input: [],
+ output: [],
+ error: { error: "network down", error_type: "WringerFetchError", source: "wringer" },
+ wringer: { error_type: "WringerFetchError", source: "wringer", signals: {}, hints: [] }
+ }
+ ],
+ errors: ["network down"]
+ )
+ StatementsController.any_instance.stubs(:statement_refresh_helper_proxy).returns(helper_proxy)
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ assert_redirected_to statement_url(@statement)
+ assert_session_trace_present_and_structured
+
+ follow_redirect_with_trace_visibility("always", "3")
+ assert_response :success
+ assert_match(/Fetch error/, response.body)
+ end
+
+ test "trace view labels dsl extraction failures as extraction error" do
+ helper_proxy = mock("helper_proxy")
+ helper_proxy.expects(:refresh_statement_helper).with(@statement).returns(
+ data: nil,
+ trace: [
+ {
+ step: 1,
+ type: "xpath",
+ code: "xpath=//h1/text()",
+ input: [],
+ output: [],
+ error: { error: "No nodes matched", error_type: "ExtractionFailed", source: "dsl_runner" },
+ wringer: { signals: {}, hints: [] }
+ }
+ ],
+ errors: ["No nodes matched"]
+ )
+ StatementsController.any_instance.stubs(:statement_refresh_helper_proxy).returns(helper_proxy)
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ assert_redirected_to statement_url(@statement)
+ assert_session_trace_present_and_structured
+
+ follow_redirect_with_trace_visibility("always", "3")
+ assert_response :success
+ assert_match(/Extraction error/, response.body)
+ end
+
+ test "always trace visibility always shows trace" do
+ @statement.source.update!(algorithm_value: "manual=Traceable value")
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ assert_redirected_to statement_url(@statement)
+ assert_session_trace_present_and_structured
+
+ follow_redirect_with_trace_visibility
+ assert_response :success
+ assert_match(/Algorithm Trace/, response.body)
+ assert_not_nil session[:dsl_trace]
+ end
+
+ test "hidden trace visibility never shows trace" do
+ @statement.source.update!(algorithm_value: "ruby=$array.each {|a| a")
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=hidden" }
+ assert_redirected_to statement_url(@statement)
+ assert_session_trace_present_and_structured
+
+ follow_redirect_with_trace_visibility("hidden")
+ assert_response :success
+ assert_no_match(/Algorithm Trace/, response.body)
+ assert_not_nil session[:dsl_trace]
+ end
+
+ test "defaults trace_view_mode to trace rendering when cookie is missing" do
+ @statement.source.update!(algorithm_value: "manual=Traceable value")
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ assert_redirected_to statement_url(@statement)
+ assert_session_trace_present_and_structured
+
+ follow_redirect_with_trace_visibility("always")
+ assert_response :success
+ assert_match(/Algorithm Trace/, response.body)
+ assert_not_nil session[:dsl_trace]
+ end
+
+ test "uses cookie trace_view_mode to hide trace when mode is 2" do
+ @statement.source.update!(algorithm_value: "manual=Traceable value")
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ assert_redirected_to statement_url(@statement)
+ assert_session_trace_present_and_structured
+
+ follow_redirect_with_trace_visibility("always", "2")
+ assert_response :success
+ assert_no_match(/Algorithm Trace/, response.body)
+ assert_not_nil session[:dsl_trace]
+ end
+
+ test "success without trace shows notice and does not render trace on redirected show page" do
+ @statement.source.update!(algorithm_value: "manual=No Trace")
+
+ patch refresh_statement_path(@statement)
+ assert_redirected_to statement_url(@statement)
+ assert_nil session[:dsl_trace]
+
+ follow_redirect_with_trace_visibility
+ assert_response :success
+ assert_match(/Statement was successfully refreshed\./, response.body)
+ assert_no_match(/Statement Error:/, response.body)
+ assert_no_match(/Algorithm Trace/, response.body)
+ assert_nil session[:dsl_trace]
+ end
+
+ test "error with trace shows alert and keeps trace rendering" do
+ @statement.source.update!(algorithm_value: "ruby=$array.each {|a| a")
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ assert_redirected_to statement_url(@statement)
+ assert_session_trace_present_and_structured
+
+ follow_redirect_with_trace_visibility
+ assert_response :success
+ assert_match(/Statement Error:/, response.body)
+ assert_no_match(/Statement was successfully refreshed\./, response.body)
+ assert_error_alert_if_present
+ assert_match(/Algorithm Trace/, response.body)
+ assert_match(/trace-error/, response.body)
+ assert_match(/⚠/, response.body)
+ assert_match(/SyntaxError/, response.body)
+ body = response.body
+ assert body.index("Statement Error") < body.index("Algorithm Trace")
+ assert_not_nil session[:dsl_trace]
+ end
+
+ test "error without trace shows alert and no trace rendering" do
+ @statement.source.update!(algorithm_value: "ruby=$array.each {|a| a")
+
+ patch refresh_statement_path(@statement)
+ assert_redirected_to statement_url(@statement)
+ assert_nil session[:dsl_trace]
+
+ follow_redirect_with_trace_visibility
+ assert_response :success
+ assert_match(/Statement Error:/, response.body)
+ assert_no_match(/Statement was successfully refreshed\./, response.body)
+ assert_error_alert_if_present
+ assert_no_match(/Algorithm Trace/, response.body)
+ assert_match(/SyntaxError/, response.body)
+ assert_nil session[:dsl_trace]
+ end
+
+ test "error without trace is visible in view" do
+ helper_proxy = mock("helper_proxy")
+ helper_proxy.expects(:refresh_statement_helper).with(@statement).returns(
+ data: nil,
+ trace: nil,
+ errors: ["boom"]
+ )
+ StatementsController.any_instance.stubs(:statement_refresh_helper_proxy).returns(helper_proxy)
+
+ patch refresh_statement_path(@statement)
+ assert_redirected_to statement_url(@statement)
+ assert_nil session[:dsl_trace]
+
+ follow_redirect_with_trace_visibility
+ assert_response :success
+ assert_match(/Statement Error: boom/, response.body)
+ assert_no_match(/Statement was successfully refreshed\./, response.body)
+ assert_error_alert_if_present
+ assert_no_match(/Algorithm Trace/, response.body)
+ assert_nil session[:dsl_trace]
+ end
+
+ test "error and trace both visible" do
+ helper_proxy = mock("helper_proxy")
+ helper_proxy.expects(:refresh_statement_helper).with(@statement).returns(
+ data: nil,
+ trace: [{ step: 1, type: "ruby", input: ["in"], output: ["out"] }],
+ errors: ["boom"]
+ )
+ StatementsController.any_instance.stubs(:statement_refresh_helper_proxy).returns(helper_proxy)
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ assert_redirected_to statement_url(@statement)
+ assert_session_trace_present_and_structured
+
+ follow_redirect_with_trace_visibility
+ assert_response :success
+ assert_match(/Statement Error: boom/, response.body)
+ assert_no_match(/Statement was successfully refreshed\./, response.body)
+ assert_error_alert_if_present
+ assert_match(/Algorithm Trace/, response.body)
+ body = response.body
+ assert body.index("Statement Error") < body.index("Algorithm Trace")
+ assert_not_nil session[:dsl_trace]
+ end
+
+ test "error is not swallowed when trace is present" do
+ helper_proxy = mock("helper_proxy")
+ helper_proxy.expects(:refresh_statement_helper).with(@statement).returns(
+ data: nil,
+ trace: [{ step: 1, type: "ruby", input: ["in"], output: ["out"] }],
+ errors: ["critical failure"]
+ )
+ StatementsController.any_instance.stubs(:statement_refresh_helper_proxy).returns(helper_proxy)
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ assert_redirected_to statement_url(@statement)
+ assert_session_trace_present_and_structured
+
+ follow_redirect_with_trace_visibility
+ assert_response :success
+ assert_match(/Statement Error: critical failure/, response.body)
+ assert_no_match(/Statement was successfully refreshed\./, response.body)
+ assert_error_alert_if_present
+ assert_match(/Algorithm Trace/, response.body)
+ body = response.body
+ assert body.index("Statement Error") < body.index("Algorithm Trace")
+ assert_not_nil session[:dsl_trace]
+ end
+
+ test "empty trace still shows error" do
+ helper_proxy = mock("helper_proxy")
+ helper_proxy.expects(:refresh_statement_helper).with(@statement).returns(
+ data: nil,
+ trace: [],
+ errors: ["failure"]
+ )
+ StatementsController.any_instance.stubs(:statement_refresh_helper_proxy).returns(helper_proxy)
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ assert_redirected_to statement_url(@statement)
+ trace = session[:dsl_trace].with_indifferent_access
+ assert_equal 2, trace[:version]
+ assert_empty trace[:steps]
+
+ follow_redirect_with_trace_visibility
+ assert_response :success
+ assert_match(/Statement Error: failure/, response.body)
+ assert_no_match(/Statement was successfully refreshed\./, response.body)
+ assert_error_alert_if_present
+ assert_match(/Algorithm Trace/, response.body)
+ body = response.body
+ assert body.index("Statement Error") < body.index("Algorithm Trace")
+ assert_not_nil session[:dsl_trace]
+ end
+
+ test "refresh stores formatted trace in session" do
+ @statement.source.update!(algorithm_value: "manual=Traceable value")
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ assert_redirected_to statement_url(@statement)
+
+ assert_nil flash[:dsl_trace]
+ trace = assert_session_trace_present_and_structured
+
+ assert_equal @statement.id, trace[:statement_id]
+ first = trace[:steps].first.with_indifferent_access
+ assert_equal 1, first[:s]
+ assert_equal "manual", first[:t]
+
+ follow_redirect_with_trace_visibility
+ assert_response :success
+ assert_not_nil session[:dsl_trace]
+ end
+
+ test "show retrieves trace after redirect and keeps session trace" do
+ @statement.source.update!(algorithm_value: "manual=Traceable value")
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ assert_redirected_to statement_url(@statement)
+ assert_session_trace_present_and_structured
+
+ follow_redirect_with_trace_visibility
+ assert_response :success
+ assert_match(/Algorithm Trace/, response.body)
+ assert_not_nil session[:dsl_trace]
+ end
+
+ test "helper contract returns structured result" do
+ helpers = Class.new do
+ include StatementsHelper
+
+ def cookies
+ @cookies ||= {}
+ end
+ end.new
+
+ helpers.stubs(:run_dsl).returns("contract value")
+ helpers.stubs(:format_datatype).returns("contract value")
+ helpers.stubs(:save_record?).returns(false)
+
+ result = helpers.refresh_statement_helper(@statement)
+
+ assert result.is_a?(Hash)
+ assert result.key?(:data)
+ assert result.key?(:trace)
+ assert result.key?(:errors)
+ assert result[:errors].is_a?(Array)
+ end
+
+ test "trace does not appear when trace is nil" do
+ helper_proxy = mock("helper_proxy")
+ helper_proxy.expects(:refresh_statement_helper).returns(
+ data: nil,
+ trace: nil,
+ errors: []
+ )
+ StatementsController.any_instance.stubs(:statement_refresh_helper_proxy).returns(helper_proxy)
+
+ patch refresh_statement_path(@statement)
+ assert_redirected_to statement_url(@statement)
+ assert_nil session[:dsl_trace]
+
+ follow_redirect_with_trace_visibility
+ assert_response :success
+ assert_no_match(/Algorithm Trace/, response.body)
+ end
+
+ test "error step is always visible even with large trace" do
+ large_trace = (1..10).map do |i|
+ {
+ step: i,
+ type: "ruby",
+ code: "x" * 1000,
+ input: ["y" * 1000],
+ output: ["z" * 1000]
+ }
+ end
+
+ large_trace.last[:error_class] = "NoMethodError"
+ large_trace.last[:error_message] = "boom"
+
+ stub_helper_with_trace(large_trace)
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ follow_redirect_with_trace_visibility
+
+ body = response.body
+
+ assert_match(/Algorithm Trace/, body)
+ assert_match(/NoMethodError/, body)
+ end
+
+ test "all trace steps are preserved in session and rendered" do
+ trace = (1..10).map do |i|
+ { step: i, type: "ruby" }
+ end
+
+ stub_helper_with_trace(trace)
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+
+ assert_equal 10, session[:dsl_trace].with_indifferent_access[:steps].size
+
+ follow_redirect_with_trace_visibility
+
+ (1..10).each do |i|
+ assert_match(/Step #{i}/, response.body)
+ end
+ end
+
+ test "trace displays state transitions instead of table" do
+ trace = [
+ { step: 1, type: "ruby", input_preview: ["one"], output_preview: ["two"] }
+ ]
+ stub_helper_with_trace(trace)
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ follow_redirect_with_trace_visibility
+
+ assert_match(/Algorithm Trace/, response.body)
+ assert_match(/→/, response.body)
+ assert_no_match(/ "dsl_trace=true; trace_visibility=always" }
+ follow_redirect_with_trace_visibility
+
+ assert_match(/\[\d+ items:/, response.body)
+ assert_match(%r{https://example.com}, response.body)
+ assert_no_match(/extra2/, response.body)
+ assert_no_match(/z{70}/, response.body)
+ end
+
+ test "code is truncated in trace viewer" do
+ trace = [
+ {
+ step: 1,
+ type: "ruby",
+ code: "x" * 500,
+ input_preview: ["in"],
+ output_preview: ["out"]
+ }
+ ]
+ stub_helper_with_trace(trace)
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ follow_redirect_with_trace_visibility
+
+ displayed_code = response.body[%r{\s*
\s*]*>([^<]+) \s*}m, 1]
+ assert displayed_code.present?
+ assert_operator displayed_code.length, :<=, StatementsController::TRACE_CODE_DEFAULT
+ assert_match(/x{20}/, displayed_code)
+ assert_no_match(/x{150}/, displayed_code)
+ assert_match(/title="/, response.body)
+ end
+
+ test "code truncation honors trace_code_display_length cookie" do
+ trace = [
+ {
+ step: 1,
+ type: "ruby",
+ code: "x" * 500,
+ input_preview: ["in"],
+ output_preview: ["out"]
+ }
+ ]
+ stub_helper_with_trace(trace)
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always; trace_code_display_length=200" }
+ follow_redirect_with_trace_visibility
+
+ displayed_code = response.body[%r{
\s*
\s*]*>([^<]+) \s*}m, 1]
+ assert displayed_code.present?
+ assert_operator displayed_code.length, :<=, 200
+ assert_match(/title="/, response.body)
+ end
+
+ test "xpath step is classified as extraction" do
+ trace = [{ step: 1, type: "xpath", code: "//div", output_preview: [] }]
+ stub_helper_with_trace(trace)
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ follow_redirect_with_trace_visibility
+
+ assert_match(/extraction/, response.body)
+ end
+
+ test "ruby reject is classified as filter" do
+ trace = [
+ { step: 1, type: "ruby", code: "$array.map{}", output_preview: ["A"] },
+ { step: 2, type: "ruby", code: "$array.reject{}", output_preview: ["B"] }
+ ]
+ stub_helper_with_trace(trace)
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ follow_redirect_with_trace_visibility
+
+ assert_match(/filter \(Δ changed\)/, response.body)
+ end
+
+ test "tooltip contains full rendered code value" do
+ trace = [{ step: 1, type: "ruby", code: "alpha_beta", output_preview: [] }]
+ stub_helper_with_trace(trace)
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ follow_redirect_with_trace_visibility
+
+ assert_match(/title="/, response.body)
+ assert_match(/alpha_beta/, response.body)
+ end
+
+ test "error marker is visible in trace viewer" do
+ trace = [
+ {
+ step: 1,
+ type: "ruby",
+ code: "bad",
+ error_class: "NoMethodError",
+ error_message: "boom"
+ }
+ ]
+ stub_helper_with_trace(trace, errors: ["boom"])
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ follow_redirect_with_trace_visibility
+
+ assert_match(/⚠/, response.body)
+ assert_match(/NoMethodError/, response.body)
+ end
+
+ test "probe result is rendered in trace viewer" do
+ trace = [
+ {
+ step: 1,
+ type: "url",
+ output_preview: []
+ },
+ {
+ step: 2,
+ type: "xpath",
+ code: "//h1/text()",
+ output_preview: [],
+ probe: {
+ xpath: "//title",
+ output: ["Probe Title"]
+ }
+ }
+ ]
+ stub_helper_with_trace(trace)
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ trace_payload = session[:dsl_trace].with_indifferent_access
+ second = trace_payload[:steps].second.with_indifferent_access
+ assert_equal "//title", second.dig(:p, :x)
+ assert_match(/Probe Title/, second.dig(:p, :o).to_s)
+
+ follow_redirect_with_trace_visibility
+
+ assert_match(%r{Probe //title}, response.body)
+ assert_match(/Probe Title/, response.body)
+ end
+
+ test "delta shows added elements in array" do
+ trace = [
+ { step: 1, type: "ruby", output_preview: [] },
+ { step: 2, type: "ruby", output_preview: ["A"] }
+ ]
+ stub_helper_with_trace(trace)
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ follow_redirect_with_trace_visibility
+
+ assert_match(/Δ added/, response.body)
+ assert_no_match(/class="trace-delta"/, response.body)
+ end
+
+ test "delta shows removed elements in array" do
+ trace = [
+ { step: 1, type: "ruby", output_preview: %w[A B] },
+ { step: 2, type: "ruby", output_preview: ["A"] }
+ ]
+ stub_helper_with_trace(trace)
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ follow_redirect_with_trace_visibility
+
+ assert_match(/Δ changed/, response.body)
+ assert_match(/-B/, response.body)
+ end
+
+ test "delta remains visible even when values are truncated" do
+ long_url = "https://example.com/" + ("x" * 200)
+
+ trace = [
+ { step: 1, type: "ruby", output_preview: ["seed"] },
+ { step: 2, type: "ruby", output_preview: [long_url] }
+ ]
+ stub_helper_with_trace(trace)
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ follow_redirect_with_trace_visibility
+
+ assert_match(/Δ changed/, response.body)
+ assert_match(%r{\+https://example.com}, response.body)
+ end
+
+ test "no delta shown when state unchanged" do
+ trace = [
+ { step: 1, type: "ruby", output_preview: ["A"] },
+ { step: 2, type: "ruby", output_preview: ["A"] }
+ ]
+ stub_helper_with_trace(trace)
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ follow_redirect_with_trace_visibility
+
+ assert_match(/No change/, response.body)
+ assert_no_match(/class="trace-delta"/, response.body)
+ end
+
+ test "no change is rendered when consecutive outputs are identical" do
+ trace = [
+ { step: 1, type: "ruby", output_preview: ["A"] },
+ { step: 2, type: "ruby", output_preview: ["A"] }
+ ]
+ stub_helper_with_trace(trace)
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ follow_redirect_with_trace_visibility
+
+ assert_match(/No change/, response.body)
+ end
+
+ test "no result is rendered when output stays empty" do
+ trace = [
+ { step: 1, type: "ruby", output_preview: [] },
+ { step: 2, type: "ruby", output_preview: [] }
+ ]
+ stub_helper_with_trace(trace)
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ follow_redirect_with_trace_visibility
+
+ assert_match(/No result/, response.body)
+ end
+
+ test "exactly one semantic label is rendered per step" do
+ trace = [
+ { step: 1, type: "ruby", output_preview: [] },
+ { step: 2, type: "ruby", output_preview: ["A"] },
+ { step: 3, type: "ruby", output_preview: ["B"] }
+ ]
+ stub_helper_with_trace(trace)
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ follow_redirect_with_trace_visibility
+
+ assert_equal 3, response.body.scan(/class="trace-semantic"/).size
+ end
+
+ test "output is always shown even when empty array" do
+ trace = [
+ { step: 1, type: "ruby", output_preview: [] }
+ ]
+ stub_helper_with_trace(trace)
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ follow_redirect_with_trace_visibility
+
+ assert_match(/→\s*\[\]/, response.body)
+ end
+
+ test "input is not shown when output is empty array" do
+ trace = [
+ { step: 1, type: "ruby", input_preview: ["INPUT_ONLY_MARKER"], output_preview: [] }
+ ]
+ stub_helper_with_trace(trace)
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ follow_redirect_with_trace_visibility
+
+ assert_match(/→\s*\[\]/, response.body)
+ assert_no_match(/→\s*INPUT_ONLY_MARKER/, response.body)
+ end
+
+ test "compact trace urls are reconstructed correctly in view" do
+ trace = [
+ { step: 1, type: "ruby", url_before: "http://example.com/a", url_after: "http://example.com/b" },
+ { step: 2, type: "ruby", url_before: "http://example.com/a", url_after: "http://example.com/b" }
+ ]
+ stub_helper_with_trace(trace)
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ compact = session[:dsl_trace].with_indifferent_access
+ assert_equal "http://example.com/a", compact[:initial].with_indifferent_access[:url]
+ assert_equal ["http://example.com/b"], compact[:urls]
+
+ follow_redirect_with_trace_visibility
+ assert_match(%r{http://example.com/b}, response.body)
+ end
+
+ test "v2 step chain reconstruction uses previous output as next input" do
+ compact = {
+ version: 2,
+ initial: { state: "seed", url: "http://example.com/start" },
+ urls: ["http://example.com/next"],
+ steps: [
+ { s: 1, t: "ruby", o: "state-1", ua: 0 },
+ { s: 2, t: "ruby", o: "state-2" }
+ ]
+ }
+
+ expanded = StatementsController.new.expand_trace_for_view(compact)
+
+ assert_equal "seed", expanded.first[:input]
+ assert_equal "state-1", expanded.first[:output]
+ assert_equal "state-1", expanded.second[:input]
+ assert_equal "state-2", expanded.second[:output]
+ end
+
+ test "expand_trace_for_view supports v1 compact format for backward compatibility" do
+ compact_v1 = {
+ version: 1,
+ urls: ["http://example.com/a", "http://example.com/b"],
+ events: [{ s: 1, t: "ruby", i: "in", o: "out", ub: 0, ua: 1, d: 1.0, e: "boom" }]
+ }
+
+ expanded = StatementsController.new.expand_trace_for_view(compact_v1)
+ first = expanded.first.with_indifferent_access
+
+ assert_equal 1, first[:step]
+ assert_equal "ruby", first[:type]
+ assert_equal "in", first[:input]
+ assert_equal "out", first[:output]
+ assert_equal "http://example.com/a", first[:url_before]
+ assert_equal "http://example.com/b", first[:url_after]
+ assert_equal "boom", first[:error]
+ end
+
+ test "expand_trace_for_view reconstructs v2 probe payload" do
+ compact = {
+ version: 2,
+ initial: { state: nil, url: "http://example.com/start" },
+ urls: [],
+ steps: [
+ { s: 1, t: "url", o: "[]" },
+ { s: 2, t: "xpath", o: "[]", p: { st: "ok", x: "//title", o: ["Probe Title"] } }
+ ]
+ }
+
+ expanded = StatementsController.new.expand_trace_for_view(compact)
+ probe = expanded.second[:probe].with_indifferent_access
+
+ assert_equal true, probe[:ok]
+ assert_equal "ok", probe[:result][:status]
+ assert_equal "//title", probe[:result][:xpath]
+ assert_equal ["Probe Title"], probe[:result][:output]
+ end
+
+ test "trace storage does not trigger CookieOverflow" do
+ large_trace = build_large_realistic_trace(20)
+ stub_helper_with_trace(large_trace)
+
+ assert_nothing_raised do
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ end
+
+ trace = assert_session_trace_present_and_structured
+ assert trace[:steps].present?
+ assert_operator Marshal.dump(session.to_hash).bytesize, :<, 3000
+ end
+
+ test "overflow trace still preserves error visibility" do
+ trace = build_large_realistic_trace(20)
+ trace.last[:error_class] = "NoMethodError"
+ trace.last[:error_message] = "boom"
+
+ stub_helper_with_trace(trace, errors: ["boom"])
+
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ follow_redirect_with_trace_visibility
+
+ assert_match(/Statement Error:/, response.body)
+ end
+
+ test "large trace payload does not overflow cookies because trace is truncated for session" do
+ large_trace = [
+ {
+ step: 1,
+ type: "ruby",
+ code: "x" * 10_000,
+ input_preview: ["in"],
+ output_preview: ["out"],
+ error_class: nil,
+ error_message: nil,
+ url_before: "http://example.com",
+ url_after: "http://example.com",
+ duration_ms: 1.0
+ }
+ ]
+
+ helper_proxy = mock("helper_proxy")
+ helper_proxy.expects(:refresh_statement_helper).with(@statement).returns(data: nil, trace: large_trace, errors: [])
+ helper_proxy.expects(:instance_variable_get).never
+ StatementsController.any_instance.stubs(:statement_refresh_helper_proxy).returns(helper_proxy)
+
+ assert_nothing_raised do
+ patch refresh_statement_path(@statement), headers: { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ end
+
+ assert_redirected_to statement_url(@statement)
+ assert_nil flash[:dsl_trace]
+ trace = assert_session_trace_present_and_structured
+ first = trace[:steps].first.with_indifferent_access
+ assert first.key?(:s)
+ assert first.key?(:t)
+ assert first.key?(:o)
+ assert_operator JSON.generate(trace).bytesize, :<, 3500
+ assert_operator Marshal.dump(session.to_hash).bytesize, :<, 3000
+
+ follow_redirect_with_trace_visibility
+ assert_response :success
+ assert_not_nil session[:dsl_trace]
+ end
+
+ test "batch update with many abort payloads keeps flash compact and avoids cookie overflow" do
+ statements = Array.new(6, @statement)
+ helper_proxy = mock("helpers_proxy")
+ 6.times do
+ helper_proxy.expects(:refresh_statement_helper).returns(
+ data: ["abort_update", {
+ error: "Legacy PhantomJS renderer is unavailable",
+ error_type: "phantomjs_unavailable",
+ step: "url",
+ signals: {
+ blocking_issue_key: "phantomjs_unavailable",
+ renderer_fallback: "direct_url",
+ primary_issue_category: "renderer",
+ phantomjs_iframe_extraction: false
+ },
+ hints: ["legacy_phantomjs", "phantomjs_unavailable"]
+ }],
+ trace: nil,
+ errors: ["Scrape aborted (phantomjs_unavailable): issue=phantomjs_unavailable: Legacy PhantomJS renderer is unavailable"]
+ )
+ end
+ StatementsController.any_instance.stubs(:build_query).returns(statements)
+ StatementsController.any_instance.stubs(:helpers).returns(helper_proxy)
+
+ assert_nothing_raised do
+ post batch_update_statements_path, params: { commit: "Refresh all listed" }
+ end
+
+ assert_redirected_to statements_path
+ assert_match(/Refresh completed with 6 errors\./, flash[:notice].to_s)
+ assert_match(/phantomjs_unavailable/, flash[:notice].to_s)
+ assert_no_match(/renderer_fallback/, flash[:notice].to_s)
+ assert_no_match(/primary_issue_category/, flash[:notice].to_s)
+ assert_no_match(/phantomjs_iframe_extraction/, flash[:notice].to_s)
+ assert_operator Marshal.dump(session.to_hash).bytesize, :<, 3000
+ end
+
+ test "batch form preserves full statements filter and pagination context" do
+ get statements_url, params: {
+ rdf_uri: "uri1",
+ seedurl: "one",
+ prop: properties(:one).id.to_s,
+ source: sources(:one).id.to_s,
+ cache: "MyString",
+ status: "initial",
+ manual: "false",
+ selected: "true",
+ selected_individual: "false",
+ sort: "cache",
+ direction: "desc",
+ page: "2",
+ per_page: "10"
+ }
+
+ assert_response :success
+ assert_select 'details.statements-batch-actions form[action="/statements/batch_update"] input[type="hidden"][name="rdf_uri"][value="uri1"]', 1
+ assert_select 'details.statements-batch-actions form[action="/statements/batch_update"] input[type="hidden"][name="seedurl"][value="one"]', 1
+ assert_select "details.statements-batch-actions form[action=\"/statements/batch_update\"] input[type=\"hidden\"][name=\"prop\"][value=\"#{properties(:one).id}\"]", 1
+ assert_select "details.statements-batch-actions form[action=\"/statements/batch_update\"] input[type=\"hidden\"][name=\"source\"][value=\"#{sources(:one).id}\"]", 1
+ assert_select 'details.statements-batch-actions form[action="/statements/batch_update"] input[type="hidden"][name="cache"][value="MyString"]', 1
+ assert_select 'details.statements-batch-actions form[action="/statements/batch_update"] input[type="hidden"][name="status"][value="initial"]', 1
+ assert_select 'details.statements-batch-actions form[action="/statements/batch_update"] input[type="hidden"][name="manual"][value="false"]', 1
+ assert_select 'details.statements-batch-actions form[action="/statements/batch_update"] input[type="hidden"][name="selected"][value="true"]', 1
+ assert_select 'details.statements-batch-actions form[action="/statements/batch_update"] input[type="hidden"][name="selected_individual"][value="false"]', 1
+ assert_select 'details.statements-batch-actions form[action="/statements/batch_update"] input[type="hidden"][name="sort"][value="cache"]', 1
+ assert_select 'details.statements-batch-actions form[action="/statements/batch_update"] input[type="hidden"][name="direction"][value="desc"]', 1
+ assert_select 'details.statements-batch-actions form[action="/statements/batch_update"] input[type="hidden"][name="page"][value="2"]', 1
+ assert_select 'details.statements-batch-actions form[action="/statements/batch_update"] input[type="hidden"][name="per_page"][value="10"]', 1
+ end
+
+ test "batch update only applies to the current filtered listed scope and preserves redirect context" do
+ matching_one, matching_two, other_page, other_filter = create_batch_scope_statements
+ StatementsController.any_instance.stubs(:build_query).returns([matching_one, matching_two])
+
+ post batch_update_statements_path, params: {
+ seedurl: matching_one.webpage.website.seedurl,
+ source: matching_one.source_id.to_s,
+ status: "initial",
+ manual: "false",
+ selected: "true",
+ selected_individual: "false",
+ sort: "id",
+ direction: "asc",
+ page: "1",
+ per_page: "2",
+ update_data: "{ manual: true, cache: 'batch-scope-updated' }",
+ commit: "Update"
+ }
+
+ assert_redirected_to statements_path(
+ seedurl: matching_one.webpage.website.seedurl,
+ source: matching_one.source_id.to_s,
+ status: "initial",
+ manual: "false",
+ selected: "true",
+ selected_individual: "false",
+ sort: "id",
+ direction: "asc",
+ page: "1",
+ per_page: "2"
+ )
+ assert_equal true, matching_one.reload.manual
+ assert_equal true, matching_two.reload.manual
+ assert_equal "batch-scope-updated", matching_one.reload.cache
+ assert_equal "batch-scope-updated", matching_two.reload.cache
+ assert_equal false, other_page.reload.manual
+ assert_equal false, other_filter.reload.manual
+ end
+
+ test "refresh all listed only applies to the current filtered listed scope and preserves redirect context" do
+ matching_one, matching_two, = create_batch_scope_statements
+ StatementsController.any_instance.stubs(:build_query).returns([matching_one, matching_two])
+ helper_proxy = mock("batch_refresh_helpers")
+ helper_proxy.expects(:refresh_statement_helper).with(matching_one).returns(data: ["ok"], trace: nil, errors: [])
+ helper_proxy.expects(:refresh_statement_helper).with(matching_two).returns(data: ["ok"], trace: nil, errors: [])
+ StatementsController.any_instance.stubs(:helpers).returns(helper_proxy)
+
+ post batch_update_statements_path, params: {
+ seedurl: matching_one.webpage.website.seedurl,
+ source: matching_one.source_id.to_s,
+ status: "initial",
+ manual: "false",
+ selected: "true",
+ selected_individual: "false",
+ sort: "id",
+ direction: "asc",
+ page: "1",
+ per_page: "2",
+ commit: "Refresh all listed"
+ }
+
+ assert_redirected_to statements_path(
+ seedurl: matching_one.webpage.website.seedurl,
+ source: matching_one.source_id.to_s,
+ status: "initial",
+ manual: "false",
+ selected: "true",
+ selected_individual: "false",
+ sort: "id",
+ direction: "asc",
+ page: "1",
+ per_page: "2"
+ )
+ end
+
+ test "review all listed only applies to the current filtered listed scope and preserves redirect context" do
+ matching_one, matching_two, other_page, other_filter = create_batch_scope_statements
+ StatementsController.any_instance.stubs(:build_query).returns([matching_one, matching_two])
+ matching_one.update!(status: "updated", status_origin: "before")
+ matching_two.update!(status: "updated", status_origin: "before")
+ other_page.update!(status: "updated", status_origin: "before")
+ other_filter.update!(status: "updated", status_origin: "before")
+
+ post batch_update_statements_path, params: {
+ seedurl: matching_one.webpage.website.seedurl,
+ source: matching_one.source_id.to_s,
+ status: "updated",
+ manual: "false",
+ selected: "true",
+ selected_individual: "false",
+ sort: "id",
+ direction: "asc",
+ page: "1",
+ per_page: "2",
+ commit: "Review all listed"
+ }
+
+ assert_redirected_to statements_path(
+ seedurl: matching_one.webpage.website.seedurl,
+ source: matching_one.source_id.to_s,
+ status: "updated",
+ manual: "false",
+ selected: "true",
+ selected_individual: "false",
+ sort: "id",
+ direction: "asc",
+ page: "1",
+ per_page: "2"
+ )
+ assert_equal "ok", matching_one.reload.status
+ assert_equal "ok", matching_two.reload.status
+ assert_equal "condenser-admin-review-all", matching_one.reload.status_origin
+ assert_equal "condenser-admin-review-all", matching_two.reload.status_origin
+ assert_equal "updated", other_page.reload.status
+ assert_equal "updated", other_filter.reload.status
+ end
+
test "should destroy statement" do
assert_difference('Statement.count', -1) do
delete statement_url(@statement)
@@ -105,6 +1964,399 @@ class StatementsControllerTest < ActionDispatch::IntegrationTest
assert_redirected_to show_resources_path(rdf_uri: "uri1.json")
end
+ test "batch update does not write redirect listing title when distillator content failed" do
+ previous_mode = ENV["DISTILLATOR_FETCH_MODE"]
+ ENV["DISTILLATOR_FETCH_MODE"] = "internal"
+ website = Website.create!(
+ name: "culturelatuque-com",
+ seedurl: "culturelatuque-com",
+ graph_name: "https://example.org/culturelatuque-com",
+ default_language: "fr",
+ distillator_mode: "active"
+ )
+ rdfs_class = RdfsClass.create!(name: "OvationBatchUpdateClass")
+ property = Property.create!(
+ label: "Ovation Batch Title",
+ value_datatype: "MyString",
+ uri: "http://schema.org/name",
+ rdfs_class: rdfs_class
+ )
+ ovation_url = "https://www.ovation.ca/00001Q/fr/Event/?seriesId=series&venueId=venue"
+ webpage = Webpage.create!(
+ url: ovation_url,
+ language: "fr",
+ rdf_uri: ovation_url,
+ rdfs_class: rdfs_class,
+ website: website
+ )
+ source = Source.create!(
+ algorithm_value: "xpath=//title;ruby=$array.map{|e| e.gsub(/ \\|.*/,'')}",
+ selected: true,
+ selected_by: "test",
+ language: "fr",
+ render_js: true,
+ property: property,
+ website: website
+ )
+ statement = Statement.create!(
+ cache: "Existing Event Title",
+ status: "ok",
+ status_origin: "test",
+ cache_refreshed: 1.day.ago,
+ cache_changed: 1.day.ago,
+ source: source,
+ webpage: webpage,
+ selected_individual: false
+ )
+
+ Distillator::FetchService.expects(:fetch_result).returns(
+ {
+ status: :ok,
+ body: "
Recherche par titre ",
+ raw_body: "
Recherche par titre ",
+ headers: { content_type: "text/html" },
+ final_url: "https://www.ovation.ca/Search/Title/",
+ redirect_chain: [ovation_url, "https://www.ovation.ca/Search/Title/"],
+ wringer: {
+ policy_action: "abort_update",
+ retry: false,
+ cache: false,
+ signals: {
+ network_status: "ok",
+ content_type: "html",
+ primary_issue_key: "redirect_to_listing",
+ primary_issue_severity: "failed",
+ blocking_issue_key: "redirect_to_listing",
+ primary_issue_label: "Redirect to listing"
+ },
+ hints: ["redirect_to_listing"]
+ },
+ http_code: 200,
+ duration_ms: 0,
+ fetch_path: "native"
+ }
+ )
+
+ post "/statements/batch_update", params: {
+ seedurl: "culturelatuque-com",
+ rdf_uri: ovation_url,
+ commit: "Refresh all listed"
+ }
+
+ assert_redirected_to(/statements/)
+ follow_redirect!
+
+ cache = Distillator::FetchCache.find_by!(uri_key: CGI.escape(ovation_url))
+
+ assert_equal "Existing Event Title", statement.reload.cache
+ assert_not_equal "Recherche par titre", statement.reload.cache
+ assert_match "Scrape aborted", response.body
+ assert_match "redirect_to_listing", response.body
+ assert_equal "content_rejected", cache.health_status
+ assert_equal "redirect_to_listing", cache.primary_issue_key
+ assert_nil cache.html
+ ensure
+ ENV["DISTILLATOR_FETCH_MODE"] = previous_mode
+ end
+
+
+
+ private
+
+ def follow_redirect_with_trace_visibility(state = "always", view_mode = nil)
+ cookies[:trace_visibility] = state
+ if view_mode.present?
+ cookies[:trace_view_mode] = view_mode
+ else
+ cookies.delete(:trace_view_mode)
+ cookies.delete("trace_view_mode")
+ end
+ follow_redirect!
+ end
+ def assert_error_alert_if_present
+ return unless response.body.match?(/class=(['"])[^'"]*\balert\b[^'"]*\1/)
+
+ assert_select ".alert", /Statement Error:/
+ end
+
+ def stub_helper_with_trace(trace, errors: [])
+ helper_proxy = mock("helper_proxy")
+ helper_proxy.expects(:refresh_statement_helper).with(@statement).returns(
+ data: nil,
+ trace: trace,
+ errors: errors
+ )
+ StatementsController.any_instance.stubs(:statement_refresh_helper_proxy).returns(helper_proxy)
+ end
+
+ def build_large_realistic_trace(n)
+ (1..n).map do |i|
+ {
+ step: i,
+ type: "ruby",
+ code: "x" * 1000,
+ input_preview: ["y" * 1000],
+ output_preview: ["z" * 1000],
+ url_before: "http://example.com",
+ url_after: "http://example.com",
+ duration_ms: 1.0
+ }
+ end
+ end
+
+ def assert_session_trace_present_and_structured
+ trace = session[:dsl_trace]
+ assert trace.present?
+ assert trace.is_a?(Hash)
+
+ compact = trace.with_indifferent_access
+ assert_equal 2, compact[:version]
+ assert compact[:statement_id].present?
+ assert compact[:initial].is_a?(Hash)
+ assert compact[:urls].is_a?(Array)
+ assert compact[:steps].is_a?(Array)
+ assert compact[:steps].present?
+
+ first = compact[:steps].first.with_indifferent_access
+ assert first.key?(:s)
+ assert first.key?(:t)
+ compact
+ end
+
+ def assert_read_only_page_does_not_fetch
+ Distillator::FetchCacheStore.expects(:fetch).never
+ Distillator::FetchService.expects(:fetch).never
+ Distillator::NativeFetch.expects(:call).never
+ Distillator::FetchShadowComparator.expects(:call).never
+ end
+
+ def create_batch_scope_statements
+ suffix = Statement.maximum(:id).to_i + 1
+ website = Website.create!(
+ name: "batch scope website #{suffix}",
+ seedurl: "batch-scope-website-#{suffix}",
+ graph_name: "http://example.com/batch-scope-website-#{suffix}",
+ default_language: "en"
+ )
+ property = Property.create!(
+ label: "Batch scope property #{suffix}",
+ value_datatype: "MyString",
+ uri: "http://example.com/properties/batch-scope-#{suffix}",
+ rdfs_class: rdfs_classes(:one)
+ )
+ selected_source = Source.create!(
+ algorithm_value: "manual=Batch scope selected",
+ selected: true,
+ selected_by: "test",
+ language: "en",
+ render_js: false,
+ property: property,
+ website: website
+ )
+ unselected_source = Source.create!(
+ algorithm_value: "manual=Batch scope unselected",
+ selected: false,
+ selected_by: "test",
+ language: "en",
+ render_js: false,
+ property: property,
+ website: website
+ )
+
+ webpages = 4.times.map do |index|
+ Webpage.create!(
+ url: "http://example.com/batch-scope/#{suffix}/#{index}",
+ language: "en",
+ rdf_uri: "rdf:batch-scope:#{suffix}:#{index}",
+ rdfs_class: rdfs_classes(:one),
+ website: website
+ )
+ end
+
+ matching_one = Statement.create!(
+ cache: "batch-scope-cache-1",
+ status: "initial",
+ status_origin: "seed",
+ source: selected_source,
+ webpage: webpages[0],
+ manual: false,
+ selected_individual: false
+ )
+ matching_two = Statement.create!(
+ cache: "batch-scope-cache-2",
+ status: "initial",
+ status_origin: "seed",
+ source: selected_source,
+ webpage: webpages[1],
+ manual: false,
+ selected_individual: false
+ )
+ other_page = Statement.create!(
+ cache: "batch-scope-cache-3",
+ status: "initial",
+ status_origin: "seed",
+ source: selected_source,
+ webpage: webpages[2],
+ manual: false,
+ selected_individual: false
+ )
+ other_filter = Statement.create!(
+ cache: "batch-scope-cache-4",
+ status: "initial",
+ status_origin: "seed",
+ source: unselected_source,
+ webpage: webpages[3],
+ manual: false,
+ selected_individual: false
+ )
+
+ [matching_one, matching_two, other_page, other_filter]
+ end
+
+ def build_statement_compare_page_records
+ website = Website.create!(
+ name: "Statement compare website",
+ seedurl: "statement-compare-website",
+ graph_name: "http://example.com/statement-compare-website",
+ default_language: "en"
+ )
+ webpage = Webpage.create!(
+ url: "http://example.com/statement-compare-website/event",
+ language: "en",
+ rdf_uri: "rdf:statement-compare-website:event",
+ rdfs_class: rdfs_classes(:one),
+ website: website
+ )
+
+ [
+ [Property.create!(id: 1, label: "Essential title", value_datatype: "MyString", uri: "http://example.com/properties/essential-title", rdfs_class: rdfs_classes(:one)), "xpath=//title/text()"],
+ [Property.create!(id: 3, label: "Essential description", value_datatype: "MyString", uri: "http://example.com/properties/essential-description", rdfs_class: rdfs_classes(:one)), "xpath=//meta[@name='description']/@content"]
+ ].each do |property, algorithm|
+ source = Source.create!(
+ algorithm_value: algorithm,
+ selected: true,
+ selected_by: "test",
+ language: "en",
+ render_js: false,
+ property: property,
+ website: website
+ )
+ Statement.create!(
+ cache: "seeded cache",
+ status: "ok",
+ status_origin: "test",
+ cache_refreshed: 1.hour.ago,
+ cache_changed: 1.hour.ago,
+ source: source,
+ webpage: webpage
+ )
+ end
+
+ legacy_only_property = Property.create!(
+ id: 5,
+ label: "Essential legacy note",
+ value_datatype: "MyString",
+ uri: "http://example.com/properties/statement-compare-legacy-only",
+ rdfs_class: rdfs_classes(:one)
+ )
+ legacy_only_source = Source.create!(
+ algorithm_value: "xpath=//div[@class='legacy-only']/text()",
+ selected: true,
+ selected_by: "test",
+ language: "en",
+ render_js: false,
+ property: legacy_only_property,
+ website: website
+ )
+ Statement.create!(
+ cache: "seeded cache",
+ status: "ok",
+ status_origin: "test",
+ cache_refreshed: 1.hour.ago,
+ cache_changed: 1.hour.ago,
+ source: legacy_only_source,
+ webpage: webpage
+ )
+
+ error_source = Source.create!(
+ algorithm_value: "url=$url + '?detail'; xpath=//title/text()",
+ selected: true,
+ selected_by: "test",
+ language: "en",
+ render_js: false,
+ property: Property.create!(
+ id: 13,
+ label: "Essential fetched title",
+ value_datatype: "MyString",
+ uri: "http://example.com/properties/statement-compare-fetched-title",
+ rdfs_class: rdfs_classes(:one)
+ ),
+ website: website
+ )
+ Statement.create!(
+ cache: "seeded cache",
+ status: "ok",
+ status_origin: "test",
+ cache_refreshed: 1.hour.ago,
+ cache_changed: 1.hour.ago,
+ source: error_source,
+ webpage: webpage
+ )
+
+ non_essential_source = Source.create!(
+ algorithm_value: "xpath=//img/@src",
+ selected: true,
+ selected_by: "test",
+ language: "en",
+ render_js: false,
+ property: Property.create!(
+ id: 21,
+ label: "Non essential photo",
+ value_datatype: "MyString",
+ uri: "http://example.com/properties/non-essential-photo",
+ rdfs_class: rdfs_classes(:one)
+ ),
+ website: website
+ )
+ Statement.create!(
+ cache: "seeded cache",
+ status: "ok",
+ status_origin: "test",
+ cache_refreshed: 1.hour.ago,
+ cache_changed: 1.hour.ago,
+ source: non_essential_source,
+ webpage: webpage
+ )
+
+ [website, webpage]
+ end
+
+ def statement_compare_html_pair
+ legacy_html = <<~HTML
+
+
+
Shared Title
+
+
+
+
Legacy note
+
+
+ HTML
+ condenser_html = <<~HTML
+
+
+
Shared Title
+
+
+
+
+
+
+ HTML
+
+ [legacy_html, condenser_html]
+ end
end
diff --git a/test/controllers/webpages_controller_test.rb b/test/controllers/webpages_controller_test.rb
index b4a98902..5b887292 100644
--- a/test/controllers/webpages_controller_test.rb
+++ b/test/controllers/webpages_controller_test.rb
@@ -3,11 +3,245 @@
class WebpagesControllerTest < ActionDispatch::IntegrationTest
setup do
@webpage = webpages(:one)
+ Distillator::WringerEndpoint.stubs(:current).returns(
+ Distillator::WringerEndpoint::Result.new(
+ compatibility_base_url: "http://compat.example",
+ legacy_lookup_base_url: "http://wringer.example",
+ compatibility_source: "DISTILLATOR_COMPAT_BASE_URL",
+ state: :remote_configured,
+ status_label: "Current Wringer: Remote configured",
+ status_detail: "http://compat.example via DISTILLATOR_COMPAT_BASE_URL"
+ )
+ )
end
- test "should get index" do
+ def create_test_website_with_webpage(distillator_mode:, seedurl:, url:)
+ website = Website.create!(
+ name: "cache link #{seedurl}",
+ seedurl: seedurl,
+ graph_name: "http://example.com/#{seedurl}",
+ default_language: "en",
+ distillator_mode: distillator_mode
+ )
+
+ webpage = Webpage.create!(
+ url: url,
+ language: "en",
+ rdf_uri: "rdf:#{seedurl}",
+ rdfs_class: rdfs_classes(:one),
+ website: website,
+ archive_date: Time.zone.parse("2026-01-01 12:00:00")
+ )
+
+ [website, webpage]
+ end
+
+ def build_transition_inspection_website(seedurl: "transition-inspection-site")
+ website = Website.create!(
+ name: "Transition inspection #{seedurl}",
+ seedurl: seedurl,
+ graph_name: "http://example.com/#{seedurl}",
+ default_language: "en"
+ )
+
+ active_publishable = Webpage.create!(
+ url: "https://example.org/#{seedurl}/active-publishable",
+ language: "en",
+ rdf_uri: "rdf:#{seedurl}:active-publishable",
+ rdfs_class: rdfs_classes(:one),
+ website: website,
+ archive_date: 3.days.from_now
+ )
+ create_publishable_statements_for(active_publishable)
+
+ archived_publishable = Webpage.create!(
+ url: "https://example.org/#{seedurl}/archived-publishable",
+ language: "en",
+ rdf_uri: "rdf:#{seedurl}:archived-publishable",
+ rdfs_class: rdfs_classes(:one),
+ website: website,
+ archive_date: 2.days.ago
+ )
+ create_publishable_statements_for(archived_publishable)
+
+ active_unpublishable = Webpage.create!(
+ url: "https://example.org/#{seedurl}/active-unpublishable",
+ language: "en",
+ rdf_uri: "rdf:#{seedurl}:active-unpublishable",
+ rdfs_class: rdfs_classes(:one),
+ website: website,
+ archive_date: 5.days.from_now
+ )
+
+ place_page = Webpage.create!(
+ url: "https://example.org/#{seedurl}/place-page",
+ language: "en",
+ rdf_uri: "rdf:#{seedurl}:place-page",
+ rdfs_class: rdfs_classes(:place),
+ website: website,
+ archive_date: 5.days.from_now
+ )
+
+ [website, active_publishable, archived_publishable, active_unpublishable, place_page]
+ end
+
+ test "webpages json index preserves existing contract" do
+ get webpages_url(format: :json)
+
+ assert_response :success
+ payload = JSON.parse(@response.body)
+ assert payload.is_a?(Array)
+ assert_includes payload.first.keys, "url"
+ assert_includes payload.first.keys, "rdf_uri"
+ assert_includes payload.first.keys, "publishable"
+ end
+
+ test "webpages index renders harmonized table shell and filters" do
get webpages_url
+
+ assert_response :success
+ assert_harmonized_table_shell
+ assert_select ".harmonized-table-filters", 1
+ assert_harmonized_filter_form(action: "/webpages")
+ assert_harmonized_apply_filters_button
+ assert_select 'a', text: "Reset filters"
+ end
+
+ test "webpages index preserves active filters in sort links" do
+ get webpages_url, params: {
+ seedurl: websites(:one).seedurl,
+ scope: "all",
+ term: "example",
+ language: "en",
+ archive_state: "active",
+ url_kind: "public",
+ rdfs_class: "Event",
+ publishable: "true",
+ page: "2"
+ }
+
+ follow_redirect! if response.redirect?
+ assert_response :success
+ assert_sort_link_preserves_filters(
+ label: "Url",
+ sort_key: "url",
+ params: {
+ seedurl: websites(:one).seedurl,
+ scope: "all",
+ term: "example",
+ language: "en",
+ archive_state: "active",
+ url_kind: "public",
+ rdfs_class: "Event",
+ publishable: "true"
+ }
+ )
+ end
+
+ test "webpages index drops ignored publishable false and page params from canonical urls" do
+ website, = build_transition_inspection_website(seedurl: "canonical-webpages-site")
+
+ get webpages_url, params: {
+ seedurl: website.seedurl,
+ publishable: "false",
+ page: "2",
+ per_page: "1"
+ }
+
+ assert_response :redirect
+ assert_redirected_to webpages_path(seedurl: website.seedurl)
+ end
+
+ test "webpages index defaults to active publishable event webpages for selected website" do
+ website, active_publishable, archived_publishable, active_unpublishable, place_page = build_transition_inspection_website(seedurl: "default-scope-site")
+
+ get webpages_url, params: { seedurl: website.seedurl }
+
assert_response :success
+ assert_includes @response.body, active_publishable.url
+ assert_not_includes @response.body, archived_publishable.url
+ assert_not_includes @response.body, active_unpublishable.url
+ assert_not_includes @response.body, place_page.url
+ assert_includes @response.body, "Showing 1 publishable event page for this website."
+ assert_match(%r{href="/webpages\?(?:scope=all&seedurl=#{website.seedurl}|seedurl=#{website.seedurl}&scope=all)">Show all webpages}, @response.body)
+ assert_select "a[href='#{webpage_path(active_publishable, return_to: webpages_path(seedurl: website.seedurl))}']", text: "Show"
+ end
+
+ test "webpages index scope all shows all webpages for selected website" do
+ website, active_publishable, archived_publishable, active_unpublishable, place_page = build_transition_inspection_website(seedurl: "all-scope-site")
+
+ get webpages_url, params: { seedurl: website.seedurl, scope: "all" }
+
+ assert_response :success
+ assert_includes @response.body, active_publishable.url
+ assert_includes @response.body, archived_publishable.url
+ assert_includes @response.body, active_unpublishable.url
+ assert_includes @response.body, place_page.url
+ assert_includes @response.body, "Showing 4 matching webpages."
+ assert_select "input[name='scope'][value='all']", 1
+ assert_match(%r{href="/webpages\?seedurl=#{website.seedurl}">Show publishable event pages}, @response.body)
+ end
+
+ test "webpages index ignores page and per page for rendering and pagination controls" do
+ website, active_publishable, = build_transition_inspection_website(seedurl: "no-pagination-site")
+ extra_pages = 30.times.map do |index|
+ webpage = Webpage.create!(
+ url: "https://example.org/no-pagination-site/more-#{index}",
+ language: "en",
+ rdf_uri: "rdf:no-pagination-site:#{index}",
+ rdfs_class: rdfs_classes(:one),
+ website: website,
+ archive_date: 5.days.from_now
+ )
+ create_publishable_statements_for(webpage)
+ webpage
+ end
+
+ get webpages_url, params: { seedurl: website.seedurl, page: "2", per_page: "1" }
+ assert_response :redirect
+ follow_redirect!
+
+ assert_response :success
+ assert_includes @response.body, active_publishable.url
+ extra_pages.each do |webpage|
+ assert_includes @response.body, webpage.url
+ end
+ assert_select "input[name='per_page']", 0
+ assert_select "a", text: "Previous page", count: 0
+ assert_select "a", text: "Next page", count: 0
+ assert_no_match(/Showing page \d+ of \d+\./, @response.body)
+ end
+
+ test "webpages index renders empty state" do
+ get webpages_url, params: { term: "no-such-webpage-filter" }
+
+ follow_redirect! if response.redirect?
+ assert_response :success
+ assert_select ".harmonized-table-empty-state", 1
+ end
+
+ test "webpages index shows website scoped summary and navigation" do
+ website = Website.create!(
+ name: "Scoped summary website",
+ seedurl: "scoped-summary-website",
+ graph_name: "https://example.org/scoped-summary-website",
+ default_language: "en"
+ )
+
+ publishable_page = Webpage.create!(url: "https://example.org/events/scoped", language: "en", rdf_uri: "rdf:scoped:event", rdfs_class: rdfs_classes(:one), website: website)
+ create_publishable_statements_for(publishable_page)
+ Webpage.create!(url: "footlight:scoped:person", language: "en", rdf_uri: "rdf:scoped:person", rdfs_class: rdfs_classes(:person), website: website)
+
+ get webpages_url, params: { seedurl: website.seedurl, scope: "all" }
+
+ assert_response :success
+ assert_includes @response.body, "Showing 2 matching webpages."
+ assert_match(%r{href="/webpages\?(?:scope=all&seedurl=#{website.seedurl}|seedurl=#{website.seedurl}&scope=all)"}, @response.body)
+ assert_match(%r{href="/webpages\?seedurl=#{website.seedurl}">Show publishable event pages}, @response.body)
+ assert_select "a[href='#{website_path(website)}']", text: website.name
+ assert_select "a[href='/webpages?seedurl=#{website.seedurl}']", text: "webpages"
+ assert_select "a[href='/sources?seedurl=#{website.seedurl}']", text: "sources"
+ assert_select "a[href='/statements?seedurl=#{website.seedurl}']", text: "statements"
end
test "should get new" do
@@ -17,15 +251,105 @@ class WebpagesControllerTest < ActionDispatch::IntegrationTest
test "should create webpage" do
assert_difference('Webpage.count') do
- post webpages_url, params: { webpage: { language: @webpage.language, rdf_uri: @webpage.rdf_uri, rdfs_class_id: @webpage.rdfs_class_id, url: @webpage.url + "newwebpageurl", website_id: @webpage.website_id } }
+ post webpages_url, params: {
+ return_to: "/webpages?seedurl=#{@webpage.website.seedurl}&scope=all",
+ webpage: { language: @webpage.language, rdf_uri: @webpage.rdf_uri, rdfs_class_id: @webpage.rdfs_class_id, url: @webpage.url + "newwebpageurl", website_id: @webpage.website_id }
+ }
end
- assert_redirected_to webpage_url(Webpage.last)
+ assert_redirected_to "/webpages?seedurl=#{@webpage.website.seedurl}&scope=all"
end
test "should show webpage" do
+ assert_read_only_page_does_not_fetch
get webpage_url(@webpage)
assert_response :success
+ assert_select 'details[data-operator-context-card]', 0
+ assert_includes @response.body, "Production transition"
+ assert_includes @response.body, "Data actions"
+ assert_includes @response.body, "JSON-LD"
+ assert_includes @response.body, "Validation"
+ assert_includes @response.body, "Page actions"
+ end
+
+ test "webpage show preserves safe return to links" do
+ return_to = "/webpages?seedurl=#{@webpage.website.seedurl}&scope=all"
+
+ get webpage_url(@webpage, return_to: return_to)
+
+ assert_response :success
+ assert_select "a[href='#{edit_webpage_path(@webpage, return_to: return_to)}']", text: "Edit"
+ assert_match(%r{href="/webpages\?(?:seedurl=#{@webpage.website.seedurl}&scope=all|scope=all&seedurl=#{@webpage.website.seedurl})"}, @response.body)
+ end
+
+ test "webpage show uses one production transition section and avoids retired wording" do
+ assert_read_only_page_does_not_fetch
+ @webpage.website.update!(distillator_mode: "shadow")
+
+ get webpage_url(@webpage)
+
+ assert_response :success
+ assert_includes @response.body, "Production transition"
+ assert_includes @response.body, Distillator::RolloutCopy.label(:shadow)
+ assert_includes @response.body, Distillator::RolloutCopy.description(:shadow)
+ assert_not_includes @response.body, "Distillator rollout"
+ assert_not_includes @response.body, "internal"
+ assert_not_includes @response.body, "new cache"
+ assert_not_includes @response.body, "phase I"
+ assert_not_includes @response.body, "preview only"
+ end
+
+ test "webpage show does not fetch" do
+ assert_read_only_page_does_not_fetch
+
+ get webpage_url(@webpage)
+
+ assert_response :success
+ end
+
+ test "webpage pages render representative cache links without fetching" do
+ assert_read_only_page_does_not_fetch
+ website, webpage = create_test_website_with_webpage(
+ distillator_mode: "active",
+ seedurl: "active-cache-index",
+ url: "http://example.com/active-cache-index"
+ )
+
+ get webpages_url, params: { seedurl: website.seedurl, scope: "all" }
+ assert_response :success
+ assert_includes @response.body, "
Active Cache "
+ assert_not_includes @response.body, "
Condenser Cache "
+ assert_includes @response.body, "Open active cache"
+ assert_not_includes @response.body, "Open Condenser cache"
+ assert_includes @response.body, "Active"
+ assert_includes @response.body, "Production: Condenser"
+ assert_includes @response.body, "/condenser/cache?term=#{CGI.escape(webpage.url)}"
+
+ get webpage_url(webpage)
+ assert_response :success
+ assert_select 'details[data-operator-context-card]', 0
+ assert_includes @response.body, "Active"
+ assert_includes @response.body, "Production backend: Condenser"
+ assert_includes @response.body, "Inspect legacy Wringer"
+ assert_includes @response.body, "Diagnose refresh"
+ end
+
+ test "webpage show uses diagnostic next step for invalid cache urls" do
+ assert_read_only_page_does_not_fetch
+ website, webpage = create_test_website_with_webpage(
+ distillator_mode: "legacy",
+ seedurl: "footlight-invalid-cache",
+ url: "footlight:test-id"
+ )
+
+ get webpage_url(webpage)
+
+ assert_response :success
+ assert_includes @response.body, "Invalid cache URL"
+ assert_operator @response.body.scan("Invalid cache URL").length, :<=, 1
+ assert_includes @response.body, "Use Diagnose refresh to inspect why this URL is not cache-inspectable."
+ assert_not_includes @response.body, "Inspect Condenser cache before promotion"
+ assert_includes @response.body, "Diagnose refresh"
end
test "should get edit" do
@@ -34,15 +358,60 @@ class WebpagesControllerTest < ActionDispatch::IntegrationTest
end
test "should update webpage" do
- patch webpage_url(@webpage), params: { webpage: { language: @webpage.language, rdf_uri: @webpage.rdf_uri, rdfs_class_id: @webpage.rdfs_class_id, url: @webpage.url, website_id: @webpage.website_id } }
- assert_redirected_to webpage_url(@webpage)
+ patch webpage_url(@webpage), params: {
+ return_to: "/webpages?seedurl=#{@webpage.website.seedurl}",
+ webpage: { language: @webpage.language, rdf_uri: @webpage.rdf_uri, rdfs_class_id: @webpage.rdfs_class_id, url: @webpage.url, website_id: @webpage.website_id }
+ }
+ assert_redirected_to "/webpages?seedurl=#{@webpage.website.seedurl}"
end
test "should destroy webpage" do
assert_difference('Webpage.count', -1) do
- delete webpage_url(@webpage)
+ delete webpage_url(@webpage), params: { return_to: "/webpages?seedurl=#{@webpage.website.seedurl}&scope=all" }
end
- assert_redirected_to webpages_url
+ assert_redirected_to "/webpages?seedurl=#{@webpage.website.seedurl}&scope=all"
+ end
+
+ test "unsafe return to falls back to a safe webpages path" do
+ patch webpage_url(@webpage), params: {
+ return_to: "https://evil.example/webpages",
+ webpage: { language: @webpage.language, rdf_uri: @webpage.rdf_uri, rdfs_class_id: @webpage.rdfs_class_id, url: @webpage.url, website_id: @webpage.website_id }
+ }
+
+ assert_redirected_to webpage_url(@webpage)
+ end
+
+ private
+
+ def create_publishable_statements_for(webpage)
+ [
+ [properties(:four), "Publishable title"],
+ [properties(:location), '[["Salle","uri:place"]]'],
+ [properties(:six), '["2026-06-01T20:00:00-04:00"]']
+ ].each do |property, cache|
+ source = Source.create!(
+ website: webpage.website,
+ property: property,
+ language: "en",
+ selected: true,
+ algorithm_value: "controller-test"
+ )
+
+ Statement.create!(
+ webpage: webpage,
+ source: source,
+ cache: cache,
+ status: "ok"
+ )
+ Statement.where(webpage: webpage, source: source).update_all(status: "ok")
+ end
+ end
+
+ def assert_read_only_page_does_not_fetch
+ Distillator::FetchCacheStore.expects(:fetch).never
+ Distillator::FetchService.expects(:fetch).never
+ Distillator::NativeFetch.expects(:call).never
+ Distillator::FetchShadowComparator.expects(:call).never
end
end
diff --git a/test/controllers/websites_controller_test.rb b/test/controllers/websites_controller_test.rb
index b193c29e..294e3939 100644
--- a/test/controllers/websites_controller_test.rb
+++ b/test/controllers/websites_controller_test.rb
@@ -3,39 +3,661 @@
class WebsitesControllerTest < ActionDispatch::IntegrationTest
setup do
@website = websites(:one)
+ @old_fetch_mode = ENV["DISTILLATOR_FETCH_MODE"]
+ @old_override_flag = ENV["DISTILLATOR_ALLOW_ACTIVE_OVERRIDE"]
+ @old_heroku_app_name = ENV["HEROKU_APP_NAME"]
+ Distillator::WringerEndpoint.stubs(:current).returns(
+ Distillator::WringerEndpoint::Result.new(
+ compatibility_base_url: "http://compat.example",
+ legacy_lookup_base_url: "http://wringer.example",
+ compatibility_source: "DISTILLATOR_COMPAT_BASE_URL",
+ state: :remote_configured,
+ status_label: "Current Wringer: Remote configured",
+ status_detail: "http://compat.example via DISTILLATOR_COMPAT_BASE_URL"
+ )
+ )
+ end
+
+ teardown do
+ ENV["DISTILLATOR_FETCH_MODE"] = @old_fetch_mode
+ ENV["DISTILLATOR_ALLOW_ACTIVE_OVERRIDE"] = @old_override_flag
+ ENV["HEROKU_APP_NAME"] = @old_heroku_app_name
end
test "should get index" do
+ assert_read_only_page_does_not_fetch
+ @website.update!(distillator_mode: "legacy")
+
get websites_url
+
assert_response :success
+ assert_harmonized_table_shell
+ assert_harmonized_filter_form(action: "/websites")
+ assert_harmonized_apply_filters_button
+ assert_harmonized_reset_filters_link(path: "/websites")
+ assert_includes @response.body, "Legacy:"
+ assert_includes @response.body, "Shadow:"
+ assert_includes @response.body, "Active:"
+ assert_includes @response.body, "Unknown:"
+ assert_includes @response.body, "La Vitrine pipeline"
+ assert_includes @response.body, "Website rollout filters"
+ assert_select 'details[data-operator-context-card]', 0
+ assert_no_cohort_source_requests
+ end
+
+ test "websites index renders typed webpage summary cell with scoped links" do
+ website = Website.create!(
+ name: "Typed dashboard website",
+ seedurl: "typed-dashboard-website",
+ graph_name: "https://example.org/typed-dashboard-website",
+ default_language: "en"
+ )
+
+ resource_list_class = RdfsClass.create!(name: "ResourceList")
+ web_page_class = RdfsClass.create!(name: "WebPage")
+ other_class = RdfsClass.create!(name: "Thingish")
+
+ publishable_page = Webpage.create!(url: "https://example.org/events/dashboard", language: "en", rdf_uri: "rdf:dashboard:event", rdfs_class: rdfs_classes(:one), website: website)
+ create_publishable_statements_for(publishable_page)
+ Webpage.create!(url: "https://example.org/events/dashboard-blocked", language: "en", rdf_uri: "rdf:dashboard:event:blocked", rdfs_class: rdfs_classes(:one), website: website)
+ Webpage.create!(url: "footlight:dashboard:person", language: "en", rdf_uri: "rdf:dashboard:person", rdfs_class: rdfs_classes(:person), website: website)
+ Webpage.create!(url: "footlight:dashboard:place", language: "en", rdf_uri: "rdf:dashboard:place", rdfs_class: rdfs_classes(:place), website: website)
+ Webpage.create!(url: "https://example.org/resources/dashboard", language: "en", rdf_uri: "rdf:dashboard:resource-list", rdfs_class: resource_list_class, website: website)
+ Webpage.create!(url: "https://example.org/pages/dashboard", language: "en", rdf_uri: "rdf:dashboard:webpage", rdfs_class: web_page_class, website: website)
+ Webpage.create!(url: "footlight:dashboard:other", language: "en", rdf_uri: "rdf:dashboard:other", rdfs_class: other_class, website: website)
+
+ get websites_url, params: { q: "Typed dashboard website" }
+
+ assert_response :success
+ assert_includes @response.body, "7 total"
+ assert_includes @response.body, "4 public"
+ assert_includes @response.body, "3 internal"
+ assert_includes @response.body, "E2"
+ assert_includes @response.body, "Pe1"
+ assert_includes @response.body, "Pl1"
+ assert_includes @response.body, "R1"
+ assert_includes @response.body, "W1"
+ assert_includes @response.body, "O1"
+ assert_includes @response.body, "1 publishable"
+ assert_includes @response.body, "6 not publishable"
+ assert_match %r{/webpages\?(seedurl=#{website.seedurl}&url_kind=public|url_kind=public&seedurl=#{website.seedurl})}, @response.body
+ assert_match %r{/webpages\?(seedurl=#{website.seedurl}&rdfs_class=Event|rdfs_class=Event&seedurl=#{website.seedurl})}, @response.body
+ assert_match %r{/webpages\?(seedurl=#{website.seedurl}&publishable=true|publishable=true&seedurl=#{website.seedurl})}, @response.body
end
test "should get new" do
get new_website_url
assert_response :success
+ assert_includes @response.body, 'name="website[distillator_mode]"'
+ assert_includes @response.body, ">Legacy<"
+ assert_includes @response.body, ">Shadow<"
+ assert_includes @response.body, ">Active<"
+ assert_includes @response.body, "Wringer serves production."
+ assert_includes @response.body, "Wringer serves production while Condenser is checked in the background."
+ assert_includes @response.body, "Condenser serves production while Wringer stays available for diagnostics."
+ assert_not_includes @response.body, "new cache"
+ assert_not_includes @response.body, ">internal<"
end
test "should create website" do
assert_difference('Website.count') do
- post websites_url, params: { website: { name: @website.name, seedurl: @website.seedurl } }
+ post websites_url, params: { website: { name: @website.name, seedurl: @website.seedurl, distillator_mode: "shadow" } }
end
assert_redirected_to website_url(Website.last)
+ assert_equal "shadow", Website.last.distillator_mode
end
test "should show website" do
+ assert_read_only_page_does_not_fetch
+ stub_remote_wringer_endpoint
+ @website.update!(distillator_mode: "shadow")
+ Distillator::TransitionCheckRunner.expects(:call).never
+ Distillator::RefreshRunner.expects(:call).never
+ ExportArtsdataService.expects(:call).never
+ ExportArtsdataService.expects(:production_equivalent).never
get website_url(@website)
assert_response :success
+ assert_select 'details[data-operator-context-card]', 0
+ assert_includes @response.body, "Transition verdict"
+ assert_includes @response.body, "Not checked: statements check is missing."
+ assert_includes @response.body, "Statements missing | Export missing | Fetch missing"
+ assert_includes @response.body, "Shadow"
+ assert_includes @response.body, "Latest transition report"
+ assert_includes @response.body, "Run transition batch check"
+ assert_includes @response.body, "Transition checks are run by batch job. This page displays the latest result."
+ assert_includes @response.body, "Open cache diagnostics"
+ assert_includes @response.body, "Inspect publishable event pages"
+ assert_includes @response.body, "name=\"website_id\" value=\"#{@website.id}\""
+ assert_includes @response.body, CGI.escapeHTML(website_path(@website, anchor: "website-batch-jobs-transition"))
+ assert_includes @response.body, distillator_shadow_report_site_path(@website, anchor: "transition-report-summary")
+ assert_includes @response.body, "Operations"
+ assert_includes @response.body, "Drill-down links"
+ assert_equal 1, @response.body.scan("Run transition batch check").size
+ assert_equal 1, @response.body.scan("Transition checks are run by batch job. This page displays the latest result.").size
+ assert_equal 1, @response.body.scan("Latest transition report").size
+ assert_not_includes @response.body, "Queue transition check"
+ assert_includes @response.body, "Danger zone"
+ assert_includes @response.body, "Edit"
+ assert_not_includes @response.body, "Back"
+ assert_not_includes @response.body, "/distillator/cache/preview?uri=#{CGI.escape(@website.seedurl)}"
+ assert_equal "shadow", @website.reload.distillator_mode
+ assert_no_cohort_source_requests
+ end
+
+ test "websites index shows la vitrine badge for cohort sites and not for non cohort sites" do
+ cohort = Website.create!(
+ name: "Hector Charland",
+ seedurl: "hector-charland-com",
+ graph_name: "http://example.com/tout-culture",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+ non_cohort = Website.create!(
+ name: "Outside Feed",
+ seedurl: "outside-feed",
+ graph_name: "http://example.com/outside-feed",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+
+ get websites_url, params: { q: "Feed" }
+
+ assert_response :success
+ assert_includes @response.body, non_cohort.name
+ assert_not_includes @response.body, cohort.name
+
+ get websites_url, params: { q: "Hector" }
+
+ assert_response :success
+ assert_includes @response.body, cohort.name
+ assert_includes @response.body, "La Vitrine pipeline"
+ assert_no_cohort_source_requests
+ end
+
+ test "websites index filters by la vitrine cohort" do
+ cohort = Website.create!(
+ name: "Hector Charland",
+ seedurl: "hector-charland-com",
+ graph_name: "http://example.com/tout-culture",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+ non_cohort = Website.create!(
+ name: "Outside Feed",
+ seedurl: "outside-feed",
+ graph_name: "http://example.com/outside-feed",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+
+ get websites_url, params: { cohort: "lavitrine_pipeline" }
+
+ assert_response :success
+ assert_includes @response.body, cohort.name
+ assert_not_includes @response.body, non_cohort.name
+ assert_includes @response.body, 'name="cohort"'
+ assert_no_cohort_source_requests
+ end
+
+ test "website show shows la vitrine badge without external requests" do
+ website = Website.create!(
+ name: "Hector Charland",
+ seedurl: "hector-charland-com",
+ graph_name: "http://example.com/tout-culture",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+
+ get website_url(website)
+
+ assert_response :success
+ assert_includes @response.body, "La Vitrine pipeline"
+ assert_no_cohort_source_requests
+ end
+
+ test "website show renders active rollout badge without exposing internal wording" do
+ assert_read_only_page_does_not_fetch
+ @website.update!(distillator_mode: "active")
+
+ get website_url(@website)
+
+ assert_response :success
+ assert_includes @response.body, "Active"
+ assert_includes @response.body, "Condenser"
+ assert_includes @response.body, "Rollback to Legacy Wringer"
+ assert_not_includes @response.body, "internal"
+ end
+
+ test "website show does not render stale rollout warning copy" do
+ assert_read_only_page_does_not_fetch
+ @website.update!(distillator_mode: "legacy")
+
+ get website_url(@website)
+
+ assert_response :success
+ assert_includes @response.body, "Move to shadow"
+ refute_includes @response.body, "Legacy mode keeps Wringer as the active fetch path."
+ end
+
+ test "website show for legacy shows simple transition controls when override is allowed" do
+ assert_read_only_page_does_not_fetch
+ @website.update!(distillator_mode: "legacy")
+
+ get website_url(@website)
+
+ assert_response :success
+ assert_includes @response.body, "Move to shadow"
+ assert_includes @response.body, "Run transition batch check"
+ assert_includes @response.body, "Activate anyway"
+ assert_includes @response.body, "Use after manual inspection or on staging. Records current blockers and reason."
+ end
+
+ test "website show for shadow shows readiness summary and transition check" do
+ assert_read_only_page_does_not_fetch
+ @website.update!(distillator_mode: "shadow")
+
+ get website_url(@website)
+
+ assert_response :success
+ assert_includes @response.body, "Run transition batch check"
+ assert_includes @response.body, "Latest transition report"
+ assert_includes @response.body, "Drill-down links"
+ end
+
+ test "website show displays latest transition evidence timestamp when recorded" do
+ website = ready_shadow_website(seedurl: "website-show-latest-evidence")
+
+ get website_url(website)
+
+ assert_response :success
+ assert_equal 1, @response.body.scan("Latest transition evidence:").size
+ refute_includes @response.body, "Batch check requested. Waiting for new transition evidence."
+ end
+
+ test "website show displays not recorded when no transition evidence exists" do
+ website = Website.create!(
+ name: "No evidence website",
+ seedurl: "no-evidence-website",
+ graph_name: "https://example.org/no-evidence-website",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+
+ get website_url(website)
+
+ assert_response :success
+ assert_includes @response.body, "Latest transition evidence: Not recorded yet."
+ end
+
+ test "website show displays pending batch check message until newer evidence exists" do
+ website = ready_shadow_website(seedurl: "website-show-pending-evidence")
+ website.transition_evidences.update_all(checked_at: 10.minutes.ago)
+ website.update!(transition_check_requested_at: 5.minutes.ago)
+
+ get website_url(website)
+
+ assert_response :success
+ assert_includes @response.body, "Batch check requested. Waiting for new transition evidence."
+ refute_includes @response.body, "Latest transition evidence:"
+ end
+
+ test "website show for shadow ready site shows promote to active action" do
+ assert_read_only_page_does_not_fetch
+ website = ready_shadow_website(seedurl: "shadow-ready-show")
+
+ get website_url(website)
+
+ assert_response :success
+ assert_includes @response.body, "Promote to active"
+ assert_includes @response.body, "Ready: all transition checks passed."
+ assert_includes @response.body, "Statements passed | Export passed | Fetch passed"
+ assert_not_includes @response.body, "Cannot promote yet"
+ end
+
+ test "website show for shadow blocked site shows blockers and transition check" do
+ assert_read_only_page_does_not_fetch
+ website = blocked_shadow_website(seedurl: "shadow-blocked-show")
+
+ get website_url(website)
+
+ assert_response :success
+ assert_includes @response.body, "Cannot promote yet"
+ assert_includes @response.body, "Cannot activate yet: statements check failed."
+ assert_includes @response.body, "Run transition batch check"
+ end
+
+ test "website show for review-needed shadow site shows activate after review instead of promote or activate anyway" do
+ assert_read_only_page_does_not_fetch
+ website = Website.create!(
+ name: "Review activation site",
+ seedurl: "review-activation-site",
+ graph_name: "https://example.org/review-activation-site",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+ url = "https://example.org/review-activation-site/event"
+ website.webpages.create!(url: url, language: "en", rdf_uri: "rdf:review-activation-site", rdfs_class: rdfs_classes(:one))
+ Distillator::FetchCache.create!(
+ uri_key: CGI.escape(url),
+ normalized_url: url,
+ html: "ok",
+ body: "ok",
+ scrape_date: 1.hour.ago,
+ successful_refresh: 1.hour.ago,
+ headers: {},
+ signals: { "transport_success" => true, "content_success" => true },
+ final_url: url
+ )
+ website.transition_evidences.create!(url: url, check_kind: "statement_delta", status: "checked", statement_count_delta_acceptable: true, checked_at: 1.hour.ago)
+ website.transition_evidences.create!(url: url, check_kind: "export_diff", status: "checked", export_diff_checked: true, checked_at: 1.hour.ago)
+ website.transition_evidences.create!(
+ url: url,
+ check_kind: "fetch_parity",
+ status: "checked",
+ checked_at: 1.hour.ago,
+ details: { reason: "review_needed_difference", comparison_policy: "operator", compare_summary: { review_needed_diffs: %w[html_sha256] } }
+ )
+
+ get website_url(website)
+
+ assert_response :success
+ assert_includes @response.body, "Activate after review"
+ assert_includes @response.body, "Review before activating"
+ assert_not_includes @response.body, "Promote to active"
+ assert_not_includes @response.body, "Activate anyway"
+ end
+
+ test "website show hides activate anyway when override is not allowed" do
+ assert_read_only_page_does_not_fetch
+ @website.update!(distillator_mode: "shadow")
+ Distillator::TransitionRuntime.stubs(:allow_active_override?).returns(false)
+
+ get website_url(@website)
+
+ assert_response :success
+ assert_not_includes @response.body, "Activate anyway"
+ end
+
+ test "website show keeps rollout copy compact and destructive actions collapsed" do
+ assert_read_only_page_does_not_fetch
+ @website.update!(distillator_mode: "legacy")
+
+ get website_url(@website)
+
+ assert_response :success
+ assert_operator @response.body.scan("Legacy").length, :>=, 1
+ assert_operator @response.body.scan("Wringer remains the production fetch path").length, :<=, 1
+ assert_includes @response.body, "Latest transition report"
+ assert_includes @response.body, "Refresh upcoming events"
+ assert_includes @response.body, "Destroy website"
+ assert_not_includes @response.body, "Back"
+ assert_select "details.website-danger-zone[open]", 0
+ assert_select "details.website-danger-zone summary", text: "Danger zone"
+ assert_select "details.website-danger-zone form", 4
end
test "should get edit" do
get edit_website_url(@website)
assert_response :success
+ assert_includes @response.body, 'name="website[distillator_mode]"'
+ assert_includes @response.body, ">Legacy<"
+ assert_includes @response.body, ">Shadow<"
+ assert_includes @response.body, ">Active<"
+ assert_includes @response.body, "Use the website Transition card for normal promotion, override, and rollback."
+ assert_not_includes @response.body, "Show"
+ assert_not_includes @response.body, "Back"
+ assert_not_includes @response.body, "new cache"
+ assert_not_includes @response.body, ">internal<"
end
test "should update website" do
- patch website_url(@website), params: { website: { name: @website.name, seedurl: @website.seedurl } }
- assert_redirected_to website_url(@website)
+ patch website_url(@website), params: { website: { name: @website.name, seedurl: @website.seedurl, distillator_mode: "active" } }
+ assert_response :success
+ assert_equal "legacy", @website.reload.distillator_mode
+ end
+
+ test "activate anyway requires a reason" do
+ website = Website.create!(
+ name: "Override reason required",
+ seedurl: "override-reason-required",
+ graph_name: "https://example.org/override-reason-required",
+ default_language: "en",
+ distillator_mode: "legacy"
+ )
+
+ post activate_anyway_website_path(website), params: { reason: "" }
+
+ assert_redirected_to website_url(website)
+ assert_equal "legacy", website.reload.distillator_mode
+ follow_redirect!
+ assert_includes @response.body, "Reason is required for Activate anyway"
+ end
+
+ test "activate anyway succeeds in test runtime and records override details" do
+ website = Website.create!(
+ name: "Override success",
+ seedurl: "override-success",
+ graph_name: "https://example.org/override-success",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+ website.transition_evidences.create!(
+ url: "https://example.org/override-success",
+ check_kind: "fetch_parity",
+ status: "pending",
+ checked_at: 1.hour.ago
+ )
+
+ post activate_anyway_website_path(website), params: { reason: "Manual inspection complete" }
+
+ assert_redirected_to website_url(website)
+ assert_equal "active", website.reload.distillator_mode
+ event = website.rollout_events.order(:created_at).last
+ assert_equal "Manual inspection complete", event.reason
+ assert_equal true, event.readiness_snapshot["override"]
+ assert_equal "rollout.override", event.readiness_snapshot["event"]
+ assert event.readiness_snapshot["blockers"].is_a?(Array)
+ assert event.readiness_snapshot["warnings"].is_a?(Array)
+ end
+
+ test "activate after review requires a reason" do
+ website = ready_review_activation_website(seedurl: "activate-after-review-required")
+
+ post activate_after_review_website_path(website), params: { reason: " " }
+
+ assert_redirected_to website_url(website)
+ assert_equal "shadow", website.reload.distillator_mode
+ follow_redirect!
+ assert_includes @response.body, "Reason is required for Activate after review"
+ end
+
+ test "activate after review records reason and review snapshot" do
+ website = ready_review_activation_website(seedurl: "activate-after-review-success")
+
+ post activate_after_review_website_path(website), params: { reason: "Title/date/location match after manual review" }
+
+ assert_redirected_to website_url(website)
+ assert_equal "active", website.reload.distillator_mode
+ event = website.rollout_events.order(:created_at).last
+ assert_equal "Title/date/location match after manual review", event.reason
+ assert_equal "rollout.review_activate", event.readiness_snapshot["event"]
+ assert_equal true, event.readiness_snapshot["review_activation"]
+ assert_equal "review", event.readiness_snapshot["safety"]
+ assert_equal "low", event.readiness_snapshot["confidence"]
+ assert_equal "operator", event.readiness_snapshot["comparison_policy"]
+ assert_equal ["html_sha256"], event.readiness_snapshot["review_needed_fields"]
+ end
+
+ test "activate after review rejects legacy body omitted state" do
+ website = Website.create!(
+ name: "Body omitted review",
+ seedurl: "body-omitted-review",
+ graph_name: "https://example.org/body-omitted-review",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+ url = "https://example.org/body-omitted-review/event"
+ website.webpages.create!(url: url, language: "en", rdf_uri: "rdf:body-omitted-review", rdfs_class: rdfs_classes(:one))
+ Distillator::FetchCache.create!(
+ uri_key: CGI.escape(url),
+ normalized_url: url,
+ html: "ok",
+ body: "ok",
+ scrape_date: 1.hour.ago,
+ successful_refresh: 1.hour.ago,
+ headers: {},
+ signals: { "transport_success" => true, "content_success" => true },
+ final_url: url
+ )
+ website.transition_evidences.create!(
+ url: url,
+ check_kind: "fetch_parity",
+ status: "checked",
+ checked_at: 1.hour.ago,
+ details: { reason: "legacy_lookup_body_omitted", comparison_policy: "operator" }
+ )
+ website.transition_evidences.create!(url: url, check_kind: "statement_delta", status: "checked", statement_count_delta_acceptable: true, checked_at: 1.hour.ago)
+ website.transition_evidences.create!(url: url, check_kind: "export_diff", status: "checked", export_diff_checked: true, checked_at: 1.hour.ago)
+
+ post activate_after_review_website_path(website), params: { reason: "Reviewed manually" }
+
+ assert_redirected_to website_url(website)
+ assert_equal "shadow", website.reload.distillator_mode
+ follow_redirect!
+ assert_includes @response.body, "Activate after review is available only for review-needed parity differences."
+ end
+
+ test "la vitrine shadow site cannot be promoted to active with missing export evidence" do
+ website = Website.create!(
+ name: "Hector Charland",
+ seedurl: "hector-charland-com",
+ graph_name: "https://example.org/hector-charland",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+
+ patch website_url(website), params: { website: { distillator_mode: "active" } }
+
+ assert_response :success
+ assert_equal "shadow", website.reload.distillator_mode
+ assert_includes @response.body, "Cannot activate yet: export check is missing."
+ end
+
+ test "la vitrine shadow site can be promoted when durable readiness blockers are empty" do
+ website = Website.create!(
+ name: "Hector Charland",
+ seedurl: "hector-charland-com",
+ graph_name: "https://example.org/hector-charland",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+ url = "https://hector-charland-com.example/event"
+ website.webpages.create!(url: url, language: "en", rdf_uri: "rdf:hector-ready", rdfs_class: rdfs_classes(:one))
+ Distillator::FetchCache.create!(
+ uri_key: CGI.escape(url),
+ normalized_url: url,
+ html: "ok",
+ body: "ok",
+ scrape_date: 1.hour.ago,
+ successful_refresh: 1.hour.ago,
+ headers: {},
+ signals: { "transport_success" => true, "content_success" => true },
+ final_url: url
+ )
+ website.transition_evidences.create!(url: url, check_kind: "fetch_parity", status: "checked", details: { representative_urls_checked: true }, checked_at: 1.hour.ago)
+ website.transition_evidences.create!(url: url, check_kind: "statement_delta", status: "checked", statement_count_delta_acceptable: true, checked_at: 1.hour.ago)
+ website.transition_evidences.create!(url: url, check_kind: "export_diff", status: "checked", export_diff_checked: true, checked_at: 1.hour.ago)
+
+ patch website_url(website), params: { website: { distillator_mode: "active" } }
+
+ assert_redirected_to website_url(website)
+ assert_equal "active", website.reload.distillator_mode
+ end
+
+ test "ordinary shadow site cannot be promoted when checks need review" do
+ website = Website.create!(
+ name: "Review site",
+ seedurl: "review-site",
+ graph_name: "https://example.org/review-site",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+ url = "https://review-site.example/event"
+ website.webpages.create!(url: url, language: "en", rdf_uri: "rdf:review-site", rdfs_class: rdfs_classes(:one))
+ Distillator::FetchCache.create!(
+ uri_key: CGI.escape(url),
+ normalized_url: url,
+ html: "ok",
+ body: "ok",
+ scrape_date: 1.hour.ago,
+ successful_refresh: 1.hour.ago,
+ headers: {},
+ signals: { "transport_success" => true, "content_success" => true },
+ final_url: url
+ )
+
+ patch website_url(website), params: { website: { distillator_mode: "active" } }
+
+ assert_response :success
+ assert_equal "shadow", website.reload.distillator_mode
+ assert_includes @response.body, "Cannot activate yet: statements check is missing."
+ end
+
+ test "active to legacy rollback is allowed even when readiness would fail" do
+ website = Website.create!(
+ name: "Rollback site",
+ seedurl: "hector-charland-com",
+ graph_name: "https://example.org/rollback",
+ default_language: "en",
+ distillator_mode: "active"
+ )
+
+ patch website_url(website), params: { website: { distillator_mode: "legacy" } }
+
+ assert_redirected_to website_url(website)
+ assert_equal "legacy", website.reload.distillator_mode
+ assert_equal "rollout.rollback", website.rollout_events.order(:created_at).last.readiness_snapshot["event"]
+ end
+
+ test "invalid distillator_mode is rejected" do
+ original_mode = @website.distillator_mode
+
+ patch website_url(@website), params: { website: { distillator_mode: "internal" } }
+
+ assert_response :success
+ assert_equal original_mode, @website.reload.distillator_mode
+ assert_includes @response.body, "Distillator mode is not included in the list"
+ end
+
+ test "replay distillator_mode is rejected and preserves prior mode" do
+ original_mode = @website.distillator_mode
+
+ patch website_url(@website), params: { website: { distillator_mode: "replay" } }
+
+ assert_response :success
+ assert_equal original_mode, @website.reload.distillator_mode
+ assert_includes @response.body, "Distillator mode is not included in the list"
+ end
+
+ test "blank distillator_mode is rejected and preserves prior mode" do
+ @website.update!(distillator_mode: "shadow")
+
+ patch website_url(@website), params: { website: { distillator_mode: "" } }
+
+ assert_response :success
+ assert_equal "shadow", @website.reload.distillator_mode
+ assert_includes @response.body, "Distillator mode is not included in the list"
+ end
+
+ test "random distillator_mode is rejected and preserves prior mode" do
+ @website.update!(distillator_mode: "active")
+
+ patch website_url(@website), params: { website: { distillator_mode: "banana" } }
+
+ assert_response :success
+ assert_equal "active", @website.reload.distillator_mode
+ assert_includes @response.body, "Distillator mode is not included in the list"
end
test "should destroy website" do
@@ -45,4 +667,827 @@ class WebsitesControllerTest < ActionDispatch::IntegrationTest
assert_redirected_to websites_url
end
+
+ test "index sorts by selected column and direction" do
+ Website.create!(
+ name: "sort-target-b",
+ seedurl: "sort-target-b",
+ graph_name: "http://example.com/b",
+ default_language: "en"
+ )
+ Website.create!(
+ name: "sort-target-a",
+ seedurl: "sort-target-a",
+ graph_name: "http://example.com/a",
+ default_language: "en"
+ )
+
+ get websites_url, params: { q: "sort-target", sort: "name", direction: "asc" }
+ assert_response :success
+ body = @response.body
+ assert_operator body.index("sort-target-a"), :<, body.index("sort-target-b")
+
+ get websites_url, params: { q: "sort-target", sort: "name", direction: "desc" }
+ assert_response :success
+ body = @response.body
+ assert_operator body.index("sort-target-b"), :<, body.index("sort-target-a")
+ end
+
+ test "index preserves q in sortable links and toggles direction" do
+ get websites_url, params: { q: "sort-target", sort: "name", direction: "asc" }
+ assert_response :success
+ assert_includes @response.body, "q=sort-target"
+ assert_includes @response.body, "sort=name"
+ assert_includes @response.body, "direction=desc"
+ end
+
+ test "index shows direction indicator on active sort column" do
+ get websites_url, params: { sort: "name", direction: "asc" }
+ assert_response :success
+ assert_includes @response.body, "Name ↑"
+
+ get websites_url, params: { sort: "name", direction: "desc" }
+ assert_response :success
+ assert_includes @response.body, "Name ↓"
+ end
+
+ test "index falls back to safe defaults for invalid sort parameters" do
+ Website.create!(
+ name: "sort-safe-a",
+ seedurl: "sort-safe-a",
+ graph_name: "http://example.com/safe-a",
+ default_language: "en"
+ )
+ Website.create!(
+ name: "sort-safe-b",
+ seedurl: "sort-safe-b",
+ graph_name: "http://example.com/safe-b",
+ default_language: "en"
+ )
+
+ get websites_url, params: { q: "sort-safe", sort: "name;DROP TABLE websites", direction: "sideways" }
+ assert_response :success
+ body = @response.body
+ assert_operator body.index("sort-safe-a"), :<, body.index("sort-safe-b")
+ end
+
+ test "index sorts computed webpages_count numerically and treats missing values as zero" do
+ zero = Website.create!(
+ name: "computed webpages zero",
+ seedurl: "computed-webpages-zero",
+ graph_name: "http://example.com/computed-webpages-zero",
+ default_language: "en"
+ )
+ two = Website.create!(
+ name: "computed webpages two",
+ seedurl: "computed-webpages-two",
+ graph_name: "http://example.com/computed-webpages-two",
+ default_language: "en"
+ )
+ ten = Website.create!(
+ name: "computed webpages ten",
+ seedurl: "computed-webpages-ten",
+ graph_name: "http://example.com/computed-webpages-ten",
+ default_language: "en"
+ )
+
+ 2.times do |index|
+ Webpage.create!(
+ url: "http://example.com/computed-two/#{index}",
+ language: "en",
+ rdf_uri: "computed-two-#{two.id}-#{index}",
+ rdfs_class: rdfs_classes(:one),
+ website: two
+ )
+ end
+
+ 10.times do |index|
+ Webpage.create!(
+ url: "http://example.com/computed-ten/#{index}",
+ language: "en",
+ rdf_uri: "computed-ten-#{ten.id}-#{index}",
+ rdfs_class: rdfs_classes(:one),
+ website: ten
+ )
+ end
+
+ get websites_url, params: { q: "computed webpages", sort: "webpages_count", direction: "asc" }
+ assert_response :success
+ body = @response.body
+ assert_operator body.index(zero.name), :<, body.index(two.name)
+ assert_operator body.index(two.name), :<, body.index(ten.name)
+
+ get websites_url, params: { q: "computed webpages", sort: "webpages_count", direction: "desc" }
+ assert_response :success
+ body = @response.body
+ assert_operator body.index(ten.name), :<, body.index(two.name)
+ assert_operator body.index(two.name), :<, body.index(zero.name)
+ end
+
+ test "index filters by seedurl partial match" do
+ Website.create!(
+ name: "seed filter one",
+ seedurl: "seed-match",
+ graph_name: "http://example.com/seed-one",
+ default_language: "en"
+ )
+ Website.create!(
+ name: "seed filter two",
+ seedurl: "other-seed-two",
+ graph_name: "http://example.com/seed-two",
+ default_language: "en"
+ )
+
+ get websites_url, params: { seed_filter: "seed-match" }
+ assert_response :success
+ assert_includes @response.body, "seed-match"
+ assert_not_includes @response.body, "other-seed-two"
+ end
+
+ test "index filters by q case-insensitively" do
+ Website.create!(
+ name: "Culture Alpha",
+ seedurl: "culture-alpha",
+ graph_name: "http://example.com/culture-alpha",
+ default_language: "en"
+ )
+ Website.create!(
+ name: "culture Beta",
+ seedurl: "culture-beta",
+ graph_name: "http://example.com/culture-beta",
+ default_language: "en"
+ )
+
+ get websites_url, params: { q: "cultu" }
+ assert_response :success
+ assert_includes @response.body, "Culture Alpha"
+ assert_includes @response.body, "culture Beta"
+ end
+
+ test "index filters by default_language exact match" do
+ Website.create!(
+ name: "lang filter en",
+ seedurl: "lang-filter-en",
+ graph_name: "http://example.com/lang-en",
+ default_language: "en"
+ )
+ Website.create!(
+ name: "lang filter fr",
+ seedurl: "lang-filter-fr",
+ graph_name: "http://example.com/lang-fr",
+ default_language: "fr"
+ )
+
+ get websites_url, params: { default_language: "fr" }
+ assert_response :success
+ assert_includes @response.body, "lang-filter-fr"
+ assert_not_includes @response.body, "lang-filter-en"
+ end
+
+ test "websites index filters by legacy distillator mode" do
+ legacy = Website.create!(
+ name: "legacy rollout site",
+ seedurl: "legacy-rollout-site",
+ graph_name: "http://example.com/legacy-rollout-site",
+ default_language: "en",
+ distillator_mode: "legacy"
+ )
+ shadow = Website.create!(
+ name: "shadow rollout site",
+ seedurl: "shadow-rollout-site",
+ graph_name: "http://example.com/shadow-rollout-site",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+
+ get websites_url, params: { distillator_mode: "legacy" }
+
+ assert_response :success
+ assert_includes @response.body, legacy.name
+ assert_not_includes @response.body, shadow.name
+ end
+
+ test "websites index filters by shadow distillator mode" do
+ legacy = Website.create!(
+ name: "legacy rollout site",
+ seedurl: "legacy-rollout-site",
+ graph_name: "http://example.com/legacy-rollout-site",
+ default_language: "en",
+ distillator_mode: "legacy"
+ )
+ shadow = Website.create!(
+ name: "shadow rollout site",
+ seedurl: "shadow-rollout-site",
+ graph_name: "http://example.com/shadow-rollout-site",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+
+ get websites_url, params: { distillator_mode: "shadow" }
+
+ assert_response :success
+ assert_includes @response.body, shadow.name
+ assert_not_includes @response.body, legacy.name
+ assert_includes @response.body, 'name="distillator_mode"'
+ end
+
+ test "websites index filters by active distillator mode" do
+ active = Website.create!(
+ name: "active rollout site",
+ seedurl: "active-rollout-site",
+ graph_name: "http://example.com/active-rollout-site",
+ default_language: "en",
+ distillator_mode: "active"
+ )
+ legacy = Website.create!(
+ name: "legacy rollout site two",
+ seedurl: "legacy-rollout-site-two",
+ graph_name: "http://example.com/legacy-rollout-site-two",
+ default_language: "en",
+ distillator_mode: "legacy"
+ )
+
+ get websites_url, params: { distillator_mode: "active" }
+
+ assert_response :success
+ assert_includes @response.body, active.name
+ assert_not_includes @response.body, legacy.name
+ end
+
+ test "websites index ignores invalid distillator mode safely" do
+ safe = Website.create!(
+ name: "safe rollout site",
+ seedurl: "safe-rollout-site",
+ graph_name: "http://example.com/safe-rollout-site",
+ default_language: "en",
+ distillator_mode: "legacy"
+ )
+
+ get websites_url, params: { distillator_mode: "shadow;DROP TABLE websites" }
+
+ assert_response :redirect
+ assert_redirected_to "/websites"
+ follow_redirect!
+ assert_response :success
+ assert_includes @response.body, safe.name
+ end
+
+ test "websites index filters by unknown unset distillator mode" do
+ Website.create!(
+ name: "unknown rollout blank",
+ seedurl: "unknown-rollout-blank",
+ graph_name: "http://example.com/unknown-rollout-blank",
+ default_language: "en",
+ distillator_mode: "legacy"
+ ).update_column(:distillator_mode, "")
+ Website.create!(
+ name: "known rollout active",
+ seedurl: "known-rollout-active",
+ graph_name: "http://example.com/known-rollout-active",
+ default_language: "en",
+ distillator_mode: "active"
+ )
+
+ get websites_url, params: { distillator_mode: "unknown" }
+
+ assert_response :success
+ assert_includes @response.body, "unknown rollout blank"
+ assert_not_includes @response.body, "known rollout active"
+ end
+
+ test "websites index shows staging warning and invalid on staging summary link" do
+ ENV["DISTILLATOR_RUNTIME"] = "staging"
+ @website.update!(distillator_mode: "legacy")
+
+ get websites_url
+
+ assert_response :success
+ assert_includes @response.body, "Staging requires every website to be Shadow or Active."
+ assert_select 'a[href="/websites?distillator_mode=invalid_on_staging"]', text: /Invalid on staging:/
+ ensure
+ ENV["DISTILLATOR_RUNTIME"] = nil
+ end
+
+ test "websites index filters invalid on staging rows" do
+ ENV["DISTILLATOR_RUNTIME"] = "staging"
+ Website.create!(
+ name: "Legacy invalid website filter",
+ seedurl: "legacy-invalid-website-filter",
+ graph_name: "http://example.com/legacy-invalid-website-filter",
+ default_language: "en",
+ distillator_mode: "legacy"
+ )
+ Website.create!(
+ name: "Active valid website filter",
+ seedurl: "active-valid-website-filter",
+ graph_name: "http://example.com/active-valid-website-filter",
+ default_language: "en",
+ distillator_mode: "active"
+ )
+
+ get websites_url, params: { distillator_mode: "invalid_on_staging" }
+
+ assert_response :success
+ assert_includes @response.body, "Legacy invalid website filter"
+ assert_not_includes @response.body, "Active valid website filter"
+ ensure
+ ENV["DISTILLATOR_RUNTIME"] = nil
+ end
+
+ test "index filters by graph_name partial match" do
+ Website.create!(
+ name: "graph filter one",
+ seedurl: "graph-filter-one",
+ graph_name: "http://example.com/graph-match",
+ default_language: "en"
+ )
+ Website.create!(
+ name: "graph filter two",
+ seedurl: "graph-filter-two",
+ graph_name: "http://example.com/graph-other",
+ default_language: "en"
+ )
+
+ get websites_url, params: { graph_name: "graph-match" }
+ assert_response :success
+ assert_includes @response.body, "graph-filter-one"
+ assert_not_includes @response.body, "graph-filter-two"
+ end
+
+ test "index composes filtering with sorting" do
+ Website.create!(
+ name: "compose sort b",
+ seedurl: "compose-seed",
+ graph_name: "http://example.com/compose-b",
+ default_language: "en"
+ )
+ Website.create!(
+ name: "compose sort a",
+ seedurl: "compose-seed",
+ graph_name: "http://example.com/compose-a",
+ default_language: "en"
+ )
+ Website.create!(
+ name: "compose sort outside",
+ seedurl: "outside-seed",
+ graph_name: "http://example.com/compose-outside",
+ default_language: "en"
+ )
+
+ get websites_url, params: { seed_filter: "compose-seed", sort: "name", direction: "asc" }
+ assert_response :success
+ body = @response.body
+ assert_operator body.index("compose sort a"), :<, body.index("compose sort b")
+ assert_not_includes body, "compose sort outside"
+ end
+
+ test "websites index preserves distillator mode filter in sort links" do
+ get websites_url, params: { distillator_mode: "shadow", q: "sort-target", sort: "name", direction: "asc" }
+
+ assert_response :success
+ assert_sort_link_preserves_params(
+ label: "Name",
+ sort_key: "name",
+ params: { distillator_mode: "shadow", q: "sort-target" }
+ )
+ end
+
+ test "websites index reset filters clears distillator mode" do
+ get websites_url, params: { distillator_mode: "shadow", q: "needle" }
+
+ assert_response :success
+ assert_harmonized_reset_filters_link(path: "/websites")
+ assert_select 'a[href="/websites"]', text: "Reset filters"
+ end
+
+ test "index handles empty or invalid filter params without breaking" do
+ get websites_url, params: { q: "", seed_filter: "", default_language: "xx", graph_name: "" }
+ assert_response :success
+ end
+
+ test "index with blank filters shows unfiltered results" do
+ Website.create!(
+ name: "blank-filter-one",
+ seedurl: "blank-filter-seed-1",
+ graph_name: "http://example.com/blank-one",
+ default_language: "en"
+ )
+ Website.create!(
+ name: "blank-filter-two",
+ seedurl: "blank-filter-seed-2",
+ graph_name: "http://example.com/blank-two",
+ default_language: "fr"
+ )
+
+ get websites_url, params: { q: "", seed_filter: "", default_language: "", graph_name: "" }
+ assert_response :success
+ assert_includes @response.body, "blank-filter-one"
+ assert_includes @response.body, "blank-filter-two"
+ end
+
+ test "index treats wildcard as a literal filter value" do
+ get websites_url, params: { q: "*", seed_filter: "*", graph_name: "*" }
+ assert_response :success
+ assert_not_includes @response.body, websites(:one).name
+ assert_not_includes @response.body, websites(:two).name
+ end
+
+ test "websites index renders active cache link in legacy and internal modes" do
+ assert_read_only_page_does_not_fetch
+
+ ENV["DISTILLATOR_FETCH_MODE"] = "legacy"
+ get websites_url
+ assert_response :success
+ assert_includes @response.body, "Open active cache"
+
+ ENV["DISTILLATOR_FETCH_MODE"] = "internal"
+ get websites_url
+ assert_response :success
+ assert_includes @response.body, "Open active cache"
+ end
+
+ test "websites index renders rollout badge for legacy website" do
+ assert_read_only_page_does_not_fetch
+ @website.update!(distillator_mode: "legacy")
+
+ get websites_url
+
+ assert_response :success
+ assert_includes @response.body, "Legacy"
+ assert_not_includes @response.body, ">legacy<"
+ end
+
+ test "websites index renders rollout badge for shadow website" do
+ assert_read_only_page_does_not_fetch
+ @website.update!(distillator_mode: "shadow")
+
+ get websites_url
+
+ assert_response :success
+ assert_includes @response.body, "Shadow"
+ assert_not_includes @response.body, ">shadow<"
+ end
+
+ test "websites index renders rollout badge for active website" do
+ assert_read_only_page_does_not_fetch
+ @website.update!(distillator_mode: "active")
+
+ get websites_url
+
+ assert_response :success
+ assert_includes @response.body, "Active"
+ assert_not_includes @response.body, ">active<"
+ end
+
+ test "websites index renders safe fallback for unknown rollout mode" do
+ assert_read_only_page_does_not_fetch
+ @website.update_column(:distillator_mode, "")
+
+ get websites_url
+
+ assert_response :success
+ assert_includes @response.body, "Unknown"
+ end
+
+ test "websites index rollout badges do not expose internal mode names" do
+ assert_read_only_page_does_not_fetch
+ @website.update!(distillator_mode: "active")
+
+ get websites_url
+
+ assert_response :success
+ assert_not_includes @response.body, ">internal<"
+ end
+
+ test "website detail shows rollout panel for legacy website" do
+ assert_read_only_page_does_not_fetch
+ @website.update!(distillator_mode: "legacy")
+
+ get website_url(@website)
+
+ assert_response :success
+ assert_includes @response.body, "Transition verdict"
+ assert_includes @response.body, "Not checked: statements check is missing."
+ assert_includes @response.body, "Statements missing | Export missing | Fetch missing"
+ assert_includes @response.body, "Move to shadow"
+ assert_includes @response.body, "Blocking reason / promote control"
+ assert_not_includes @response.body, "Queue transition check"
+ end
+
+ test "website detail shows rollout panel for shadow website" do
+ assert_read_only_page_does_not_fetch
+ stub_remote_wringer_endpoint
+ website = ready_shadow_website(seedurl: "rollout-panel-shadow")
+
+ get website_url(website)
+
+ assert_response :success
+ assert_includes @response.body, "Transition verdict"
+ assert_includes @response.body, "Ready: all transition checks passed."
+ assert_includes @response.body, "Statements passed | Export passed | Fetch passed"
+ assert_includes @response.body, "Promote to active"
+ assert_includes @response.body, "Run transition batch check"
+ end
+
+ test "website detail shows rollout panel for active website" do
+ assert_read_only_page_does_not_fetch
+ @website.update!(distillator_mode: "active")
+
+ get website_url(@website)
+
+ assert_response :success
+ assert_includes @response.body, "Transition verdict"
+ assert_includes @response.body, "Rollback to Legacy Wringer"
+ end
+
+ test "website detail shows comparison link only for shadow website" do
+ assert_read_only_page_does_not_fetch
+ stub_remote_wringer_endpoint
+ @website.update!(distillator_mode: "shadow")
+
+ get website_url(@website)
+
+ assert_response :success
+ assert_not_includes @response.body, "Compare Condenser vs Wringer"
+ end
+
+ test "website detail shows legacy inspection link for active website" do
+ assert_read_only_page_does_not_fetch
+ stub_remote_wringer_endpoint
+ @website.update!(distillator_mode: "active")
+
+ get website_url(@website)
+
+ assert_response :success
+ assert_includes @response.body, "Open cache diagnostics"
+ assert_not_includes @response.body, "Inspect legacy Wringer"
+ end
+
+ test "website detail rollout panel does not fetch" do
+ assert_read_only_page_does_not_fetch
+ @website.update!(distillator_mode: "shadow")
+
+ get website_url(@website)
+
+ assert_response :success
+ end
+
+ test "website rollout panel uses centralized operator copy and avoids retired wording" do
+ assert_read_only_page_does_not_fetch
+ @website.update!(distillator_mode: "active")
+
+ get website_url(@website)
+
+ assert_response :success
+ assert_includes @response.body, "Transition"
+ assert_includes @response.body, Distillator::RolloutCopy.label(:active)
+ assert_not_includes @response.body, Distillator::RolloutCopy.rollout_panel_title
+ assert_not_includes @response.body, "Distillator rollout"
+ assert_not_includes @response.body, "internal"
+ assert_not_includes @response.body, "new cache"
+ assert_not_includes @response.body, "phase I"
+ assert_not_includes @response.body, "preview only"
+ assert_not_includes @response.body, "first UI batch"
+ end
+
+ test "websites index shows rollout summary counts" do
+ Website.create!(
+ name: "summary shadow",
+ seedurl: "summary-shadow",
+ graph_name: "http://example.com/summary-shadow",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+
+ get websites_url
+
+ assert_response :success
+ assert_includes @response.body, "Legacy:"
+ assert_includes @response.body, "Shadow:"
+ assert_includes @response.body, "Active:"
+ assert_includes @response.body, "Unknown:"
+ end
+
+ test "websites index summary counts include legacy shadow active and unknown" do
+ Website.create!(
+ name: "summary legacy",
+ seedurl: "summary-legacy",
+ graph_name: "http://example.com/summary-legacy",
+ default_language: "en",
+ distillator_mode: "legacy"
+ )
+ Website.create!(
+ name: "summary active",
+ seedurl: "summary-active",
+ graph_name: "http://example.com/summary-active",
+ default_language: "en",
+ distillator_mode: "active"
+ )
+ Website.create!(
+ name: "summary unknown",
+ seedurl: "summary-unknown",
+ graph_name: "http://example.com/summary-unknown",
+ default_language: "en",
+ distillator_mode: "legacy"
+ ).update_column(:distillator_mode, "")
+
+ get websites_url
+
+ assert_response :success
+ assert_select 'a[href="/websites?distillator_mode=legacy"]', text: /Legacy:/
+ assert_select 'a[href="/websites?distillator_mode=shadow"]', text: /Shadow:/
+ assert_select 'a[href="/websites?distillator_mode=active"]', text: /Active:/
+ assert_select 'a[href="/websites?distillator_mode=unknown"]', text: /Unknown:/
+ end
+
+ test "websites index summary counts remain global when search is applied" do
+ Website.create!(
+ name: "global legacy count",
+ seedurl: "global-legacy-count",
+ graph_name: "http://example.com/global-legacy-count",
+ default_language: "en",
+ distillator_mode: "legacy"
+ )
+ Website.create!(
+ name: "global shadow count",
+ seedurl: "global-shadow-count",
+ graph_name: "http://example.com/global-shadow-count",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+
+ get websites_url, params: { q: "global legacy count" }
+
+ assert_response :success
+ assert_includes @response.body, "Shadow: 1"
+ end
+
+ test "websites index does not fetch" do
+ assert_read_only_page_does_not_fetch
+
+ get websites_url
+
+ assert_response :success
+ end
+
+ test "website edit does not fetch" do
+ assert_read_only_page_does_not_fetch
+
+ get edit_website_url(@website)
+
+ assert_response :success
+ end
+
+ test "website new does not fetch" do
+ assert_read_only_page_does_not_fetch
+
+ get new_website_url
+
+ assert_response :success
+ end
+
+ private
+
+ def ready_shadow_website(seedurl:)
+ website = Website.create!(
+ name: "Ready shadow #{seedurl}",
+ seedurl: seedurl,
+ graph_name: "https://example.org/#{seedurl}",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+ url = "https://example.org/#{seedurl}/event"
+ website.webpages.create!(url: url, language: "en", rdf_uri: "rdf:#{seedurl}", rdfs_class: rdfs_classes(:one))
+ Distillator::FetchCache.create!(
+ uri_key: CGI.escape(url),
+ normalized_url: url,
+ html: "ok",
+ body: "ok",
+ scrape_date: 1.hour.ago,
+ successful_refresh: 1.hour.ago,
+ headers: {},
+ signals: { "transport_success" => true, "content_success" => true },
+ final_url: url
+ )
+ website.transition_evidences.create!(url: url, check_kind: "fetch_parity", status: "checked", details: { representative_urls_checked: true }, checked_at: 1.hour.ago)
+ website.transition_evidences.create!(url: url, check_kind: "statement_delta", status: "checked", statement_count_delta_acceptable: true, checked_at: 1.hour.ago)
+ website.transition_evidences.create!(url: url, check_kind: "export_diff", status: "checked", export_diff_checked: true, checked_at: 1.hour.ago)
+ website
+ end
+
+ def blocked_shadow_website(seedurl:)
+ website = Website.create!(
+ name: "Blocked shadow #{seedurl}",
+ seedurl: seedurl,
+ graph_name: "https://example.org/#{seedurl}",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+ url = "https://example.org/#{seedurl}/event"
+ website.webpages.create!(url: url, language: "en", rdf_uri: "rdf:#{seedurl}", rdfs_class: rdfs_classes(:one))
+ Distillator::FetchCache.create!(
+ uri_key: CGI.escape(url),
+ normalized_url: url,
+ html: "ok",
+ body: "ok",
+ scrape_date: 1.hour.ago,
+ successful_refresh: 1.hour.ago,
+ headers: {},
+ signals: { "transport_success" => true, "content_success" => true },
+ final_url: url
+ )
+ website.transition_evidences.create!(
+ url: url,
+ check_kind: "statement_delta",
+ status: "failed",
+ statement_count_delta_acceptable: false,
+ checked_at: 1.hour.ago
+ )
+ website
+ end
+
+ def ready_review_activation_website(seedurl:)
+ website = Website.create!(
+ name: "Review-ready #{seedurl}",
+ seedurl: seedurl,
+ graph_name: "https://example.org/#{seedurl}",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+ url = "https://example.org/#{seedurl}/event"
+ website.webpages.create!(url: url, language: "en", rdf_uri: "rdf:#{seedurl}", rdfs_class: rdfs_classes(:one))
+ Distillator::FetchCache.create!(
+ uri_key: CGI.escape(url),
+ normalized_url: url,
+ html: "ok",
+ body: "ok",
+ scrape_date: 1.hour.ago,
+ successful_refresh: 1.hour.ago,
+ headers: {},
+ signals: { "transport_success" => true, "content_success" => true },
+ final_url: url
+ )
+ website.transition_evidences.create!(
+ url: url,
+ check_kind: "fetch_parity",
+ status: "checked",
+ checked_at: 1.hour.ago,
+ details: {
+ reason: "review_needed_difference",
+ comparison_policy: "operator",
+ compare_summary: { review_needed_diffs: ["html_sha256"] }
+ }
+ )
+ website.transition_evidences.create!(url: url, check_kind: "statement_delta", status: "checked", statement_count_delta_acceptable: true, checked_at: 1.hour.ago)
+ website.transition_evidences.create!(url: url, check_kind: "export_diff", status: "checked", export_diff_checked: true, checked_at: 1.hour.ago)
+ website
+ end
+
+ def create_publishable_statements_for(webpage)
+ [
+ [properties(:four), "Publishable title"],
+ [properties(:location), '[["Salle","uri:place"]]'],
+ [properties(:six), '["2026-06-01T20:00:00-04:00"]']
+ ].each do |property, cache|
+ source = Source.create!(
+ website: webpage.website,
+ property: property,
+ language: "en",
+ selected: true,
+ algorithm_value: "controller-test"
+ )
+
+ Statement.create!(
+ webpage: webpage,
+ source: source,
+ cache: cache,
+ status: "ok"
+ )
+ Statement.where(webpage: webpage, source: source).update_all(status: "ok")
+ end
+ end
+
+ def assert_read_only_page_does_not_fetch
+ Distillator::FetchCacheStore.expects(:fetch).never
+ Distillator::FetchService.expects(:fetch).never
+ Distillator::NativeFetch.expects(:call).never
+ Distillator::FetchShadowComparator.expects(:call).never
+ end
+
+ def assert_no_cohort_source_requests
+ WebMock.assert_not_requested(:any, Distillator::Cohorts::LavitrinePipeline.query_url)
+ end
+
+ def stub_remote_wringer_endpoint
+ Distillator::WringerEndpoint.stubs(:current).returns(
+ Distillator::WringerEndpoint::Result.new(
+ compatibility_base_url: "http://compat.example",
+ legacy_lookup_base_url: "http://wringer.example",
+ compatibility_source: "DISTILLATOR_COMPAT_BASE_URL",
+ state: :remote_configured,
+ status_label: "Current Wringer: Remote configured",
+ status_detail: "http://compat.example via DISTILLATOR_COMPAT_BASE_URL"
+ )
+ )
+ end
end
diff --git a/test/controllers/wringer_compat_controller_test.rb b/test/controllers/wringer_compat_controller_test.rb
new file mode 100644
index 00000000..44968e95
--- /dev/null
+++ b/test/controllers/wringer_compat_controller_test.rb
@@ -0,0 +1,479 @@
+require "test_helper"
+
+class WringerCompatControllerTest < ActionDispatch::IntegrationTest
+ setup do
+ Distillator::FetchCache.delete_all
+ end
+
+ test "missing uri returns no_content and does not fetch" do
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get wring_websites_url, params: { format: "json" }
+
+ assert_response :no_content
+ end
+
+ test "raw response renders cached html and removes x frame options" do
+ Distillator::FetchCacheStore.expects(:fetch).with do |kwargs|
+ assert_equal "http://example.org/cached", kwargs[:uri]
+ true
+ end.returns(cache_like(html: "cached"))
+
+ get wring_websites_url, params: { uri: "http://example.org/cached", format: "raw" }
+
+ assert_response :success
+ assert_equal "cached", @response.body
+ assert_nil @response.headers["X-Frame-Options"]
+ end
+
+ test "raw response returns preserved last known good html after 404" do
+ Distillator::FetchCacheStore.expects(:fetch).returns(
+ cache_like(
+ html: "last good",
+ signals: { network_status: "ok", content_type: "html" },
+ hints: [],
+ final_url: nil,
+ redirect_chain: [],
+ http_response_code: 404
+ )
+ )
+
+ get wring_websites_url, params: { uri: "http://example.org/cached", format: "raw", force_scrape: "true" }
+
+ assert_response :success
+ assert_equal "last good", @response.body
+ assert_nil @response.headers["X-Frame-Options"]
+ end
+
+ test "html format redirects to websites path with legacy notice and removes x frame options" do
+ Distillator::FetchCacheStore.expects(:fetch).returns(cache_like(html: "cached"))
+
+ get wring_websites_url, params: { uri: "http://example.org/cached", format: "html" }
+
+ assert_redirected_to websites_path
+ follow_redirect!
+ assert_equal "Website was successfully wrung.", flash[:notice]
+ end
+
+ test "html format redirects after forced refresh" do
+ Distillator::FetchCacheStore.expects(:fetch).with do |kwargs|
+ assert_equal true, kwargs[:force_scrape]
+ true
+ end.returns(cache_like(html: "fresh"))
+
+ get wring_websites_url, params: { uri: "http://example.org/cached", format: "html", force_scrape: "true" }
+
+ assert_redirected_to websites_path
+ follow_redirect!
+ assert_equal "Website was successfully wrung.", flash[:notice]
+ end
+
+ test "json response returns exact legacy compatible contract keys and preserves redirect metadata" do
+ Distillator::FetchCacheStore.expects(:fetch).returns(cache_like(
+ html: "cached",
+ signals: {
+ network_status: "ok",
+ content_type: "html",
+ redirect_type: "normal",
+ redirected: true,
+ final_url: "https://example.org/final"
+ },
+ hints: ["json_detected"],
+ final_url: "https://example.org/final",
+ redirect_chain: ["http://example.org/cached", "https://example.org/final"],
+ http_response_code: 200
+ ))
+
+ get wring_websites_url, params: { uri: "http://example.org/cached", format: "json" }
+
+ assert_response :success
+ payload = JSON.parse(@response.body)
+ assert_equal %w[final_url hints html http_code redirect_chain signals].sort, payload.keys.sort
+ assert_equal "cached", payload["html"]
+ assert_equal "ok", payload["signals"]["network_status"]
+ assert_equal "html", payload["signals"]["content_type"]
+ assert_equal "normal", payload["signals"]["redirect_type"]
+ assert_equal true, payload["signals"]["redirected"]
+ assert_equal "https://example.org/final", payload["signals"]["final_url"]
+ assert_equal ["json_detected"], payload["hints"]
+ assert_equal "https://example.org/final", payload["final_url"]
+ assert_equal ["http://example.org/cached", "https://example.org/final"], payload["redirect_chain"]
+ assert_equal 200, payload["http_code"]
+ assert_nil @response.headers["X-Frame-Options"]
+ end
+
+ test "json response preserves empty redirect chain as array" do
+ Distillator::FetchCacheStore.expects(:fetch).returns(cache_like(
+ html: "cached",
+ signals: { network_status: "ok" },
+ hints: [],
+ final_url: nil,
+ redirect_chain: [],
+ http_response_code: 200
+ ))
+
+ get wring_websites_url, params: { uri: "http://example.org/cached", format: "json" }
+
+ assert_response :success
+ payload = JSON.parse(@response.body)
+ assert_equal [], payload["redirect_chain"]
+ assert_equal [], payload["hints"]
+ end
+
+ test "json response returns failure signals and hints without crashing" do
+ Distillator::FetchCacheStore.expects(:fetch).returns(cache_like(
+ html: "last good",
+ signals: { network_status: "failed", timeout: true, content_type: "unknown", redirect_type: "none" },
+ hints: ["timeout"],
+ final_url: nil,
+ redirect_chain: [],
+ http_response_code: nil
+ ))
+
+ get wring_websites_url, params: { uri: "http://example.org/cached", format: "json", force_scrape: "true" }
+
+ assert_response :success
+ payload = JSON.parse(@response.body)
+ assert_equal %w[final_url hints html http_code redirect_chain signals].sort, payload.keys.sort
+ assert_equal "last good", payload["html"]
+ assert_equal "failed", payload["signals"]["network_status"]
+ assert_equal true, payload["signals"]["timeout"]
+ assert_equal "unknown", payload["signals"]["content_type"]
+ assert_equal "none", payload["signals"]["redirect_type"]
+ assert_equal ["timeout"], payload["hints"]
+ assert_nil payload["final_url"]
+ assert_equal [], payload["redirect_chain"]
+ assert_nil payload["http_code"]
+ end
+
+ test "json_post replay preserves POST response contract fields" do
+ Distillator::FetchCacheStore.expects(:fetch).returns(cache_like(
+ html: '{"result":"ok"}',
+ signals: {
+ network_status: "ok",
+ content_type: "json",
+ request_method: "POST",
+ redirect_type: "none"
+ },
+ hints: ["json_detected"],
+ final_url: "https://example.org/api",
+ redirect_chain: [],
+ http_response_code: 200
+ ))
+
+ get wring_websites_url, params: { uri: "https://example.org/api", format: "json", json_post: "true" }
+
+ assert_response :success
+ payload = JSON.parse(@response.body)
+ assert_equal %w[final_url hints html http_code redirect_chain signals].sort, payload.keys.sort
+ assert_equal '{"result":"ok"}', payload["html"]
+ assert_equal "POST", payload["signals"]["request_method"]
+ assert_equal "json", payload["signals"]["content_type"]
+ assert_equal ["json_detected"], payload["hints"]
+ assert_equal "https://example.org/api", payload["final_url"]
+ assert_equal [], payload["redirect_chain"]
+ assert_equal 200, payload["http_code"]
+ end
+
+ test "json_post replay preserves deterministic empty-body failure metadata" do
+ Distillator::FetchCacheStore.expects(:fetch).returns(cache_like(
+ html: "",
+ signals: {
+ network_status: "ok",
+ content_type: "json",
+ request_method: "POST",
+ redirect_type: "none",
+ empty_body: true
+ },
+ hints: ["empty_body"],
+ final_url: "https://example.org/api",
+ redirect_chain: [],
+ http_response_code: 200
+ ))
+
+ get wring_websites_url, params: { uri: "https://example.org/api", format: "json", json_post: "true", force_scrape: "true" }
+
+ assert_response :success
+ payload = JSON.parse(@response.body)
+ assert_equal "", payload["html"]
+ assert_equal true, payload["signals"]["empty_body"]
+ assert_equal "POST", payload["signals"]["request_method"]
+ assert_equal ["empty_body"], payload["hints"]
+ assert_equal 200, payload["http_code"]
+ end
+
+ test "json response returns preserved html with failed http code after 404" do
+ Distillator::FetchCacheStore.expects(:fetch).returns(cache_like(
+ html: "last good",
+ signals: { network_status: "ok", content_type: "html" },
+ hints: [],
+ final_url: nil,
+ redirect_chain: [],
+ http_response_code: 404
+ ))
+
+ get wring_websites_url, params: { uri: "http://example.org/cached", format: "json", force_scrape: "true" }
+
+ assert_response :success
+ payload = JSON.parse(@response.body)
+ assert_equal %w[final_url hints html http_code redirect_chain signals].sort, payload.keys.sort
+ assert_equal "last good", payload["html"]
+ assert_equal 404, payload["http_code"]
+ assert_equal [], payload["hints"]
+ assert_equal [], payload["redirect_chain"]
+ end
+
+ test "iframe uri returns extracted iframe html in json format" do
+ Distillator::FetchCacheStore.expects(:fetch).with do |kwargs|
+ assert_equal "http://example.org/iframe", kwargs[:uri]
+ assert_equal true, kwargs[:use_phantomjs]
+ true
+ end.returns(cache_like(
+ html: "child iframe",
+ signals: { network_status: "ok", content_type: "html" },
+ hints: [],
+ http_response_code: 200
+ ))
+
+ get wring_websites_url, params: { uri: "http://example.org/iframe", format: "json" }
+
+ assert_response :success
+ payload = JSON.parse(@response.body)
+ assert_equal "child iframe", payload["html"]
+ assert_equal 200, payload["http_code"]
+ end
+
+ test "iframe uri returns extracted iframe html in raw format" do
+ Distillator::FetchCacheStore.expects(:fetch).with do |kwargs|
+ assert_equal "http://example.org/iframe", kwargs[:uri]
+ assert_equal true, kwargs[:use_phantomjs]
+ true
+ end.returns(cache_like(
+ html: "child iframe",
+ signals: { network_status: "ok", content_type: "html" },
+ hints: [],
+ http_response_code: 200
+ ))
+
+ get wring_websites_url, params: { uri: "http://example.org/iframe", format: "raw" }
+
+ assert_response :success
+ assert_equal "child iframe", @response.body
+ end
+
+ test "iframe non 2xx response preserves cached html at controller boundary" do
+ Distillator::FetchCacheStore.expects(:fetch).with do |kwargs|
+ assert_equal "http://example.org/iframe", kwargs[:uri]
+ assert_equal true, kwargs[:use_phantomjs]
+ true
+ end.returns(cache_like(
+ html: "last good iframe",
+ signals: { network_status: "ok", content_type: "html" },
+ hints: [],
+ http_response_code: 404
+ ))
+
+ get wring_websites_url, params: { uri: "http://example.org/iframe", format: "json", force_scrape: "true" }
+
+ assert_response :success
+ payload = JSON.parse(@response.body)
+ assert_equal "last good iframe", payload["html"]
+ assert_equal 404, payload["http_code"]
+ end
+
+ test "wring passes include_fragment to fetch cache store" do
+ expect_wring_fetch_param(:include_fragment, true, uri: "http://example.org/page#fragment")
+
+ get wring_websites_url, params: { uri: "http://example.org/page#fragment", format: "json", include_fragment: "true" }
+
+ assert_response :success
+ end
+
+ test "wring passes force_scrape to fetch cache store" do
+ expect_wring_fetch_param(:force_scrape, true)
+
+ get wring_websites_url, params: { uri: "http://example.org/cached", format: "json", force_scrape: "true" }
+
+ assert_response :success
+ end
+
+ test "wring passes force_scrape_every_hrs to fetch cache store" do
+ expect_wring_fetch_param(:force_scrape_every_hrs, "24")
+
+ get wring_websites_url, params: { uri: "http://example.org/cached", format: "json", force_scrape_every_hrs: "24" }
+
+ assert_response :success
+ end
+
+ test "wring passes absolute_src to fetch cache store" do
+ expect_wring_fetch_param(:absolute_src, true)
+
+ get wring_websites_url, params: { uri: "http://example.org/cached", format: "json", absolute_src: "true" }
+
+ assert_response :success
+ end
+
+ test "wring passes json_post to fetch cache store" do
+ expect_wring_fetch_param(:json_post, true)
+
+ get wring_websites_url, params: { uri: "http://example.org/cached", format: "json", json_post: "true" }
+
+ assert_response :success
+ end
+
+ test "wring passes use_phantomjs to fetch cache store" do
+ expect_wring_fetch_param(:use_phantomjs, true)
+
+ get wring_websites_url, params: { uri: "http://example.org/cached", format: "json", use_phantomjs: "true" }
+
+ assert_response :success
+ end
+
+ test "/websites.json?term finds escaped uri" do
+ cache = create_cache(
+ html: "cached 404",
+ name: "Cached 404",
+ json_ld: { "@type" => "Event" },
+ http_response_code: 404
+ )
+
+ get websites_url(format: :json), params: { term: "http%3A%2F%2Fexample.org%2Fcached" }
+
+ assert_response :success
+ payload = JSON.parse(@response.body)
+ assert_equal 1, payload.size
+ record = payload.first
+ assert_equal cache.id, record["id"]
+ assert_equal "http%3A%2F%2Fexample.org%2Fcached", record["uri"]
+ assert_equal "cached 404", record["html"]
+ assert_equal "Cached 404", record["name"]
+ assert_equal({ "@type" => "Event" }, record["json_ld"])
+ assert record["scrape_date"].present?
+ assert record["successful_refresh"].present?
+ assert_equal 404, record["http_response_code"]
+ assert record["created_at"].present?
+ assert record["updated_at"].present?
+ end
+
+ test "/websites.json?term supports exact legacy URL string" do
+ create_cache(http_response_code: 404)
+
+ get "/websites.json?term=http%3A%2F%2Fexample.org%2Fcached"
+
+ assert_response :success
+ payload = JSON.parse(@response.body)
+ assert_equal 1, payload.size
+ assert_equal "http%3A%2F%2Fexample.org%2Fcached", payload.first["uri"]
+ assert_equal 404, payload.first["http_response_code"]
+ end
+
+ test "invalid URI returns no_content" do
+ get "/websites/wring?uri=http://[invalid&format=json"
+
+ assert_response :no_content
+ end
+
+ test "fragment is ignored by default so repeated wrings with different fragments reuse one cache row" do
+ Distillator::FetchGuard.stubs(:check_url).returns(Distillator::FetchGuard::Result.new(allowed: true))
+ Distillator::FetchGuard.stubs(:check_response).returns(Distillator::FetchGuard::Result.new(allowed: true))
+ Distillator::NativeFetch.expects(:call).once.returns(native_fetch_result(body: "cached"))
+
+ get wring_websites_url, params: { uri: "http://example.org/page#one", format: "json" }
+ get wring_websites_url, params: { uri: "http://example.org/page#two", format: "json" }
+
+ keys = Distillator::FetchCache.order(:uri_key).pluck(:uri_key)
+ assert_equal [CGI.escape("http://example.org/page")], keys
+ end
+
+ test "include_fragment true creates separate cache rows for fragment variants" do
+ Distillator::FetchGuard.stubs(:check_url).returns(Distillator::FetchGuard::Result.new(allowed: true))
+ Distillator::FetchGuard.stubs(:check_response).returns(Distillator::FetchGuard::Result.new(allowed: true))
+ Distillator::NativeFetch.expects(:call).twice.returns(native_fetch_result(body: "cached"))
+
+ get wring_websites_url, params: { uri: "http://example.org/page#one", format: "json", include_fragment: "true" }
+ get wring_websites_url, params: { uri: "http://example.org/page#two", format: "json", include_fragment: "true" }
+
+ keys = Distillator::FetchCache.order(:uri_key).pluck(:uri_key)
+ assert_equal [
+ CGI.escape("http://example.org/page#one"),
+ CGI.escape("http://example.org/page#two")
+ ], keys
+ end
+
+ private
+
+ def expect_wring_fetch_param(key, expected_value, uri: "http://example.org/cached")
+ Distillator::FetchCacheStore.expects(:fetch).with do |kwargs|
+ assert_equal uri, kwargs[:uri]
+ assert_equal expected_value, kwargs[key]
+ true
+ end.returns(cache_like(html: "cached"))
+ end
+
+ def cache_like(
+ html: nil,
+ signals: {},
+ hints: [],
+ final_url: nil,
+ redirect_chain: [],
+ http_response_code: nil
+ )
+ Struct.new(
+ :html,
+ :signals,
+ :hints,
+ :final_url,
+ :redirect_chain,
+ :http_response_code,
+ keyword_init: true
+ ).new(
+ html: html,
+ signals: signals,
+ hints: hints,
+ final_url: final_url,
+ redirect_chain: redirect_chain,
+ http_response_code: http_response_code
+ )
+ end
+
+ def native_fetch_result(body:, status: :ok, http_code: 200, final_url: "http://example.org/page", redirect_chain: [])
+ {
+ status: status,
+ body: body,
+ headers: { "content_type" => "text/html" },
+ final_url: final_url,
+ redirect_chain: redirect_chain,
+ wringer: {},
+ http_code: http_code,
+ raw_body: body
+ }
+ end
+
+ def create_cache(
+ html: "cached",
+ name: "cached",
+ json_ld: nil,
+ signals: {},
+ hints: [],
+ final_url: nil,
+ redirect_chain: [],
+ http_response_code: 200
+ )
+ Distillator::FetchCache.create!(
+ uri_key: "http%3A%2F%2Fexample.org%2Fcached",
+ normalized_url: "http://example.org/cached",
+ html: html,
+ body: html,
+ name: name,
+ json_ld: json_ld,
+ scrape_date: Time.zone.now,
+ successful_refresh: Time.zone.now,
+ http_response_code: http_response_code,
+ headers: {},
+ signals: signals,
+ hints: hints,
+ final_url: final_url,
+ redirect_chain: redirect_chain
+ )
+ end
+end
diff --git a/test/docs/distillator_phase1_staging_plan_test.rb b/test/docs/distillator_phase1_staging_plan_test.rb
new file mode 100644
index 00000000..39b6b4b2
--- /dev/null
+++ b/test/docs/distillator_phase1_staging_plan_test.rb
@@ -0,0 +1,47 @@
+require "test_helper"
+
+class DistillatorPhase1StagingPlanTest < ActiveSupport::TestCase
+ PLAN_PATH = Rails.root.join("docs", "distillator_phase1_full_migration_staging_plan.md")
+ REPORT_PATH = Rails.root.join("docs", "distillator_phase1_staging_acceptance_report.md")
+
+ REQUIRED_ENDPOINTS = [
+ "GET /websites/wring?uri=
&format=raw",
+ "GET /websites/wring?uri=&format=json",
+ "GET /websites.json?term="
+ ].freeze
+
+ test "staging plan and acceptance report exist" do
+ assert File.exist?(PLAN_PATH), "Expected #{PLAN_PATH} to exist"
+ assert File.exist?(REPORT_PATH), "Expected #{REPORT_PATH} to exist"
+ end
+
+ test "staging plan references readiness document" do
+ plan = File.read(PLAN_PATH)
+
+ assert_includes plan, "distillator_phase1_wringer_readiness.md"
+ assert_includes plan, "Distillator Phase 1 Wringer Readiness"
+ end
+
+ test "staging plan references required endpoints" do
+ plan = File.read(PLAN_PATH)
+
+ REQUIRED_ENDPOINTS.each do |endpoint|
+ assert_includes plan, endpoint
+ end
+ end
+
+ test "staging plan includes rollback section" do
+ plan = File.read(PLAN_PATH)
+
+ assert_match(/^## Rollback$/, plan)
+ assert_includes plan, "Route traffic back to old Wringer"
+ assert_includes plan, "Unset any non-default fetch mode"
+ end
+
+ test "acceptance report includes decision field" do
+ report = File.read(REPORT_PATH)
+
+ assert_match(/^## Decision$/, report)
+ assert_includes report, "Decision: accept / reject / accept with follow-ups"
+ end
+end
diff --git a/test/docs/distillator_phase1_wringer_readiness_test.rb b/test/docs/distillator_phase1_wringer_readiness_test.rb
new file mode 100644
index 00000000..85b7865c
--- /dev/null
+++ b/test/docs/distillator_phase1_wringer_readiness_test.rb
@@ -0,0 +1,123 @@
+require "test_helper"
+
+class DistillatorPhase1WringerReadinessTest < ActiveSupport::TestCase
+ DOC_PATH = Rails.root.join("docs", "distillator_phase1_wringer_readiness.md")
+ ALLOWED_STATUSES = %w[legacy-only native shadow-only tested open].freeze
+
+ REQUIRED_BEHAVIORS = [
+ "GET /websites/wring route",
+ "raw response format",
+ "json response format",
+ "html redirect/default response",
+ "invalid params no_content behavior",
+ "/websites.json?term lookup",
+ "URI key generation",
+ "no-scheme URL handling",
+ "query preservation",
+ "fragment exclusion by default",
+ "fragment inclusion with include_fragment",
+ "CGI.escape uri_key storage",
+ "cache hit",
+ "force_scrape",
+ "force_scrape_every_hrs",
+ "successful 2xx cache update",
+ "failed 404 metadata update without overwriting last successful HTML",
+ "scrape_date",
+ "successful_refresh",
+ "http_response_code",
+ "headers",
+ "signals",
+ "hints",
+ "final_url",
+ "redirect_chain",
+ "absolute_src rewriting for src and href",
+ "json_post behavior",
+ "use_phantomjs behavior",
+ "PhantomJS missing-key fallback",
+ "iframe special case",
+ "ERB delimiter escaping",
+ "initial URL guard",
+ "redirect URL guard",
+ "SSL/Mechanize/Socket error metadata",
+ "materialized cache health summary",
+ "export invariance",
+ "legacy/internal fetch parity",
+ "default legacy mode",
+ "replay fixture compatibility"
+ ].freeze
+
+ test "readiness document exists" do
+ assert File.exist?(DOC_PATH), "Expected #{DOC_PATH} to exist"
+ end
+
+ test "all required behavior labels appear in the document" do
+ doc = File.read(DOC_PATH)
+
+ REQUIRED_BEHAVIORS.each do |behavior|
+ assert_includes doc, behavior
+ end
+ end
+
+ test "readiness table has one row per required behavior with all required columns" do
+ doc = File.read(DOC_PATH)
+ rows = table_rows(doc)
+
+ assert_equal REQUIRED_BEHAVIORS.sort, rows.map { |row| row.fetch("behavior") }.sort
+
+ rows.each do |row|
+ assert_includes ALLOWED_STATUSES, row.fetch("status")
+ assert row.fetch("code_path").present?, "#{row.fetch("behavior")} is missing code path"
+ assert row.fetch("test_path").present?, "#{row.fetch("behavior")} is missing test path"
+ assert row.fetch("migration_risk").present?, "#{row.fetch("behavior")} is missing migration risk"
+ assert row.fetch("cutover_requirement").present?, "#{row.fetch("behavior")} is missing cutover requirement"
+ end
+ end
+
+ test "no row is marked open unless listed in Open items" do
+ doc = File.read(DOC_PATH)
+ open_items = open_items_section(doc)
+
+ table_rows(doc).each do |row|
+ next unless row.fetch("status") == "open"
+
+ behavior = row.fetch("behavior")
+ assert_includes open_items, behavior, "#{behavior.inspect} is open but not listed in Open items"
+ end
+ end
+
+ test "referenced test files exist" do
+ doc = File.read(DOC_PATH)
+ referenced_tests = doc.scan(%r{test/[A-Za-z0-9_./-]+_test\.rb}).uniq
+
+ assert referenced_tests.any?, "Expected readiness doc to reference test files"
+ referenced_tests.each do |path|
+ assert File.exist?(Rails.root.join(path)), "Referenced test file does not exist: #{path}"
+ end
+ end
+
+ private
+
+ def table_rows(doc)
+ doc.lines.filter_map do |line|
+ next unless line.start_with?("| ")
+ next if line.include?("|---")
+ next if line.start_with?("| Required behavior")
+
+ cells = line.strip.split("|").map(&:strip).reject(&:empty?)
+ next unless cells.size == 6
+
+ {
+ "behavior" => cells[0],
+ "status" => cells[1],
+ "code_path" => cells[2],
+ "test_path" => cells[3],
+ "migration_risk" => cells[4],
+ "cutover_requirement" => cells[5]
+ }
+ end
+ end
+
+ def open_items_section(doc)
+ doc.split(/^## Open items\s*$/).last.to_s
+ end
+end
diff --git a/test/fixtures/files/distillator/failed_404.html b/test/fixtures/files/distillator/failed_404.html
new file mode 100644
index 00000000..b39d45ad
--- /dev/null
+++ b/test/fixtures/files/distillator/failed_404.html
@@ -0,0 +1,7 @@
+
+
+
+ 404 Fixture
+ This body is present only to represent a failed fetch fixture.
+
+
diff --git a/test/fixtures/files/distillator/json_post_payload.json b/test/fixtures/files/distillator/json_post_payload.json
new file mode 100644
index 00000000..0b78da5c
--- /dev/null
+++ b/test/fixtures/files/distillator/json_post_payload.json
@@ -0,0 +1,7 @@
+{
+ "query": "fixture-json-post",
+ "filters": {
+ "language": "en",
+ "mode": "legacy-only"
+ }
+}
diff --git a/test/fixtures/files/distillator/jsonld_script_event.html b/test/fixtures/files/distillator/jsonld_script_event.html
new file mode 100644
index 00000000..859062e2
--- /dev/null
+++ b/test/fixtures/files/distillator/jsonld_script_event.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+ JSON-LD Script Fixture Event
+
+
diff --git a/test/fixtures/files/distillator/redirected_event_final.html b/test/fixtures/files/distillator/redirected_event_final.html
new file mode 100644
index 00000000..d298e46b
--- /dev/null
+++ b/test/fixtures/files/distillator/redirected_event_final.html
@@ -0,0 +1,7 @@
+
+
+
+ Redirected Fixture Event
+ This file represents the final body after a redirect chain.
+
+
diff --git a/test/fixtures/files/distillator/relative_links.html b/test/fixtures/files/distillator/relative_links.html
new file mode 100644
index 00000000..d73234d8
--- /dev/null
+++ b/test/fixtures/files/distillator/relative_links.html
@@ -0,0 +1,7 @@
+
+
+
+
+ Tickets
+
+
diff --git a/test/fixtures/files/distillator/render_js_iframe.html b/test/fixtures/files/distillator/render_js_iframe.html
new file mode 100644
index 00000000..c8a42c23
--- /dev/null
+++ b/test/fixtures/files/distillator/render_js_iframe.html
@@ -0,0 +1,7 @@
+
+
+
+
+ Rendered content fixture placeholder
+
+
diff --git a/test/fixtures/files/distillator/simple_static_title.html b/test/fixtures/files/distillator/simple_static_title.html
new file mode 100644
index 00000000..d3094123
--- /dev/null
+++ b/test/fixtures/files/distillator/simple_static_title.html
@@ -0,0 +1,10 @@
+
+
+
+ Simple Static Title Event
+
+
+ Simple Static Title Event
+ Static fixture page for baseline HTML extraction.
+
+
diff --git a/test/fixtures/files/distillator/timezone_dates.html b/test/fixtures/files/distillator/timezone_dates.html
new file mode 100644
index 00000000..7bd630b8
--- /dev/null
+++ b/test/fixtures/files/distillator/timezone_dates.html
@@ -0,0 +1,8 @@
+
+
+
+ Timezone Dates Fixture
+ May 1, 2026 7:30 PM EDT
+ May 2, 2026 7:30 PM EDT
+
+
diff --git a/test/fixtures/files/distillator/xpath_extraction.html b/test/fixtures/files/distillator/xpath_extraction.html
new file mode 100644
index 00000000..a9d21570
--- /dev/null
+++ b/test/fixtures/files/distillator/xpath_extraction.html
@@ -0,0 +1,9 @@
+
+
+
+
+ XPath Fixture Event
+ Fixture page intended for XPath title extraction.
+
+
+
diff --git a/test/fixtures/files/distillator/xpath_sanitize.html b/test/fixtures/files/distillator/xpath_sanitize.html
new file mode 100644
index 00000000..44b80831
--- /dev/null
+++ b/test/fixtures/files/distillator/xpath_sanitize.html
@@ -0,0 +1,11 @@
+
+
+
+
+
XPath Sanitize Fixture
+
+
+
Sanitized text should keep this paragraph.
+
+
+
diff --git a/test/fixtures/files/distillator_dsl/event_full.html b/test/fixtures/files/distillator_dsl/event_full.html
new file mode 100644
index 00000000..83f56a5e
--- /dev/null
+++ b/test/fixtures/files/distillator_dsl/event_full.html
@@ -0,0 +1,21 @@
+
+
+
+ Distillator Full Event
+
+
+
+
+
+ Distillator Full Event
+
+ A fixture-backed full event used to prove stable Artsdata export.
+
+
+
+
+
+ Tickets
+ 15.00
+
+
diff --git a/test/fixtures/files/distillator_dsl/json_ld_event.html b/test/fixtures/files/distillator_dsl/json_ld_event.html
new file mode 100644
index 00000000..261c70ce
--- /dev/null
+++ b/test/fixtures/files/distillator_dsl/json_ld_event.html
@@ -0,0 +1,15 @@
+
+
+
+ JSON-LD Fixture
+
+
+
+ Fixture Event
+
+
diff --git a/test/fixtures/files/distillator_dsl/price_block.html b/test/fixtures/files/distillator_dsl/price_block.html
new file mode 100644
index 00000000..eee2e36e
--- /dev/null
+++ b/test/fixtures/files/distillator_dsl/price_block.html
@@ -0,0 +1,14 @@
+
+
+
+ Price Block Fixture
+
+
+
+ Opening
+ night
+ gala
+
+ $15.00
+
+
diff --git a/test/fixtures/files/distillator_dsl/resource_list.html b/test/fixtures/files/distillator_dsl/resource_list.html
new file mode 100644
index 00000000..372fb35b
--- /dev/null
+++ b/test/fixtures/files/distillator_dsl/resource_list.html
@@ -0,0 +1,12 @@
+
+
+
+ Resource List Fixture
+
+
+
+
+
diff --git a/test/fixtures/files/distillator_dsl/simple_title.html b/test/fixtures/files/distillator_dsl/simple_title.html
new file mode 100644
index 00000000..23d06c8b
--- /dev/null
+++ b/test/fixtures/files/distillator_dsl/simple_title.html
@@ -0,0 +1,10 @@
+
+
+
+ Simple Fixture Title
+
+
+
+ Simple Fixture Title
+
+
diff --git a/test/fixtures/files/distillator_export/README.md b/test/fixtures/files/distillator_export/README.md
new file mode 100644
index 00000000..da4bb08e
--- /dev/null
+++ b/test/fixtures/files/distillator_export/README.md
@@ -0,0 +1,8 @@
+This directory contains golden export fixtures for the Distillator fixture pack.
+
+Files:
+- `expected_distillator_fixture_pack.jsonld`: canonical JSON-LD export snapshot.
+- `expected_distillator_fixture_pack.nq`: canonical N-Quads export snapshot.
+
+The snapshots are intended to represent graph-equivalent Artsdata export output
+for the `distillator_fixture_pack` website fixture.
diff --git a/test/fixtures/files/distillator_export/expected_distillator_fixture_pack.jsonld b/test/fixtures/files/distillator_export/expected_distillator_fixture_pack.jsonld
new file mode 100644
index 00000000..2f7c4069
--- /dev/null
+++ b/test/fixtures/files/distillator_export/expected_distillator_fixture_pack.jsonld
@@ -0,0 +1,86 @@
+[
+ {
+ "@id": "http://kg.footlight.io/resource/distillator-full-event",
+ "@type": [
+ "http://schema.org/Event"
+ ],
+ "MyString": [
+ {
+ "@language": "en",
+ "@value": "Distillator Full Event"
+ }
+ ],
+ "http://schema.org/description": [
+ {
+ "@language": "en",
+ "@value": "A fixture-backed full event used to prove stable Artsdata export."
+ }
+ ],
+ "http://schema.org/url": [
+ {
+ "@value": "https://fixtures.example/distillator/event-full"
+ }
+ ],
+ "http://schema.org/startDate": [
+ {
+ "@type": "http://www.w3.org/2001/XMLSchema#dateTime",
+ "@value": "2026-05-10T19:30:00-04:00"
+ }
+ ],
+ "http://schema.org/endDate": [
+ {
+ "@type": "http://www.w3.org/2001/XMLSchema#dateTime",
+ "@value": "2026-05-10T21:15:00-04:00"
+ }
+ ],
+ "http://schema.org/photo": [
+ {
+ "@value": "https://fixtures.example/distillator/assets/poster.jpg"
+ }
+ ],
+ "http://schema.org/location": [
+ {
+ "@id": "http://kg.footlight.io/resource/distillator-main-hall"
+ }
+ ],
+ "http://schema.org/eventStatus": [
+ {
+ "@id": "http://schema.org/EventScheduled"
+ }
+ ],
+ "http://schema.org/eventAttendanceMode": [
+ {
+ "@id": "http://schema.org/OnlineEventAttendanceMode"
+ }
+ ],
+ "http://schema.org/additionalType": [
+ {
+ "@id": "http://schema.org/MusicEvent"
+ },
+ {
+ "@id": "http://ontology.artsdata.ca/Performance"
+ }
+ ],
+ "http://schema.org/offers": [
+ {
+ "@id": "http://kg.footlight.io/resource/distillator-full-event#Offer_0"
+ }
+ ]
+ },
+ {
+ "@id": "http://kg.footlight.io/resource/distillator-full-event#Offer_0",
+ "http://schema.org/url": [
+ {
+ "@value": "https://tickets.example.org/distillator-full-event"
+ }
+ ],
+ "@type": [
+ "http://schema.org/Offer"
+ ],
+ "http://schema.org/price": [
+ {
+ "@value": "15.00"
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/test/fixtures/files/distillator_export/expected_distillator_fixture_pack.nq b/test/fixtures/files/distillator_export/expected_distillator_fixture_pack.nq
new file mode 100644
index 00000000..7859d986
--- /dev/null
+++ b/test/fixtures/files/distillator_export/expected_distillator_fixture_pack.nq
@@ -0,0 +1,15 @@
+ "15.00" .
+ "https://tickets.example.org/distillator-full-event" .
+ .
+ .
+ .
+ "A fixture-backed full event used to prove stable Artsdata export."@en .
+ "2026-05-10T21:15:00-04:00"^^ .
+ .
+ .
+ .
+ .
+ "https://fixtures.example/distillator/assets/poster.jpg" .
+ "2026-05-10T19:30:00-04:00"^^ .
+ "https://fixtures.example/distillator/event-full" .
+ .
\ No newline at end of file
diff --git a/test/fixtures/files/distillator_export/phase1_golden.jsonld b/test/fixtures/files/distillator_export/phase1_golden.jsonld
new file mode 100644
index 00000000..8aa46069
--- /dev/null
+++ b/test/fixtures/files/distillator_export/phase1_golden.jsonld
@@ -0,0 +1,101 @@
+[
+ {
+ "@id": "http://kg.footlight.io/resource/phase1-jsonld-event",
+ "@type": [
+ "http://schema.org/Event"
+ ],
+ "http://schema.org/location": [
+ {
+ "@id": "http://kg.footlight.io/resource/phase1-jsonld-event#VirtualLocation"
+ }
+ ],
+ "http://schema.org/name": [
+ {
+ "@value": "JSON-LD Fixture Event"
+ }
+ ],
+ "http://schema.org/startDate": [
+ {
+ "@type": "http://www.w3.org/2001/XMLSchema#dateTime",
+ "@value": "2026-05-02T20:00:00Z"
+ }
+ ]
+ },
+ {
+ "@id": "http://kg.footlight.io/resource/phase1-jsonld-event#VirtualLocation",
+ "@type": [
+ "http://schema.org/VirtualLocation"
+ ],
+ "http://schema.org/url": [
+ {
+ "@value": "https://phase1.example/jsonld-event"
+ }
+ ]
+ },
+ {
+ "@id": "http://kg.footlight.io/resource/phase1-redirect-event",
+ "@type": [
+ "http://schema.org/Event"
+ ],
+ "http://schema.org/location": [
+ {
+ "@id": "http://kg.footlight.io/resource/phase1-redirect-event#VirtualLocation"
+ }
+ ],
+ "http://schema.org/name": [
+ {
+ "@value": "Redirect Fixture Event"
+ }
+ ],
+ "http://schema.org/startDate": [
+ {
+ "@type": "http://www.w3.org/2001/XMLSchema#dateTime",
+ "@value": "2026-05-03T20:00:00Z"
+ }
+ ]
+ },
+ {
+ "@id": "http://kg.footlight.io/resource/phase1-redirect-event#VirtualLocation",
+ "@type": [
+ "http://schema.org/VirtualLocation"
+ ],
+ "http://schema.org/url": [
+ {
+ "@value": "https://phase1.example/final-event"
+ }
+ ]
+ },
+ {
+ "@id": "http://kg.footlight.io/resource/phase1-simple-event",
+ "@type": [
+ "http://schema.org/Event"
+ ],
+ "http://schema.org/location": [
+ {
+ "@id": "http://kg.footlight.io/resource/phase1-simple-event#VirtualLocation"
+ }
+ ],
+ "http://schema.org/name": [
+ {
+ "@value": "Simple Fixture Event"
+ }
+ ],
+ "http://schema.org/startDate": [
+ {
+ "@type": "http://www.w3.org/2001/XMLSchema#dateTime",
+ "@value": "2026-05-01T20:00:00Z"
+ }
+ ]
+ },
+ {
+ "@id": "http://kg.footlight.io/resource/phase1-simple-event#VirtualLocation",
+ "@type": [
+ "http://schema.org/VirtualLocation"
+ ],
+ "http://schema.org/url": [
+ {
+ "@value": "https://phase1.example/simple-event"
+ }
+ ]
+ }
+]
diff --git a/test/fixtures/files/distillator_shadow_logs/sample.log b/test/fixtures/files/distillator_shadow_logs/sample.log
new file mode 100644
index 00000000..3bc868e0
--- /dev/null
+++ b/test/fixtures/files/distillator_shadow_logs/sample.log
@@ -0,0 +1,7 @@
+{"event":"distillator.fetch_shadow.compare","url":"https://example.com/a","matched":true,"mismatches":[]}
+{"event":"distillator.fetch_shadow.compare","url":"https://example.com/b","matched":false,"mismatches":[{"field":"body_hash","legacy":"abc123","internal":"def456"},{"field":"final_url","legacy":"https://example.com/b","internal":"https://example.com/final"}]}
+{:event=>"distillator.fetch_shadow.error", :url=>"https://example.com/c", :error_class=>"RuntimeError", :error=>"boom"}
+{:event=>"distillator.fetch_mode.internal_ineligible", :url=>"https://example.com/d", :render_js=>true, :json_post=>false}
+{:event=>"distillator.fetch_shadow.compare", :url=>"https://example.com/e", :matched=>false, :mismatches=>[{:field=>:body_hash}, {:field=>"final_url"}]}
+{:event=>"distillator.fetch_shadow.compare", :url=>"https://example.com/f", :matched=>true, :mismatches=>[]}
+{:event=>"distillator.fetch_shadow.compare", :url=>"https://example.com/g", :matched=>false, :mismatches=>[{:field=>:wringer_error_type, :internal=>"DistillatorFetchBlocked"}]}
diff --git a/test/fixtures/properties.yml b/test/fixtures/properties.yml
index df698636..d24e35ff 100644
--- a/test/fixtures/properties.yml
+++ b/test/fixtures/properties.yml
@@ -98,6 +98,31 @@ AttendanceMode:
rdfs_class: one
expected_class: EventAttendanceModeEnumeration
+eventUrl:
+ label: URL
+ value_datatype:
+ uri: http://schema.org/url
+ rdfs_class: one
+
+eventEndDate:
+ label: EndDate
+ value_datatype: xsd:dateTime
+ uri: http://schema.org/endDate
+ rdfs_class: one
+
+location:
+ label: Location
+ value_datatype: xsd:anyURI
+ uri: http://schema.org/location
+ rdfs_class: one
+ expected_class: Place, VirtualLocation
+
+offerUrl:
+ label: URL
+ value_datatype:
+ uri: http://schema.org/url
+ rdfs_class: offer
+
personName:
label: Name
value_datatype:
@@ -126,4 +151,4 @@ ones:
label: Mystring
value_datatype: MyString
uri: MyString
- rdfs_class: one
\ No newline at end of file
+ rdfs_class: one
diff --git a/test/fixtures/sources.yml b/test/fixtures/sources.yml
index 40111096..db132bdd 100644
--- a/test/fixtures/sources.yml
+++ b/test/fixtures/sources.yml
@@ -131,6 +131,192 @@ onesource:
render_js: false
property: ones
website: one
-
-
-
\ No newline at end of file
+
+distillator_title_source:
+ algorithm_value: fixture:title
+ selected: true
+ selected_by: Distillator
+ language: en
+ render_js: false
+ property: four
+ website: distillator_fixture_pack
+
+distillator_description_source:
+ algorithm_value: fixture:description
+ selected: true
+ selected_by: Distillator
+ language: en
+ render_js: false
+ property: five
+ website: distillator_fixture_pack
+
+distillator_dates_source:
+ algorithm_value: fixture:dates
+ selected: true
+ selected_by: Distillator
+ language: en
+ render_js: false
+ property: ten
+ website: distillator_fixture_pack
+
+distillator_render_js_source:
+ algorithm_value: fixture:render_js
+ selected: true
+ selected_by: Distillator
+ language: en
+ render_js: true
+ property: four
+ website: distillator_fixture_pack
+
+culture3r_refresh_source:
+ algorithm_value: xpath=//h1/text()
+ selected: true
+ selected_by: Distillator
+ language: en
+ render_js: false
+ property: four
+ website: culture3r_refresh_fixture
+
+distillator_event_name_source:
+ algorithm_value: xpath=//h1/text()
+ selected: true
+ selected_by: Distillator
+ language: en
+ render_js: false
+ property: four
+ website: distillator_fixture_pack
+
+distillator_event_description_source:
+ algorithm_value: xpath_sanitize=//div[@class='description'];ruby=$array.map { |str| str.squish }
+ selected: true
+ selected_by: Distillator
+ language: en
+ render_js: false
+ property: five
+ website: distillator_fixture_pack
+
+distillator_event_url_source:
+ algorithm_value: ruby=[$url]
+ selected: true
+ selected_by: Distillator
+ language:
+ render_js: false
+ property: eventUrl
+ website: distillator_fixture_pack
+
+distillator_event_start_date_source:
+ algorithm_value: xpath=//time[@itemprop='startDate']/@datetime
+ selected: true
+ selected_by: Distillator
+ language:
+ render_js: false
+ property: ten
+ website: distillator_fixture_pack
+
+distillator_event_end_date_source:
+ algorithm_value: xpath=//time[@itemprop='endDate']/@datetime
+ selected: true
+ selected_by: Distillator
+ language:
+ render_js: false
+ property: eventEndDate
+ website: distillator_fixture_pack
+
+distillator_event_image_source:
+ algorithm_value: xpath=//img[@class='hero']/@src
+ selected: true
+ selected_by: Distillator
+ language:
+ render_js: false
+ property: eleven
+ website: distillator_fixture_pack
+
+distillator_event_location_source:
+ algorithm_value: xpath=//div[@data-venue]/@data-venue
+ selected: true
+ selected_by: Distillator
+ language:
+ render_js: false
+ property: location
+ website: distillator_fixture_pack
+
+distillator_event_offer_url_source:
+ algorithm_value: xpath=//a[@class='offer-link']/@href
+ selected: true
+ selected_by: Distillator
+ language:
+ render_js: false
+ property: offerUrl
+ website: distillator_fixture_pack
+
+distillator_event_price_source:
+ algorithm_value: xpath=//span[@class='price']/text()
+ selected: true
+ selected_by: Distillator
+ language:
+ render_js: false
+ property: price
+ website: distillator_fixture_pack
+
+distillator_event_status_source:
+ algorithm_value: xpath=//meta[@property='eventStatus']/@content
+ selected: true
+ selected_by: Distillator
+ language:
+ render_js: false
+ property: twelve
+ website: distillator_fixture_pack
+
+distillator_event_attendance_mode_source:
+ algorithm_value: xpath=//meta[@property='eventAttendanceMode']/@content
+ selected: true
+ selected_by: Distillator
+ language:
+ render_js: false
+ property: AttendanceMode
+ website: distillator_fixture_pack
+
+distillator_event_type_source:
+ algorithm_value: xpath=//meta[@property='additionalType']/@content
+ selected: true
+ selected_by: Distillator
+ language:
+ render_js: false
+ property: additionalType
+ website: distillator_fixture_pack
+
+distillator_place_name_source:
+ algorithm_value: xpath=//h2[@class='venue-name']/text()
+ selected: true
+ selected_by: Distillator
+ language:
+ render_js: false
+ property: two
+ website: distillator_fixture_pack
+
+resource_list_rdf_class_source:
+ algorithm_value: manual=Event
+ selected: true
+ selected_by: Distillator
+ language: en
+ render_js: false
+ property: one
+ website: two
+
+resource_list_uri_list_source:
+ algorithm_value: manual=[]
+ selected: true
+ selected_by: Distillator
+ language: en
+ render_js: false
+ property: three
+ website: two
+
+resource_list_url_list_source:
+ algorithm_value: manual=[]
+ selected: true
+ selected_by: Distillator
+ language: en
+ render_js: false
+ property: four
+ website: two
diff --git a/test/fixtures/statements.yml b/test/fixtures/statements.yml
index 9993e6e7..8cdbf5cd 100644
--- a/test/fixtures/statements.yml
+++ b/test/fixtures/statements.yml
@@ -147,3 +147,223 @@ prices:
cache_changed: 2018-07-30 16:46:00 UTC
source: priceSource
webpage: eventPage2
+
+distillator_simple_title_statement:
+ cache: Simple Static Title Event
+ status: ok
+ status_origin: DistillatorFixture
+ cache_refreshed: 2026-04-27 16:46:00 UTC
+ cache_changed: 2026-04-27 16:46:00 UTC
+ source: distillator_title_source
+ webpage: distillator_simple_title
+ selected_individual: true
+
+distillator_xpath_statement:
+ cache: XPath Fixture Event
+ status: ok
+ status_origin: DistillatorFixture
+ cache_refreshed: 2026-04-27 16:46:00 UTC
+ cache_changed: 2026-04-27 16:46:00 UTC
+ source: distillator_title_source
+ webpage: distillator_xpath
+ selected_individual: true
+
+distillator_jsonld_script_statement:
+ cache: JSON-LD Script Fixture Event
+ status: ok
+ status_origin: DistillatorFixture
+ cache_refreshed: 2026-04-27 16:46:00 UTC
+ cache_changed: 2026-04-27 16:46:00 UTC
+ source: distillator_title_source
+ webpage: distillator_jsonld_script
+ selected_individual: true
+
+distillator_redirected_statement:
+ cache: Redirected Fixture Event
+ status: updated
+ status_origin: DistillatorFixture
+ cache_refreshed: 2026-04-27 16:46:00 UTC
+ cache_changed: 2026-04-27 16:46:00 UTC
+ source: distillator_title_source
+ webpage: distillator_redirected
+ selected_individual: true
+
+distillator_timezone_dates_statement:
+ cache: "[\"2026-05-01T19:30:00-04:00\"]"
+ status: updated
+ status_origin: DistillatorFixture
+ cache_refreshed: 2026-04-27 16:46:00 UTC
+ cache_changed: 2026-04-27 16:46:00 UTC
+ source: distillator_dates_source
+ webpage: distillator_timezone_dates
+ selected_individual: true
+
+distillator_event_name_statement:
+ cache: Distillator Full Event
+ status: ok
+ status_origin: DistillatorFixture
+ cache_refreshed: 2026-04-27 16:46:00 UTC
+ cache_changed: 2026-04-27 16:46:00 UTC
+ source: distillator_event_name_source
+ webpage: distillator_full_event
+ selected_individual: true
+
+distillator_event_description_statement:
+ cache: A fixture-backed full event used to prove stable Artsdata export.
+ status: ok
+ status_origin: DistillatorFixture
+ cache_refreshed: 2026-04-27 16:46:00 UTC
+ cache_changed: 2026-04-27 16:46:00 UTC
+ source: distillator_event_description_source
+ webpage: distillator_full_event
+ selected_individual: true
+
+distillator_event_url_statement:
+ cache: https://fixtures.example/distillator/event-full
+ status: ok
+ status_origin: DistillatorFixture
+ cache_refreshed: 2026-04-27 16:46:00 UTC
+ cache_changed: 2026-04-27 16:46:00 UTC
+ source: distillator_event_url_source
+ webpage: distillator_full_event
+ selected_individual: true
+
+distillator_event_start_date_statement:
+ cache: "[\"2026-05-10T19:30:00-04:00\"]"
+ status: ok
+ status_origin: DistillatorFixture
+ cache_refreshed: 2026-04-27 16:46:00 UTC
+ cache_changed: 2026-04-27 16:46:00 UTC
+ source: distillator_event_start_date_source
+ webpage: distillator_full_event
+ selected_individual: true
+
+distillator_event_end_date_statement:
+ cache: "[\"2026-05-10T21:15:00-04:00\"]"
+ status: ok
+ status_origin: DistillatorFixture
+ cache_refreshed: 2026-04-27 16:46:00 UTC
+ cache_changed: 2026-04-27 16:46:00 UTC
+ source: distillator_event_end_date_source
+ webpage: distillator_full_event
+ selected_individual: true
+
+distillator_event_image_statement:
+ cache: https://fixtures.example/distillator/assets/poster.jpg
+ status: ok
+ status_origin: DistillatorFixture
+ cache_refreshed: 2026-04-27 16:46:00 UTC
+ cache_changed: 2026-04-27 16:46:00 UTC
+ source: distillator_event_image_source
+ webpage: distillator_full_event
+ selected_individual: true
+
+distillator_event_location_statement:
+ cache: "[\"Main Hall\",\"Place\",[\"Main Hall\",\"footlight:distillator-main-hall\"]]"
+ status: ok
+ status_origin: DistillatorFixture
+ cache_refreshed: 2026-04-27 16:46:00 UTC
+ cache_changed: 2026-04-27 16:46:00 UTC
+ source: distillator_event_location_source
+ webpage: distillator_full_event
+ selected_individual: true
+
+distillator_event_offer_url_statement:
+ cache: https://tickets.example.org/distillator-full-event
+ status: ok
+ status_origin: DistillatorFixture
+ cache_refreshed: 2026-04-27 16:46:00 UTC
+ cache_changed: 2026-04-27 16:46:00 UTC
+ source: distillator_event_offer_url_source
+ webpage: distillator_full_event
+ selected_individual: true
+
+distillator_event_price_statement:
+ cache: "[\"15.00\"]"
+ status: ok
+ status_origin: DistillatorFixture
+ cache_refreshed: 2026-04-27 16:46:00 UTC
+ cache_changed: 2026-04-27 16:46:00 UTC
+ source: distillator_event_price_source
+ webpage: distillator_full_event
+ selected_individual: true
+
+distillator_event_status_statement:
+ cache: "[\"Scheduled\",\"EventStatusType\",[\"EventScheduled\",\"http://schema.org/EventScheduled\"]]"
+ status: ok
+ status_origin: DistillatorFixture
+ cache_refreshed: 2026-04-27 16:46:00 UTC
+ cache_changed: 2026-04-27 16:46:00 UTC
+ source: distillator_event_status_source
+ webpage: distillator_full_event
+ selected_individual: true
+
+distillator_event_attendance_mode_statement:
+ cache: "[\"OnlineEventAttendanceMode\",\"EventAttendanceModeEnumeration\",[\"Online\",\"http://schema.org/OnlineEventAttendanceMode\"]]"
+ status: ok
+ status_origin: DistillatorFixture
+ cache_refreshed: 2026-04-27 16:46:00 UTC
+ cache_changed: 2026-04-27 16:46:00 UTC
+ source: distillator_event_attendance_mode_source
+ webpage: distillator_full_event
+ selected_individual: true
+
+distillator_event_type_statement:
+ cache: "[\"Music Performance\",\"EventTypeEnumeration\",[\"MusicEvent\",\"http://schema.org/MusicEvent\"],[\"Performance\",\"http://ontology.artsdata.ca/Performance\"]]"
+ status: ok
+ status_origin: DistillatorFixture
+ cache_refreshed: 2026-04-27 16:46:00 UTC
+ cache_changed: 2026-04-27 16:46:00 UTC
+ source: distillator_event_type_source
+ webpage: distillator_full_event
+ selected_individual: true
+
+distillator_place_name_statement:
+ cache: Main Hall
+ status: ok
+ status_origin: DistillatorFixture
+ cache_refreshed: 2026-04-27 16:46:00 UTC
+ cache_changed: 2026-04-27 16:46:00 UTC
+ source: distillator_place_name_source
+ webpage: distillator_main_hall
+ selected_individual: true
+
+culture3r_refresh_statement:
+ cache: Old Fixture Title
+ status: initial
+ status_origin: DistillatorFixture
+ cache_refreshed: 2026-04-20 16:46:00 UTC
+ cache_changed: 2026-04-20 16:46:00 UTC
+ source: culture3r_refresh_source
+ webpage: culture3r_refresh_fixture
+ selected_individual: true
+
+resource_list_rdf_class_statement:
+ cache: Event
+ status: ok
+ status_origin: DistillatorFixture
+ cache_refreshed: 2026-04-27 16:46:00 UTC
+ cache_changed: 2026-04-27 16:46:00 UTC
+ source: resource_list_rdf_class_source
+ webpage: resource_list_page
+ selected_individual: true
+
+resource_list_uri_list_statement:
+ cache: '["adr:event-1","adr:event-2"]'
+ status: ok
+ status_origin: DistillatorFixture
+ cache_refreshed: 2026-04-27 16:46:00 UTC
+ cache_changed: 2026-04-27 16:46:00 UTC
+ source: resource_list_uri_list_source
+ webpage: resource_list_page
+ selected_individual: true
+
+resource_list_url_list_statement:
+ cache: '["https://example.org/events/1","https://example.org/events/2"]'
+ status: ok
+ status_origin: DistillatorFixture
+ cache_refreshed: 2026-04-27 16:46:00 UTC
+ cache_changed: 2026-04-27 16:46:00 UTC
+ source: resource_list_url_list_source
+ webpage: resource_list_page
+ selected_individual: true
diff --git a/test/fixtures/webpages.yml b/test/fixtures/webpages.yml
index 6db225bb..567e8982 100644
--- a/test/fixtures/webpages.yml
+++ b/test/fixtures/webpages.yml
@@ -100,4 +100,113 @@ eventPage2:
rdf_uri: uri1
rdfs_class: one
website: two
- archive_date: 2020-01-01 16:46:00 UTC
\ No newline at end of file
+ archive_date: 2020-01-01 16:46:00 UTC
+
+distillator_simple_title:
+ url: https://fixtures.example/distillator/simple-static-title
+ language: en
+ rdf_uri: adr:distillator-simple-title
+ rdfs_class: one
+ website: distillator_fixture_pack
+ archive_date: 2026-06-01 16:46:00 UTC
+
+distillator_xpath:
+ url: https://fixtures.example/distillator/xpath-extraction
+ language: en
+ rdf_uri: adr:distillator-xpath
+ rdfs_class: one
+ website: distillator_fixture_pack
+ archive_date: 2026-06-01 16:46:00 UTC
+
+distillator_xpath_sanitize:
+ url: https://fixtures.example/distillator/xpath-sanitize
+ language: en
+ rdf_uri: adr:distillator-xpath-sanitize
+ rdfs_class: one
+ website: distillator_fixture_pack
+ archive_date: 2026-06-01 16:46:00 UTC
+
+distillator_jsonld_script:
+ url: https://fixtures.example/distillator/jsonld-script
+ language: en
+ rdf_uri: adr:distillator-jsonld-script
+ rdfs_class: one
+ website: distillator_fixture_pack
+ archive_date: 2026-06-01 16:46:00 UTC
+
+distillator_relative_links:
+ url: https://fixtures.example/distillator/relative-links
+ language: en
+ rdf_uri: adr:distillator-relative-links
+ rdfs_class: one
+ website: distillator_fixture_pack
+ archive_date: 2026-06-01 16:46:00 UTC
+
+distillator_redirected:
+ url: https://fixtures.example/distillator/redirected
+ language: en
+ rdf_uri: adr:distillator-redirected
+ rdfs_class: one
+ website: distillator_fixture_pack
+ archive_date: 2026-06-01 16:46:00 UTC
+
+distillator_failed_fetch:
+ url: https://fixtures.example/distillator/failed-404
+ language: en
+ rdf_uri: adr:distillator-failed-fetch
+ rdfs_class: one
+ website: distillator_fixture_pack
+ archive_date: 2026-06-01 16:46:00 UTC
+
+distillator_json_post:
+ url: https://fixtures.example/distillator/json-post
+ language: en
+ rdf_uri: adr:distillator-json-post
+ rdfs_class: one
+ website: distillator_fixture_pack
+ archive_date: 2026-06-01 16:46:00 UTC
+
+distillator_render_js:
+ url: https://fixtures.example/distillator/render-js
+ language: en
+ rdf_uri: adr:distillator-render-js
+ rdfs_class: one
+ website: distillator_fixture_pack
+ archive_date: 2026-06-01 16:46:00 UTC
+
+distillator_timezone_dates:
+ url: https://fixtures.example/distillator/timezone-dates
+ language: en
+ rdf_uri: adr:distillator-timezone-dates
+ rdfs_class: one
+ website: distillator_fixture_pack
+ archive_date: 2026-06-01 16:46:00 UTC
+
+distillator_full_event:
+ url: https://fixtures.example/distillator/event-full
+ language: en
+ rdf_uri: footlight:distillator-full-event
+ rdfs_class: one
+ website: distillator_fixture_pack
+ archive_date: 2026-06-01 16:46:00 UTC
+
+distillator_main_hall:
+ url: https://fixtures.example/distillator/venues/main-hall
+ language: en
+ rdf_uri: footlight:distillator-main-hall
+ rdfs_class: place
+ website: distillator_fixture_pack
+
+culture3r_refresh_fixture:
+ url: https://fixtures.example/culture3r/gabrielle-caron-rodage
+ language: en
+ rdf_uri: footlight:culture3r-com_gabrielle-caron-rodage
+ rdfs_class: one
+ website: culture3r_refresh_fixture
+
+resource_list_page:
+ url: https://fixtures.example/resource-list
+ language: en
+ rdf_uri: adr:resource-list
+ rdfs_class: one
+ website: two
diff --git a/test/fixtures/websites.yml b/test/fixtures/websites.yml
index a891d44b..3d9a95b1 100644
--- a/test/fixtures/websites.yml
+++ b/test/fixtures/websites.yml
@@ -27,4 +27,22 @@ four:
musiconmain:
name: test musiconmain-ca
- seedurl: musiconmain-ca
\ No newline at end of file
+ seedurl: musiconmain-ca
+
+distillator_fixture_pack:
+ name: Distillator migration fixture pack
+ seedurl: distillator-fixture-pack
+ graph_name: https://fixtures.example/distillator
+ default_language: en
+ last_refresh: <%= Time.now - 1.day %>
+ schedule_time: <%= Time.now - 5.minutes %>
+ schedule_every_days: 1
+
+culture3r_refresh_fixture:
+ name: culture3r refresh fixture
+ seedurl: culture3r-com
+ graph_name: https://fixtures.example/culture3r
+ default_language: en
+ last_refresh: <%= Time.now - 2.days %>
+ schedule_time: <%= Time.now - 5.minutes %>
+ schedule_every_days: 1
diff --git a/test/helpers/admin_table_helper_test.rb b/test/helpers/admin_table_helper_test.rb
new file mode 100644
index 00000000..af65f2f7
--- /dev/null
+++ b/test/helpers/admin_table_helper_test.rb
@@ -0,0 +1,18 @@
+require "test_helper"
+
+class AdminTableHelperTest < ActionView::TestCase
+ tests AdminTableHelper
+
+ test "admin_sortable preserves filters and toggles direction" do
+ @request.path_parameters = { controller: "distillator/cache", action: "index" }
+ params[:sort] = "name"
+ params[:direction] = "asc"
+
+ html = admin_sortable("name", "Name", filters: { term: "needle" })
+
+ assert_includes html, "term=needle"
+ assert_includes html, "sort=name"
+ assert_includes html, "direction=desc"
+ assert_includes html, "Name ↑"
+ end
+end
diff --git a/test/helpers/application_helper_test.rb b/test/helpers/application_helper_test.rb
new file mode 100644
index 00000000..7e5a733f
--- /dev/null
+++ b/test/helpers/application_helper_test.rb
@@ -0,0 +1,107 @@
+require "test_helper"
+
+class ApplicationHelperTest < ActionView::TestCase
+ setup do
+ @distillator_config = Rails.application.config.x.distillator
+ @old_compatibility_base_url = @distillator_config.compatibility_base_url
+ @old_legacy_wringer_base_url = @distillator_config.legacy_wringer_base_url
+ @old_allow_localhost = @distillator_config.allow_localhost_compatibility
+ end
+
+ teardown do
+ @distillator_config.compatibility_base_url = @old_compatibility_base_url
+ @distillator_config.legacy_wringer_base_url = @old_legacy_wringer_base_url
+ @distillator_config.allow_localhost_compatibility = @old_allow_localhost
+ end
+
+ test "current wringer status text shows missing staging config explicitly" do
+ Rails.stubs(:env).returns(ActiveSupport::StringInquirer.new("staging"))
+ @distillator_config.compatibility_base_url = nil
+ @distillator_config.legacy_wringer_base_url = nil
+ @distillator_config.allow_localhost_compatibility = false
+ ENV.delete("DISTILLATOR_COMPAT_BASE_URL")
+ ENV.delete("DISTILLATOR_COMPATIBILITY_BASE_URL")
+ Distillator::TransitionEvidence.stubs(:latest_legacy_lookup_error).returns(nil)
+
+ endpoint = Distillator::WringerEndpoint.current
+
+ assert_equal :missing_config, endpoint.state
+ assert_equal "Current Wringer: Missing staging config - comparisons disabled", current_wringer_status_text
+ end
+
+ test "current wringer status text shows unreachable endpoint after latest lookup failure" do
+ record = stub(
+ details: {
+ "legacy_lookup_error" => "Connection refused",
+ "legacy_lookup_status" => "unreachable",
+ "reason" => "legacy_lookup_unreachable"
+ }
+ )
+ Rails.stubs(:env).returns(ActiveSupport::StringInquirer.new("staging"))
+ @distillator_config.compatibility_base_url = "https://user:secret@compat.example/token"
+ @distillator_config.legacy_wringer_base_url = "https://legacy.example"
+ @distillator_config.allow_localhost_compatibility = false
+
+ Distillator::TransitionEvidence.stubs(:latest_legacy_lookup_error).returns(record)
+
+ assert_equal "Current Wringer: Unreachable - last lookup failed", current_wringer_status_text
+ end
+
+ test "current wringer status text sanitizes configured remote endpoint" do
+ Rails.stubs(:env).returns(ActiveSupport::StringInquirer.new("staging"))
+ @distillator_config.compatibility_base_url = "https://user:secret@compat.example/token"
+ @distillator_config.legacy_wringer_base_url = "https://legacy.example"
+ @distillator_config.allow_localhost_compatibility = false
+ Distillator::TransitionEvidence.stubs(:latest_legacy_lookup_error).returns(nil)
+
+ assert_equal "Current Wringer: Remote configured - https://compat.example via config.x.distillator.compatibility_base_url", current_wringer_status_text
+ end
+
+ test "current wringer status text uses DISTILLATOR_COMPATIBILITY_BASE_URL when config is nil" do
+ Rails.stubs(:env).returns(ActiveSupport::StringInquirer.new("staging"))
+ @distillator_config.compatibility_base_url = nil
+ @distillator_config.legacy_wringer_base_url = nil
+ @distillator_config.allow_localhost_compatibility = false
+ old_compatibility_alias = ENV["DISTILLATOR_COMPATIBILITY_BASE_URL"]
+ ENV["DISTILLATOR_COMPATIBILITY_BASE_URL"] = "https://footlight-wringer.herokuapp.com"
+ Distillator::TransitionEvidence.stubs(:latest_legacy_lookup_error).returns(nil)
+
+ assert_equal "Current Wringer: Remote configured - https://footlight-wringer.herokuapp.com via DISTILLATOR_COMPATIBILITY_BASE_URL", current_wringer_status_text
+ ensure
+ ENV["DISTILLATOR_COMPATIBILITY_BASE_URL"] = old_compatibility_alias
+ end
+
+ test "current wringer status text shows canonical compat env source when both aliases are set" do
+ Rails.stubs(:env).returns(ActiveSupport::StringInquirer.new("staging"))
+ @distillator_config.compatibility_base_url = nil
+ @distillator_config.legacy_wringer_base_url = nil
+ @distillator_config.allow_localhost_compatibility = false
+ old_compat = ENV["DISTILLATOR_COMPAT_BASE_URL"]
+ old_alias = ENV["DISTILLATOR_COMPATIBILITY_BASE_URL"]
+ ENV["DISTILLATOR_COMPAT_BASE_URL"] = "https://canonical.example"
+ ENV["DISTILLATOR_COMPATIBILITY_BASE_URL"] = "https://alias.example"
+ Distillator::TransitionEvidence.stubs(:latest_legacy_lookup_error).returns(nil)
+
+ assert_equal "Current Wringer: Remote configured - https://canonical.example via DISTILLATOR_COMPAT_BASE_URL", current_wringer_status_text
+ ensure
+ ENV["DISTILLATOR_COMPAT_BASE_URL"] = old_compat
+ ENV["DISTILLATOR_COMPATIBILITY_BASE_URL"] = old_alias
+ end
+
+ test "missing config transition evidence does not mark a configured endpoint as unreachable" do
+ record = stub(
+ details: {
+ "legacy_lookup_error" => "missing_config",
+ "legacy_lookup_status" => "missing_config",
+ "reason" => "legacy_lookup_missing_config"
+ }
+ )
+ Rails.stubs(:env).returns(ActiveSupport::StringInquirer.new("staging"))
+ @distillator_config.compatibility_base_url = "https://user:secret@compat.example/token"
+ @distillator_config.legacy_wringer_base_url = "https://legacy.example"
+ @distillator_config.allow_localhost_compatibility = false
+ Distillator::TransitionEvidence.stubs(:latest_legacy_lookup_error).returns(record)
+
+ assert_equal "Current Wringer: Remote configured - https://compat.example via config.x.distillator.compatibility_base_url", current_wringer_status_text
+ end
+end
diff --git a/test/helpers/cache_link_rendering_test.rb b/test/helpers/cache_link_rendering_test.rb
new file mode 100644
index 00000000..3fca061c
--- /dev/null
+++ b/test/helpers/cache_link_rendering_test.rb
@@ -0,0 +1,123 @@
+require "test_helper"
+
+class CacheLinkRenderingTest < ActionView::TestCase
+ include ApplicationHelper
+ include Distillator::CacheHelper
+ include Distillator::ShadowReportsHelper
+ include WebsitesHelper
+ include WebpagesHelper
+ include StatementsHelper
+ include SourcesHelper
+ include OperatorContextHelper
+
+ setup do
+ @old_fetch_mode = ENV["DISTILLATOR_FETCH_MODE"]
+ Distillator::WringerEndpoint.stubs(:current).returns(
+ Distillator::WringerEndpoint::Result.new(
+ compatibility_base_url: "https://wringer.example",
+ legacy_lookup_base_url: "https://wringer.example",
+ compatibility_source: Distillator::WringerEndpoint::CANONICAL_COMPATIBILITY_ENV,
+ state: :remote_configured,
+ status_label: "Current Wringer: Remote configured",
+ status_detail: "https://wringer.example"
+ )
+ )
+ end
+
+ teardown do
+ ENV["DISTILLATOR_FETCH_MODE"] = @old_fetch_mode
+ end
+
+ test "common surfaces agree on cache links for legacy shadow and active rollout" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "active"
+
+ {
+ legacy: { mode: "legacy", active_backend: :wringer, secondary_labels: ["Open Condenser cache"] },
+ shadow: { mode: "shadow", active_backend: :wringer, secondary_labels: ["Compare Condenser vs Wringer", "Open Condenser cache"] },
+ active: { mode: "active", active_backend: :condenser, secondary_labels: ["Inspect legacy Wringer", "Compare Condenser vs Wringer"] }
+ }.each do |rollout_mode, expected|
+ website, webpage, source, statement = rollout_fixture_for(mode: expected[:mode], suffix: rollout_mode)
+ expected_links = active_cache_links_for(website.seedurl, website: website)
+
+ assert_cache_link_contract(
+ expected_links,
+ expected_rollout: rollout_mode,
+ expected_active_backend: expected[:active_backend],
+ expected_secondary_labels: expected[:secondary_labels],
+ expected_url: website.seedurl
+ )
+ assert_equal shared_contract(expected_links), shared_contract(website_cache_links(website))
+ assert_equal shared_contract(expected_links), shared_contract(webpage_cache_links(webpage))
+ assert_equal shared_contract(expected_links), shared_contract(statement_cache_links(statement))
+ assert_equal shared_contract(expected_links), shared_contract(source_cache_links(source))
+
+ @statement = statement
+ operator_links = operator_context_payload[:cache_links]
+ assert_equal shared_contract(expected_links), shared_contract(operator_links)
+ remove_instance_variable(:@statement)
+ end
+ end
+
+ test "cache compare and shadow report share the same inspection link labels" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "active"
+ website, = rollout_fixture_for(mode: "shadow", suffix: "inspection-shared")
+ url = website.webpages.first.url
+
+ cache_links = Distillator::CacheHelper.instance_method(:cache_inspection_links).bind_call(self, url, website: website)
+ shadow_links = Distillator::ShadowReportsHelper.instance_method(:cache_inspection_links).bind_call(self, url, website: website)
+
+ assert_equal cache_links, shadow_links
+ assert_equal ["Source website", "Compare", "Compare extracted statements", "Open active cache", "Open Condenser cache", "Webpage record"], cache_links.map { |link| link[:label] }
+ end
+
+ private
+
+ def rollout_fixture_for(mode:, suffix:)
+ website = Website.create!(
+ name: "cache-link-#{suffix}",
+ seedurl: "http://example.org/#{suffix}",
+ graph_name: "http://example.com/cache-link-#{suffix}",
+ default_language: "en",
+ distillator_mode: mode
+ )
+ webpage = Webpage.create!(
+ url: website.seedurl,
+ language: "en",
+ rdf_uri: "rdf:cache-link-#{suffix}",
+ rdfs_class: rdfs_classes(:one),
+ website: website
+ )
+ source = Source.create!(
+ algorithm_value: "xpath=//title/text()",
+ selected: true,
+ selected_by: "Distillator",
+ language: "en",
+ render_js: false,
+ property: properties(:one),
+ website: website
+ )
+ statement = Statement.create!(
+ cache: "cache-link-#{suffix}",
+ source: source,
+ webpage: webpage,
+ status: "ok",
+ status_origin: "condenser_refresh"
+ )
+
+ [website, webpage, source, statement]
+ end
+
+ def assert_cache_link_contract(links, expected_rollout:, expected_active_backend:, expected_secondary_labels:, expected_url:)
+ assert_equal expected_rollout, links[:rollout_mode]
+ assert_equal expected_active_backend, links[:active_backend]
+ assert_equal "/condenser/cache/compare?uri=#{CGI.escape(expected_url)}", links[:compare_url]
+ assert_equal expected_secondary_labels, Array(links[:secondary_links]).map { |link| link[:label] }
+ assert links[:active_cache_url].present?
+ end
+
+ def shared_contract(links)
+ links.slice(:active_cache_url, :rollout_mode, :active_backend, :compare_url).merge(
+ secondary_labels: Array(links[:secondary_links]).map { |link| link[:label] }
+ )
+ end
+end
diff --git a/test/helpers/cc_wringer_helper_test.rb b/test/helpers/cc_wringer_helper_test.rb
index ef7ec0b7..bd3c53b8 100644
--- a/test/helpers/cc_wringer_helper_test.rb
+++ b/test/helpers/cc_wringer_helper_test.rb
@@ -1,29 +1,204 @@
require 'test_helper'
class CcWringerHelperTest < ActionView::TestCase
+ setup do
+ Distillator::WringerRules.reset!
+ @distillator_config = Rails.application.config.x.distillator
+ @old_compatibility_base_url = @distillator_config.compatibility_base_url
+ @old_legacy_wringer_base_url = @distillator_config.legacy_wringer_base_url
+ @old_allow_localhost = @distillator_config.allow_localhost_compatibility
+ end
+
+ teardown do
+ Distillator::WringerRules.reset!
+ @distillator_config.compatibility_base_url = @old_compatibility_base_url
+ @distillator_config.legacy_wringer_base_url = @old_legacy_wringer_base_url
+ @distillator_config.allow_localhost_compatibility = @old_allow_localhost
+ end
+
+ test "development can default wringer urls to localhost" do
+ Rails.stubs(:env).returns(ActiveSupport::StringInquirer.new("development"))
+ @distillator_config.compatibility_base_url = nil
+ @distillator_config.legacy_wringer_base_url = nil
+ @distillator_config.allow_localhost_compatibility = true
- test "should get wringer url for DEV" do
expected_output = "http://localhost:3009"
assert_equal expected_output, get_wringer_url_per_environment()
+
+ assert_equal "http://localhost:3000", distillator_compatibility_base_url
+ assert_equal "http://localhost:3000/websites/wring?uri=http%3A%2F%2Fculturecreates.com&format=raw&include_fragment=true", use_wringer("http://culturecreates.com", false)
+ assert_equal "http://localhost:3000/websites/wring?uri=http%3A%2F%2Fculturecreates.com&format=raw&include_fragment=true&use_phantomjs=true", use_wringer("http://culturecreates.com", true)
+ assert_equal "http://localhost:3000/websites/wring?uri=http%3A%2F%2Fculturecreates.com&format=raw&include_fragment=true&json_post=true", use_wringer("http://culturecreates.com", false, { json_post: true })
+ assert_equal "http://localhost:3000/websites/wring?uri=https%3A%2F%2Fculturecreates.com%2Fpeople%23gregory&format=raw&include_fragment=true", use_wringer("https://culturecreates.com/people#gregory", false)
end
+ test "use_wringer can explicitly target legacy live wringer fallback" do
+ Rails.stubs(:env).returns(ActiveSupport::StringInquirer.new("development"))
+ @distillator_config.compatibility_base_url = nil
+ @distillator_config.legacy_wringer_base_url = nil
+ @distillator_config.allow_localhost_compatibility = true
- test "should convert url for wringer" do
expected_output = "http://localhost:3009/websites/wring?uri=http%3A%2F%2Fculturecreates.com&format=raw&include_fragment=true"
- assert_equal expected_output, use_wringer("http://culturecreates.com", false)
+ assert_equal expected_output, use_wringer("http://culturecreates.com", false, { force_legacy: true })
+ end
+
+ test "staging without compatibility config does not silently use localhost" do
+ Rails.stubs(:env).returns(ActiveSupport::StringInquirer.new("staging"))
+ @distillator_config.compatibility_base_url = nil
+ @distillator_config.legacy_wringer_base_url = nil
+ @distillator_config.allow_localhost_compatibility = false
+
+ assert_nil distillator_compatibility_base_url
+ assert_nil get_wringer_url_per_environment
+ assert_equal "Current Wringer: Missing staging config", current_wringer_endpoint.status_label
+ assert_raises(ArgumentError) { use_wringer("http://culturecreates.com", false) }
end
- test "should convert url for wringer using phantomjs" do
- expected_output = "http://localhost:3009/websites/wring?uri=http%3A%2F%2Fculturecreates.com&format=raw&include_fragment=true&use_phantomjs=true"
- assert_equal expected_output, use_wringer("http://culturecreates.com", true)
+ test "staging with configured remote endpoint uses remote wringer urls" do
+ Rails.stubs(:env).returns(ActiveSupport::StringInquirer.new("staging"))
+ @distillator_config.compatibility_base_url = "https://compat.example"
+ @distillator_config.legacy_wringer_base_url = "https://legacy.example"
+ @distillator_config.allow_localhost_compatibility = false
+
+ assert_equal "https://compat.example", distillator_compatibility_base_url
+ assert_equal "https://legacy.example", get_wringer_url_per_environment
+ assert_equal "Current Wringer: Remote configured", current_wringer_endpoint.status_label
+ assert_equal "config.x.distillator.compatibility_base_url", current_wringer_endpoint.compatibility_source
+ assert_equal "https://compat.example via config.x.distillator.compatibility_base_url", current_wringer_endpoint.status_detail
+ end
+
+ test "staging can resolve remote endpoint from DISTILLATOR_COMPAT_BASE_URL when config is nil" do
+ Rails.stubs(:env).returns(ActiveSupport::StringInquirer.new("staging"))
+ @distillator_config.compatibility_base_url = nil
+ @distillator_config.legacy_wringer_base_url = nil
+ @distillator_config.allow_localhost_compatibility = false
+ old_compat_alias = ENV["DISTILLATOR_COMPAT_BASE_URL"]
+ ENV["DISTILLATOR_COMPAT_BASE_URL"] = "https://footlight-wringer.herokuapp.com"
+
+ assert_equal "https://footlight-wringer.herokuapp.com", distillator_compatibility_base_url
+ assert_equal "https://footlight-wringer.herokuapp.com", get_wringer_url_per_environment
+ assert_equal "Current Wringer: Remote configured", current_wringer_endpoint.status_label
+ assert_equal "DISTILLATOR_COMPAT_BASE_URL", current_wringer_endpoint.compatibility_source
+ assert_equal "https://footlight-wringer.herokuapp.com via DISTILLATOR_COMPAT_BASE_URL", current_wringer_endpoint.status_detail
+ ensure
+ ENV["DISTILLATOR_COMPAT_BASE_URL"] = old_compat_alias
end
- test "should convert url for wringer using json_post" do
- expected_output = "http://localhost:3009/websites/wring?uri=http%3A%2F%2Fculturecreates.com&format=raw&include_fragment=true&json_post=true"
- assert_equal expected_output, use_wringer("http://culturecreates.com", false, { json_post: true })
+ test "staging can resolve remote endpoint from DISTILLATOR_COMPATIBILITY_BASE_URL alias when canonical is blank" do
+ Rails.stubs(:env).returns(ActiveSupport::StringInquirer.new("staging"))
+ @distillator_config.compatibility_base_url = nil
+ @distillator_config.legacy_wringer_base_url = nil
+ @distillator_config.allow_localhost_compatibility = false
+ old_compatibility_alias = ENV["DISTILLATOR_COMPATIBILITY_BASE_URL"]
+ ENV["DISTILLATOR_COMPATIBILITY_BASE_URL"] = "https://alias.example"
+
+ assert_equal "https://alias.example", distillator_compatibility_base_url
+ assert_equal "DISTILLATOR_COMPATIBILITY_BASE_URL", current_wringer_endpoint.compatibility_source
+ assert_equal "https://alias.example via DISTILLATOR_COMPATIBILITY_BASE_URL", current_wringer_endpoint.status_detail
+ ensure
+ ENV["DISTILLATOR_COMPATIBILITY_BASE_URL"] = old_compatibility_alias
+ end
+
+ test "canonical compat env wins over alias in helper status" do
+ Rails.stubs(:env).returns(ActiveSupport::StringInquirer.new("staging"))
+ @distillator_config.compatibility_base_url = nil
+ @distillator_config.legacy_wringer_base_url = nil
+ @distillator_config.allow_localhost_compatibility = false
+ old_compat = ENV["DISTILLATOR_COMPAT_BASE_URL"]
+ old_alias = ENV["DISTILLATOR_COMPATIBILITY_BASE_URL"]
+ ENV["DISTILLATOR_COMPAT_BASE_URL"] = "https://canonical.example"
+ ENV["DISTILLATOR_COMPATIBILITY_BASE_URL"] = "https://alias.example"
+
+ assert_equal "https://canonical.example", distillator_compatibility_base_url
+ assert_equal "DISTILLATOR_COMPAT_BASE_URL", current_wringer_endpoint.compatibility_source
+ assert_equal "https://canonical.example via DISTILLATOR_COMPAT_BASE_URL", current_wringer_endpoint.status_detail
+ ensure
+ ENV["DISTILLATOR_COMPAT_BASE_URL"] = old_compat
+ ENV["DISTILLATOR_COMPATIBILITY_BASE_URL"] = old_alias
+ end
+
+ test "staging configured remote endpoint can be marked unreachable without exposing credentials" do
+ Rails.stubs(:env).returns(ActiveSupport::StringInquirer.new("staging"))
+ @distillator_config.compatibility_base_url = "https://user:secret@compat.example/token"
+ @distillator_config.legacy_wringer_base_url = "https://legacy.example"
+ @distillator_config.allow_localhost_compatibility = false
+
+ endpoint = current_wringer_endpoint(last_error: "connection refused")
+
+ assert_equal :unreachable, endpoint.state
+ assert_equal "Current Wringer: Unreachable", endpoint.status_label
+ assert_equal "last lookup failed", endpoint.status_detail
+ assert_equal "config.x.distillator.compatibility_base_url", endpoint.compatibility_source
+ assert_equal "https://user:secret@compat.example/token", endpoint.compatibility_base_url
+ end
+
+ test "normalized_fetch_url strips fragment without affecting wringer uri target" do
+ assert_equal "https://culturecreates.com/people", normalized_fetch_url("https://culturecreates.com/people#gregory")
+ assert_equal "https://culturecreates.com/people#gregory", wringer_uri_target("https://culturecreates.com/people#gregory")
+ end
+
+ test "wringer_received_404 uses local distillator cache by default" do
+ key = Distillator::WringerUrlKey.call("https://example.com", include_fragment: true)
+ Distillator::FetchCache.create!(
+ uri_key: key.uri_key,
+ normalized_url: key.normalized_url,
+ http_response_code: 404,
+ scrape_date: Time.zone.now
+ )
+
+ assert wringer_received_404?("https://example.com")
+ end
+
+ test "wringer_received_404 returns false when explicit legacy fallback aborts" do
+ previous = ENV[Distillator::WringerEndpoint::LEGACY_WRINGER_FALLBACK_ENV]
+ ENV[Distillator::WringerEndpoint::LEGACY_WRINGER_FALLBACK_ENV] = "true"
+ stubs(:safe_wringer_call).returns(["abort_update", { error: "Wringer unreachable", error_type: "SocketError" }])
+
+ assert_not wringer_received_404?("https://example.com")
+ ensure
+ ENV[Distillator::WringerEndpoint::LEGACY_WRINGER_FALLBACK_ENV] = previous
+ end
+
+ test "safe_wringer_call handles connection error" do
+ result = safe_wringer_call do
+ raise Errno::ECONNREFUSED
+ end
+
+ assert_equal "abort_update", result.first
+ assert_equal "wringer_unreachable", result.last[:error_type]
+ assert result.last[:policy][:retry]
end
+ test "safe_wringer_call preserves explicit false retry and cache values" do
+ rules = {
+ "empty_body" => {
+ "match" => { "body_blank" => true },
+ "policy" => {
+ "action" => "abort_update",
+ "retry" => false,
+ "cache" => false,
+ "delete" => false,
+ "error_code" => "empty_body"
+ }
+ }
+ }
+
+ stubs(:wringer_rules).returns(rules.to_a)
+
+ fake_response = Struct.new(:code, :body, :uri).new(
+ 200,
+ nil,
+ URI("https://example.com/empty")
+ )
+
+ result = safe_wringer_call { fake_response }
+
+ assert_equal "abort_update", result.first
+ assert_equal false, result.last[:retry]
+ assert_equal false, result.last[:cache]
+ assert_equal false, result.last[:policy]["delete"]
+ end
# test "should call wringer to condense and add webpage to knowledge graph" do
# expected_output = ""
@@ -41,4 +216,335 @@ class CcWringerHelperTest < ActionView::TestCase
# jsonld = {}
# assert_equal expected_output, update_jsonld_on_wringer(url, graph_uri, jsonld)
# end
+
+ # -------------------------
+ # WRINGER RULE ENGINE TESTS
+ # -------------------------
+
+ test "detects redirect_to_listing via final_url" do
+ rules = {
+ "redirect_to_listing" => {
+ "match" => {
+ "final_url_patterns" => ["/events$", "/$"]
+ },
+ "policy" => {
+ "action" => "abort_update",
+ "retry" => false,
+ "cache" => false,
+ "delete" => true,
+ "error_code" => "redirect_to_listing"
+ }
+ }
+ }
+
+ stubs(:wringer_rules).returns(rules.to_a)
+
+ response = {
+ body: "listing",
+ http_code: 200,
+ final_url: "https://example.com/events"
+ }
+
+ result = wringer_system_error?(response)
+
+ assert_not_nil result
+ assert_equal "redirect_to_listing", result[:error_type]
+ assert result[:policy]["delete"]
+ assert_not result[:policy]["retry"]
+ end
+
+
+ test "does not trigger redirect_to_listing for valid event page" do
+ rules = {
+ "redirect_to_listing" => {
+ "match" => {
+ "final_url_patterns" => ["/events$", "/$"]
+ },
+ "policy" => {
+ "error_code" => "redirect_to_listing"
+ }
+ }
+ }
+
+ stubs(:wringer_rules).returns(rules.to_a)
+
+ response = {
+ body: "event page",
+ http_code: 200,
+ final_url: "https://example.com/events/123"
+ }
+
+ result = wringer_system_error?(response)
+
+ assert_nil result
+ end
+
+
+ test "http_404 rule still works" do
+ rules = {
+ "http_404" => {
+ "match" => { "http_code" => 404 },
+ "policy" => {
+ "error_code" => "http_404",
+ "retry" => false
+ }
+ }
+ }
+
+ stubs(:wringer_rules).returns(rules.to_a)
+
+ response = {
+ body: "Not found",
+ http_code: 404,
+ final_url: "https://example.com/foo"
+ }
+
+ result = wringer_system_error?(response)
+
+ assert_not_nil result
+ assert_equal "http_404", result[:error_type]
+ end
+
+
+ test "rule ordering prioritizes redirect over generic rules" do
+ rules = {
+ "redirect_to_listing" => {
+ "match" => {
+ "final_url_patterns" => ["/events$"]
+ },
+ "policy" => {
+ "error_code" => "redirect_to_listing"
+ }
+ },
+ "http_404" => {
+ "match" => { "http_code" => 200 }, # fake overlap
+ "policy" => {
+ "error_code" => "http_404"
+ }
+ }
+ }
+
+ stubs(:wringer_rules).returns(rules.to_a)
+
+ response = {
+ body: "something",
+ http_code: 200,
+ final_url: "https://example.com/events"
+ }
+
+ result = wringer_system_error?(response)
+
+ assert_equal "redirect_to_listing", result[:error_type]
+ end
+
+ test "wringer_system_error prefers severity-aware primary issue" do
+ response = {
+ body: "",
+ http_code: 500,
+ final_url: "https://example.org/failure",
+ hints: ["empty_body"],
+ signals: {}
+ }
+
+ issue = wringer_system_error?(response)
+
+ assert_equal "http_server_error", issue[:error_type]
+ end
+
+
+ test "safe_wringer_call returns abort_update when redirect rule matches" do
+ rules = {
+ "redirect_to_listing" => {
+ "match" => {
+ "final_url_patterns" => ["/events$"]
+ },
+ "policy" => {
+ "action" => "abort_update",
+ "retry" => false,
+ "delete" => true,
+ "error_code" => "redirect_to_listing"
+ }
+ }
+ }
+
+ stubs(:wringer_rules).returns(rules.to_a)
+
+ fake_response = Struct.new(:code, :body, :uri).new(
+ 200,
+ "listing",
+ URI("https://example.com/events")
+ )
+
+ result = safe_wringer_call { fake_response }
+
+ assert_equal "abort_update", result.first
+ assert_equal "redirect_to_listing", result.last[:error_type]
+ end
+
+ test "safe_wringer_call returns body when no rule matches" do
+ stubs(:wringer_rules).returns([])
+
+ fake_response = Struct.new(:code, :body, :uri).new(
+ 200,
+ "event",
+ URI("https://example.com/events/123")
+ )
+
+ result = safe_wringer_call { fake_response }
+
+ assert_equal "event", result
+ end
+
+ test "safe_wringer_call works when wringer config is missing" do
+ CcWringerHelper.instance_variable_set(:@wringer_rules, nil)
+ Rails.application.stubs(:config_for).with(:wringer).returns(nil)
+
+ fake_response = Struct.new(:code, :body, :uri).new(
+ 200,
+ "event",
+ URI("https://example.com/events/123")
+ )
+
+ result = safe_wringer_call { fake_response }
+
+ assert_equal "event", result
+ end
+
+ test "safe_wringer_call preserves false return value" do
+ stubs(:wringer_rules).returns([])
+
+ result = safe_wringer_call { false }
+
+ assert_equal false, result
+ assert_instance_of FalseClass, result
+ end
+
+ test "safe_wringer_call preserves true return value" do
+ stubs(:wringer_rules).returns([])
+
+ result = safe_wringer_call { true }
+
+ assert_equal true, result
+ assert_instance_of TrueClass, result
+ end
+
+ test "safe_wringer_call preserves plain string return value" do
+ stubs(:wringer_rules).returns([])
+
+ result = safe_wringer_call { "hello" }
+
+ assert_equal "hello", result
+ end
+
+ test "safe_wringer_call can return normalized response hash" do
+ stubs(:wringer_rules).returns([])
+
+ fake_response = Struct.new(:code, :body, :uri).new(
+ 302,
+ "redirect",
+ URI("https://example.com/events")
+ )
+
+ result = safe_wringer_call(normalize_response: true) { fake_response }
+
+ assert_equal(
+ { body: "redirect", http_code: 302, final_url: "https://example.com/events" },
+ result
+ )
+ end
+
+ test "matches only when all conditions are satisfied" do
+ rules = {
+ "complex_rule" => {
+ "match" => {
+ "http_code" => 200,
+ "body_contains" => ["listing"],
+ "final_url_patterns" => ["/events$"]
+ },
+ "policy" => {
+ "error_code" => "complex_rule"
+ }
+ }
+ }
+
+ stubs(:wringer_rules).returns(rules.to_a)
+
+ response = {
+ body: "listing",
+ http_code: 200,
+ final_url: "https://example.com/events"
+ }
+
+ result = wringer_system_error?(response)
+
+ assert_equal "complex_rule", result[:error_type]
+ end
+
+ test "safe_wringer_call respects custom action from policy" do
+ rules = {
+ "custom_action_rule" => {
+ "match" => {
+ "http_code" => 200
+ },
+ "policy" => {
+ "action" => "skip",
+ "error_code" => "custom_action"
+ }
+ }
+ }
+
+ stubs(:wringer_rules).returns(rules.to_a)
+
+ fake_response = Struct.new(:code, :body, :uri).new(
+ 200,
+ "whatever",
+ URI("https://example.com/foo")
+ )
+
+ result = safe_wringer_call { fake_response }
+
+ assert_equal "skip", result.first
+ assert_equal "custom_action", result.last[:error_type]
+ end
+
+ test "safe_wringer_call preserves redirect delete policy" do
+ response = Struct.new(:code, :body, :uri).new(
+ 200,
+ 'listing
',
+ URI("https://example.org/events")
+ )
+
+ result = safe_wringer_call { response }
+
+ assert_equal "abort_update", result.first
+ assert_equal "redirect_to_listing", result.last[:error_type]
+ assert_not result.last[:retry]
+ assert_not result.last[:cache]
+ assert result.last[:delete]
+ end
+
+ test "invalid regex pattern does not crash rule engine" do
+ rules = {
+ "bad_regex" => {
+ "match" => {
+ "final_url_patterns" => ["*invalid["]
+ },
+ "policy" => {
+ "error_code" => "bad_regex"
+ }
+ }
+ }
+
+ stubs(:wringer_rules).returns(rules.to_a)
+
+ response = {
+ body: "ok",
+ http_code: 200,
+ final_url: "https://example.com/events"
+ }
+
+ result = wringer_system_error?(response)
+
+ assert_nil result
+ end
end
diff --git a/test/helpers/harmonized_card_partials_test.rb b/test/helpers/harmonized_card_partials_test.rb
new file mode 100644
index 00000000..fbf96092
--- /dev/null
+++ b/test/helpers/harmonized_card_partials_test.rb
@@ -0,0 +1,46 @@
+require "test_helper"
+
+class HarmonizedCardPartialRenderingTest < ActiveSupport::TestCase
+ test "card grid escapes text by default" do
+ html = ApplicationController.render(
+ partial: "shared/cards/card_grid",
+ locals: {
+ cards: [
+ {
+ title: "Unsafe card",
+ rows: [{ label: "Body", value: "unsafe " }]
+ }
+ ]
+ }
+ )
+
+ assert_includes html, "<em>unsafe</em>"
+ refute_includes html, "unsafe "
+ end
+
+ test "card grid renders HTML only when explicitly marked html true" do
+ html = ApplicationController.render(
+ partial: "shared/cards/card_grid",
+ locals: {
+ cards: [
+ {
+ title: "Trusted card",
+ rows: [{ label: "Body", value: "trusted ", html: true }]
+ }
+ ]
+ }
+ )
+
+ assert_includes html, "trusted "
+ end
+
+ test "action bar handles empty action groups" do
+ html = ApplicationController.render(
+ partial: "shared/cards/action_bar",
+ locals: { groups: [] }
+ )
+
+ assert_includes html, "harmonized-card-actions"
+ refute_includes html, "cache-action-group"
+ end
+end
diff --git a/test/helpers/harmonized_table_helper_test.rb b/test/helpers/harmonized_table_helper_test.rb
new file mode 100644
index 00000000..56ed5cb2
--- /dev/null
+++ b/test/helpers/harmonized_table_helper_test.rb
@@ -0,0 +1,73 @@
+require "test_helper"
+
+class HarmonizedTableHelperTest < ActionView::TestCase
+ tests HarmonizedTableHelper
+
+ test "renders sortable header link" do
+ @request.path_parameters = { controller: "distillator/cache", action: "index" }
+
+ html = harmonized_sortable_header("normalized_url", "URI / Name", filters: {})
+
+ assert_includes html, "URI / Name"
+ assert_includes html, "sort=normalized_url"
+ assert_includes html, "direction=asc"
+ end
+
+ test "toggles asc and desc" do
+ @request.path_parameters = { controller: "distillator/cache", action: "index" }
+
+ params[:sort] = "http_response_code"
+ params[:direction] = "asc"
+ ascending_html = harmonized_sortable_header("http_response_code", "HTTP", filters: {})
+
+ params[:direction] = "desc"
+ descending_html = harmonized_sortable_header("http_response_code", "HTTP", filters: {})
+
+ assert_includes ascending_html, "direction=desc"
+ assert_includes descending_html, "direction=asc"
+ end
+
+ test "preserves term advanced filters and per page" do
+ @request.path_parameters = { controller: "distillator/cache", action: "index" }
+
+ html = harmonized_sortable_header(
+ "http_response_code",
+ "HTTP",
+ filters: {
+ term: "needle",
+ health: "healthy",
+ http_response_code: "404",
+ content_type: "json",
+ per_page: "25"
+ }
+ )
+
+ assert_includes html, "term=needle"
+ assert_includes html, "health=healthy"
+ assert_includes html, "http_response_code=404"
+ assert_includes html, "content_type=json"
+ assert_includes html, "per_page=25"
+ end
+
+ test "drops page when changing sort" do
+ @request.path_parameters = { controller: "distillator/cache", action: "index" }
+
+ html = harmonized_sortable_header("name", "Name", filters: { term: "needle", page: "4", per_page: "25" })
+
+ assert_includes html, "term=needle"
+ assert_includes html, "per_page=25"
+ assert_not_includes html, "page=4"
+ end
+
+ test "marks active sorted column" do
+ @request.path_parameters = { controller: "distillator/cache", action: "index" }
+ params[:sort] = "name"
+ params[:direction] = "asc"
+
+ html = harmonized_sortable_header("name", "Name", filters: {})
+
+ assert_includes html, "Name ↑"
+ assert_includes html, 'class="is-active-sort"'
+ assert_includes html, 'aria-current="true"'
+ end
+end
diff --git a/test/helpers/statements_helper_format_datatype_test.rb b/test/helpers/statements_helper_format_datatype_test.rb
index 956a0bab..cd94c0c9 100644
--- a/test/helpers/statements_helper_format_datatype_test.rb
+++ b/test/helpers/statements_helper_format_datatype_test.rb
@@ -66,6 +66,32 @@ class StatementsHelperTest < ActionView::TestCase
end
end
+ test "array string input for any:URI returns structured abort when a downstream linked-data lookup aborts" do
+ property = properties(:nine)
+ webpage = webpages(:one)
+ abort_payload = [
+ "abort_update",
+ {
+ error: "No server running at http://example.test",
+ error_type: "LinkedDataLookupError",
+ source: "search_cckg",
+ query: "ArtistsSantee Smith",
+ expected_class: "Organization"
+ }
+ ]
+
+ expects(:search_for_uri).with("CompanyKaha:wi Dance Theatre", property, webpage).returns(
+ ["CompanyKaha:wi Dance Theatre", "Organization", ["Kaha:wi Dance Theatre", "http://kg.artsdata.ca/resource/K10-206"]]
+ )
+ expects(:search_for_uri).with("ArtistsSantee Smith", property, webpage).returns(abort_payload)
+
+ assert_equal abort_payload, format_datatype(
+ ["CompanyKaha:wi Dance Theatre", "ArtistsSantee Smith"],
+ property,
+ webpage
+ )
+ end
+
test "format_datatype with time_zone" do
property = properties(:ten)
scraped_data = ["time_zone: Eastern Time (US & Canada) ","2020-05-28T22:00:00-00:00", "2020-05-31T22:00:00-00:00"]
diff --git a/test/helpers/statements_helper_refresh_test.rb b/test/helpers/statements_helper_refresh_test.rb
index 3fe29ca6..512ed658 100644
--- a/test/helpers/statements_helper_refresh_test.rb
+++ b/test/helpers/statements_helper_refresh_test.rb
@@ -4,6 +4,39 @@
class StatementsHelperRefreshTest < ActionView::TestCase
tests StatementsHelper
+ class CapturingLogger
+ attr_reader :infos, :warnings, :debugs
+
+ def initialize
+ @infos = []
+ @warnings = []
+ @debugs = []
+ end
+
+ def info(payload)
+ @infos << payload
+ end
+
+ def debug(payload = nil, &block)
+ payload = block.call if block
+ @debugs << payload
+ end
+
+ def warn(payload = nil, &block)
+ payload = block.call if block
+ @warnings << payload
+ end
+ end
+
+ setup do
+ @old_fetch_mode = ENV["DISTILLATOR_FETCH_MODE"]
+ Distillator::FetchCache.delete_all
+ end
+
+ teardown do
+ ENV["DISTILLATOR_FETCH_MODE"] = @old_fetch_mode
+ end
+
# statement set to manual
test "should not refresh when manual and ok" do
stat = statements(:one)
@@ -28,6 +61,10 @@ class StatementsHelperRefreshTest < ActionView::TestCase
stat.manual = true
stat.status = "problem"
expected = stat.cache_refreshed
+ self.stubs(:trace_enabled_for_request?).returns(false)
+ self.stubs(:run_dsl).returns(["fresh data"])
+ self.stubs(:format_datatype).returns("formatted")
+ self.stubs(:save_record?).returns(true)
refresh_statement_helper(stat)
assert_not_equal expected, stat.cache_refreshed
end
@@ -37,6 +74,10 @@ class StatementsHelperRefreshTest < ActionView::TestCase
stat.manual = true
stat.status = "initial"
expected = stat.cache_refreshed
+ self.stubs(:trace_enabled_for_request?).returns(false)
+ self.stubs(:run_dsl).returns(["fresh data"])
+ self.stubs(:format_datatype).returns("formatted")
+ self.stubs(:save_record?).returns(true)
refresh_statement_helper(stat)
assert_not_equal expected, stat.cache_refreshed, "Cache refresh dates should have changed"
end
@@ -46,19 +87,863 @@ class StatementsHelperRefreshTest < ActionView::TestCase
stat.manual = true
stat.status = "missing"
expected = stat.cache_refreshed
+ self.stubs(:trace_enabled_for_request?).returns(false)
+ self.stubs(:run_dsl).returns(["fresh data"])
+ self.stubs(:format_datatype).returns("formatted")
+ self.stubs(:save_record?).returns(true)
refresh_statement_helper(stat)
assert_not_equal expected, stat.cache_refreshed, "Cache refresh dates should have changed"
end
+
+ test "refresh_statement_helper preserves full non-trace run_dsl result" do
+ stat = statements(:one)
+ run_result = %w[first second]
+
+ self.stubs(:trace_enabled_for_request?).returns(false)
+ self.expects(:run_dsl).returns(run_result)
+ self.expects(:format_datatype).with(run_result, stat.source.property, stat.webpage).returns("formatted")
+ self.stubs(:save_record?).returns(true)
+
+ refresh_statement_helper(stat)
+
+ assert_equal "formatted", stat.reload.cache
+ end
+
+ test "refresh_statement_helper adds error when run_dsl aborts" do
+ stat = statements(:one)
+
+ self.stubs(:trace_enabled_for_request?).returns(false)
+ self.stubs(:run_dsl).returns(["abort_update", { error_type: "SocketError", error: "Wringer unreachable" }])
+
+ refresh_statement_helper(stat)
+
+ assert stat.errors.any?
+ assert_includes stat.errors.full_messages.to_sentence, "Scrape aborted (SocketError)"
+ end
+
+ test "refresh_statement_helper short-circuits on abort_update without formatting or saving" do
+ stat = statements(:one)
+ original_cache = stat.cache
+ original_cache_refreshed = stat.cache_refreshed
+
+ self.stubs(:trace_enabled_for_request?).returns(false)
+ self.stubs(:run_dsl).returns(["abort_update", { error_type: "SocketError", error: "Wringer unreachable" }])
+ self.expects(:format_datatype).never
+ self.expects(:save_record?).never
+
+ result = refresh_statement_helper(stat)
+
+ assert_match(/Scrape aborted \(SocketError\)/, result[:errors].join(" "))
+ assert_equal original_cache, stat.reload.cache
+ assert_equal original_cache_refreshed, stat.cache_refreshed
+ end
+
+ test "compact_refresh_error removes nested abort payload details from user message" do
+ message = compact_refresh_error(
+ error_type: "phantomjs_unavailable",
+ step: "url",
+ error: "Legacy PhantomJS renderer is unavailable",
+ signals: {
+ blocking_issue_key: "phantomjs_unavailable",
+ renderer_fallback: "direct_url",
+ primary_issue_category: "renderer",
+ phantomjs_iframe_extraction: false
+ },
+ hints: ["legacy_phantomjs", "phantomjs_unavailable"]
+ )
+
+ assert_match "Scrape aborted (phantomjs_unavailable)", message
+ assert_match "step=url", message
+ assert_match "Legacy PhantomJS renderer is unavailable", message
+ assert_no_match "renderer_fallback", message
+ assert_no_match "primary_issue_category", message
+ assert_no_match "phantomjs_iframe_extraction", message
+ end
+
+ test "refresh_statement_helper adds compact abort error without nested signals" do
+ stat = statements(:one)
+
+ self.stubs(:trace_enabled_for_request?).returns(false)
+ self.stubs(:run_dsl).returns(
+ ["abort_update", {
+ error_type: "phantomjs_unavailable",
+ error: "Legacy PhantomJS renderer is unavailable",
+ step: "url",
+ signals: {
+ blocking_issue_key: "phantomjs_unavailable",
+ renderer_fallback: "direct_url",
+ primary_issue_category: "renderer",
+ phantomjs_iframe_extraction: false
+ },
+ hints: ["legacy_phantomjs", "phantomjs_unavailable"]
+ }]
+ )
+
+ result = refresh_statement_helper(stat)
+ combined = result[:errors].join(" ")
+
+ assert_match "phantomjs_unavailable", combined
+ assert_no_match "renderer_fallback", combined
+ assert_no_match "primary_issue_category", combined
+ assert_no_match "phantomjs_iframe_extraction", combined
+ end
+
+ test "blank DSL result produces explicit error" do
+ stat = statements(:one)
+
+ self.stubs(:trace_enabled_for_request?).returns(false)
+ self.stubs(:run_dsl).returns(nil)
+
+ result = refresh_statement_helper(stat)
+
+ assert result[:errors].any?
+ assert_includes result[:errors].join, "blank result"
+ end
+
+ test "blank DSL result with trace still returns trace and error" do
+ stat = statements(:one)
+
+ cookies[:dsl_trace] = "true"
+ self.stubs(:run_dsl).returns([nil, [{ step: 1, type: "xpath" }]])
+
+ result = refresh_statement_helper(stat)
+
+ assert result[:errors].any?
+ assert result[:trace].present?
+ end
+
+ test "run_dsl returning unexpected shape still preserves error and safe trace" do
+ stat = statements(:one)
+
+ cookies[:dsl_trace] = "true"
+ self.stubs(:run_dsl).returns(nil)
+
+ result = refresh_statement_helper(stat)
+
+ assert result[:errors].any?
+ assert_equal [], result[:trace]
+ end
+
+ test "refresh_statement_helper returns structured result with trace when dsl_trace cookie is enabled" do
+ stat = statements(:one)
+ run_result = ["first"]
+ trace_events = [{ step: 1, type: "xpath" }]
+ expected_log_context = {
+ statement_id: stat.id,
+ source_id: stat.source_id,
+ webpage_id: stat.webpage_id,
+ website_id: stat.webpage.website_id
+ }
+
+ cookies[:dsl_trace] = "true"
+
+ self.expects(:run_dsl).with(
+ algorithm: stat.source.algorithm_value,
+ render_js: stat.source.render_js,
+ language: stat.source.language,
+ url: stat.webpage.url,
+ scrape_options: {
+ json_post: stat.source.json_post?,
+ use_phantomjs: stat.source.render_js,
+ website: stat.source.website,
+ website_id: stat.source.website_id,
+ log_context: expected_log_context
+ },
+ trace: true
+ ).returns([run_result, trace_events])
+ self.expects(:format_datatype).with(run_result, stat.source.property, stat.webpage).returns("formatted")
+ self.stubs(:save_record?).returns(true)
+
+ returned_result = refresh_statement_helper(stat)
+
+ assert_equal trace_events, instance_variable_get(:@dsl_trace)
+ assert_equal run_result, returned_result[:data]
+ assert_equal trace_events, returned_result[:trace]
+ assert_equal [], returned_result[:errors]
+ end
+
+ test "refresh_statement_helper returns structured result with nil trace when cookie is disabled" do
+ stat = statements(:one)
+ run_result = ["first"]
+ expected_log_context = {
+ statement_id: stat.id,
+ source_id: stat.source_id,
+ webpage_id: stat.webpage_id,
+ website_id: stat.webpage.website_id
+ }
+
+ cookies[:dsl_trace] = "false"
+
+ self.expects(:run_dsl).with(
+ algorithm: stat.source.algorithm_value,
+ render_js: stat.source.render_js,
+ language: stat.source.language,
+ url: stat.webpage.url,
+ scrape_options: {
+ json_post: stat.source.json_post?,
+ use_phantomjs: stat.source.render_js,
+ website: stat.source.website,
+ website_id: stat.source.website_id,
+ log_context: expected_log_context
+ },
+ trace: false
+ ).returns(run_result)
+ self.expects(:format_datatype).with(run_result, stat.source.property, stat.webpage).returns("formatted")
+ self.stubs(:save_record?).returns(true)
+
+ returned_result = refresh_statement_helper(stat)
+
+ assert_equal run_result, returned_result[:data]
+ assert_nil returned_result[:trace]
+ assert_equal [], returned_result[:errors]
+ assert_nil instance_variable_get(:@dsl_trace)
+ end
+
+ test "refresh_statement_helper forwards force_scrape_every_hrs into dsl execution scrape options" do
+ stat = statements(:one)
+ run_result = ["first"]
+ expected_log_context = {
+ statement_id: stat.id,
+ source_id: stat.source_id,
+ webpage_id: stat.webpage_id,
+ website_id: stat.webpage.website_id
+ }
+
+ self.stubs(:trace_enabled_for_request?).returns(false)
+ self.expects(:run_dsl).with(
+ algorithm: stat.source.algorithm_value,
+ render_js: stat.source.render_js,
+ language: stat.source.language,
+ url: stat.webpage.url,
+ scrape_options: {
+ force_scrape_every_hrs: "1",
+ json_post: stat.source.json_post?,
+ use_phantomjs: stat.source.render_js,
+ website: stat.source.website,
+ website_id: stat.source.website_id,
+ log_context: expected_log_context
+ },
+ trace: false
+ ).returns(run_result)
+ self.expects(:format_datatype).with(run_result, stat.source.property, stat.webpage).returns("formatted")
+ self.stubs(:save_record?).returns(true)
+
+ refresh_statement_helper(stat, force_scrape_every_hrs: "1")
+
+ assert_equal "formatted", stat.reload.cache
+ end
+
+ test "statement_refresh_scrape_options always includes website_id" do
+ stat = statements(:one)
+
+ options = statement_refresh_scrape_options(stat, force_scrape_every_hrs: "1")
+
+ assert_equal stat.source.json_post?, options[:json_post]
+ assert_equal stat.source.render_js, options[:use_phantomjs]
+ assert_equal stat.source.website, options[:website]
+ assert_equal stat.source.website_id, options[:website_id]
+ assert_equal stat.id, options.dig(:log_context, :statement_id)
+ assert_equal stat.source_id, options.dig(:log_context, :source_id)
+ assert_equal stat.webpage_id, options.dig(:log_context, :webpage_id)
+ assert_equal stat.webpage.website_id, options.dig(:log_context, :website_id)
+ end
+
+ test "statement refresh in internal mode without explicit scrape options still uses Distillator fetch cache store" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "internal"
+ stat = statements(:one)
+ stat.source.algorithm_value = "xpath=//h1/text()"
+ stat.source.render_js = false
+ stat.webpage.url = "https://example.com/refresh"
+
+ self.stubs(:trace_enabled_for_request?).returns(false)
+ self.expects(:format_datatype).with(["Title"], stat.source.property, stat.webpage).returns("formatted")
+ self.stubs(:save_record?).returns(false)
+ fetch = Struct.new(
+ :status,
+ :body,
+ :html,
+ :headers,
+ :final_url,
+ :redirect_chain,
+ :http_response_code,
+ :signals,
+ :hints,
+ :duration_ms,
+ :cache_hit,
+ :cache_write,
+ :cache_reason,
+ :uri_key,
+ :normalized_url,
+ :fetch_path,
+ keyword_init: true
+ ).new(
+ status: :ok,
+ body: "Title ",
+ html: "Title ",
+ headers: { content_type: "text/html" },
+ final_url: "https://example.com/refresh",
+ redirect_chain: ["https://example.com/refresh"],
+ http_response_code: 200,
+ signals: { "network_status" => "ok" },
+ hints: [],
+ duration_ms: 0,
+ cache_hit: false,
+ cache_write: true,
+ cache_reason: "missing_cache",
+ uri_key: CGI.escape("https://example.com/refresh"),
+ normalized_url: "https://example.com/refresh",
+ fetch_path: "native"
+ )
+
+ Distillator::FetchCacheStore.expects(:fetch).with do |kwargs|
+ assert_equal "https://example.com/refresh", kwargs[:uri]
+ assert_equal false, kwargs[:render_js]
+ assert_equal true, kwargs[:include_fragment]
+ assert_equal false, kwargs[:json_post]
+ assert_equal false, kwargs[:use_phantomjs]
+ assert_equal stat.source.website, kwargs[:website]
+ assert_equal stat.source.website_id, kwargs[:website_id]
+ true
+ end.returns(fetch)
+
+ result = refresh_statement_helper(stat)
+
+ assert_equal [], result[:errors]
+ assert_equal ["Title"], result[:data]
+ end
+
+ test "refresh_statement_helper with crawl cache options writes distillator fetch cache for eligible native url" do
+ stat = statements(:one)
+ stat.source.algorithm_value = "xpath=//h1/text()"
+ stat.source.render_js = false
+ stat.webpage.url = "https://www.culture3r.com/evenements/gabrielle-caron-rodage/"
+ html = "Gabrielle Caron "
+
+ self.stubs(:trace_enabled_for_request?).returns(false)
+ fetch = Struct.new(
+ :status,
+ :body,
+ :html,
+ :headers,
+ :final_url,
+ :redirect_chain,
+ :http_response_code,
+ :signals,
+ :hints,
+ :duration_ms,
+ :cache_hit,
+ :cache_write,
+ :cache_reason,
+ :uri_key,
+ :normalized_url,
+ :fetch_path,
+ keyword_init: true
+ ).new(
+ status: :ok,
+ body: html,
+ html: html,
+ headers: { content_type: "text/html" },
+ final_url: stat.webpage.url,
+ redirect_chain: [stat.webpage.url],
+ http_response_code: 200,
+ signals: {},
+ hints: [],
+ duration_ms: 0,
+ cache_hit: false,
+ cache_write: true,
+ cache_reason: "stale_by_force_scrape_every_hrs",
+ uri_key: CGI.escape(stat.webpage.url),
+ normalized_url: stat.webpage.url,
+ fetch_path: "native"
+ )
+ Distillator::FetchCacheStore.expects(:fetch).with do |kwargs|
+ assert_equal stat.webpage.url, kwargs[:uri]
+ assert_equal false, kwargs[:render_js]
+ assert_equal true, kwargs[:include_fragment]
+ assert_equal "1", kwargs[:force_scrape_every_hrs]
+ assert_equal stat.source.website, kwargs[:website]
+ assert_equal stat.source.website_id, kwargs[:website_id]
+ true
+ end.returns(fetch)
+
+ result = refresh_statement_helper(stat, force_scrape_every_hrs: "1")
+
+ assert_equal [], result[:errors]
+ assert_equal "Gabrielle Caron", stat.reload.cache
+ end
+
+ test "refresh_statement_helper passes render_js and json_post through Distillator fetch cache store" do
+ stat = statements(:one)
+ stat.source.algorithm_value = "xpath=//h1/text()"
+ stat.source.render_js = true
+ stat.webpage.url = "https://example.com/post"
+ fetch = Struct.new(
+ :status,
+ :body,
+ :html,
+ :headers,
+ :final_url,
+ :redirect_chain,
+ :http_response_code,
+ :signals,
+ :hints,
+ :duration_ms,
+ :cache_hit,
+ :cache_write,
+ :cache_reason,
+ :uri_key,
+ :normalized_url,
+ :fetch_path,
+ keyword_init: true
+ ).new(
+ status: :ok,
+ body: "Title ",
+ html: "Title ",
+ headers: { content_type: "text/html" },
+ final_url: stat.webpage.url,
+ redirect_chain: [stat.webpage.url],
+ http_response_code: 200,
+ signals: {},
+ hints: [],
+ duration_ms: 0,
+ cache_hit: false,
+ cache_write: true,
+ cache_reason: "stale_by_force_scrape_every_hrs",
+ uri_key: CGI.escape(stat.webpage.url),
+ normalized_url: stat.webpage.url,
+ fetch_path: "legacy"
+ )
+
+ self.stubs(:trace_enabled_for_request?).returns(false)
+ self.expects(:format_datatype).with(["Title"], stat.source.property, stat.webpage).returns("formatted")
+ self.stubs(:save_record?).returns(false)
+ Distillator::FetchCacheStore.expects(:fetch).with do |kwargs|
+ assert_equal stat.webpage.url, kwargs[:uri]
+ assert_equal true, kwargs[:render_js]
+ assert_equal true, kwargs[:include_fragment]
+ assert_equal true, kwargs[:json_post]
+ assert_equal true, kwargs[:use_phantomjs]
+ assert_equal "24", kwargs[:force_scrape_every_hrs]
+ assert_equal stat.source.website, kwargs[:website]
+ assert_equal stat.source.website_id, kwargs[:website_id]
+ true
+ end.returns(fetch)
+
+ result = refresh_statement_helper(stat, json_post: true, force_scrape_every_hrs: "24")
+
+ assert_equal [], result[:errors]
+ assert_equal ["Title"], result[:data]
+ end
+
+ test "refresh_statement_helper preserves previous cache and skips formatting when distillator fetch aborts" do
+ stat = statements(:one)
+ stat.source.algorithm_value = "xpath=//h1/text()"
+ stat.source.render_js = false
+ stat.webpage.url = "https://example.com/failure"
+ original_cache = stat.cache
+ original_cache_refreshed = stat.cache_refreshed
+ fetch = Struct.new(
+ :status,
+ :body,
+ :html,
+ :headers,
+ :final_url,
+ :redirect_chain,
+ :http_response_code,
+ :signals,
+ :hints,
+ :duration_ms,
+ :cache_hit,
+ :cache_write,
+ :cache_reason,
+ :uri_key,
+ :normalized_url,
+ :fetch_path,
+ keyword_init: true
+ ).new(
+ status: :abort,
+ body: ["abort_update", { error: "connection reset", error_type: "NativeFetchError", source: "native_fetch", retry: true, cache: false }],
+ html: nil,
+ headers: {},
+ final_url: stat.webpage.url,
+ redirect_chain: [],
+ http_response_code: nil,
+ signals: { "error_type" => "NativeFetchError", "network_status" => "failed" },
+ hints: ["timeout"],
+ duration_ms: 4.0,
+ cache_hit: false,
+ cache_write: true,
+ cache_reason: "force_scrape",
+ uri_key: CGI.escape(stat.webpage.url),
+ normalized_url: stat.webpage.url,
+ fetch_path: "native"
+ )
+
+ self.stubs(:trace_enabled_for_request?).returns(false)
+ self.expects(:format_datatype).never
+ self.expects(:save_record?).never
+ Distillator::FetchCacheStore.expects(:fetch).returns(fetch)
+
+ result = refresh_statement_helper(stat, force_scrape: true)
+
+ assert_match(/Scrape aborted \(NativeFetchError\)/, result[:errors].join(" "))
+ assert_equal original_cache, stat.reload.cache
+ assert_equal original_cache_refreshed, stat.cache_refreshed
+ end
+
+ test "refresh_statement_helper does not replace statement cache with redirect listing title when content failed" do
+ stat = statements(:one)
+ stat.update!(cache: "Existing Event Title", cache_refreshed: 1.day.ago, status: "ok")
+ stat.source.algorithm_value = "xpath=//title;ruby=$array.map{|e| e.gsub(/ \\|.*/,'')}"
+ stat.source.render_js = true
+ stat.webpage.url = "https://www.ovation.ca/00001Q/fr/Event/?seriesId=series&venueId=venue"
+
+ fetch = Struct.new(
+ :status,
+ :body,
+ :html,
+ :headers,
+ :final_url,
+ :redirect_chain,
+ :http_response_code,
+ :signals,
+ :hints,
+ :duration_ms,
+ :cache_hit,
+ :cache_write,
+ :cache_reason,
+ :uri_key,
+ :normalized_url,
+ :fetch_path,
+ keyword_init: true
+ ) do
+ def content_success?
+ false
+ end
+ end.new(
+ status: :ok,
+ body: "Recherche par titre ",
+ html: nil,
+ headers: { content_type: "text/html" },
+ final_url: "https://www.ovation.ca/Search/Title/",
+ redirect_chain: [stat.webpage.url, "https://www.ovation.ca/Search/Title/"],
+ http_response_code: 200,
+ signals: {
+ "network_status" => "ok",
+ "content_type" => "html",
+ "primary_issue_key" => "redirect_to_listing",
+ "primary_issue_severity" => "failed",
+ "blocking_issue_key" => "redirect_to_listing",
+ "content_success" => false
+ },
+ hints: ["redirect_to_listing"],
+ duration_ms: 0,
+ cache_hit: false,
+ cache_write: true,
+ cache_reason: "force_scrape",
+ uri_key: CGI.escape(stat.webpage.url),
+ normalized_url: stat.webpage.url,
+ fetch_path: "native"
+ )
+
+ self.stubs(:trace_enabled_for_request?).returns(false)
+ self.expects(:format_datatype).never
+ self.expects(:save_record?).never
+ Distillator::FetchCacheStore.expects(:fetch).returns(fetch)
+
+ result = refresh_statement_helper(stat, force_scrape: true)
+
+ assert_match(/redirect_to_listing/, result[:errors].join(" "))
+ assert_equal "Existing Event Title", stat.reload.cache
+ assert_not_equal "Recherche par titre", stat.reload.cache
+ end
+
+ test "run_dsl content failure abort preserves retry and cache policy for phantomjs unavailable" do
+ stat = statements(:one)
+ fetch = Struct.new(
+ :status,
+ :body,
+ :html,
+ :headers,
+ :final_url,
+ :redirect_chain,
+ :http_response_code,
+ :signals,
+ :hints,
+ :duration_ms,
+ :cache_hit,
+ :cache_write,
+ :cache_reason,
+ :uri_key,
+ :normalized_url,
+ :fetch_path,
+ keyword_init: true
+ ) do
+ def content_success?
+ false
+ end
+
+ def retry_policy
+ true
+ end
+
+ def cache_policy
+ false
+ end
+ end.new(
+ status: :abort,
+ body: nil,
+ html: nil,
+ headers: {},
+ final_url: stat.webpage.url,
+ redirect_chain: [],
+ http_response_code: nil,
+ signals: {
+ "network_status" => "failed",
+ "renderer" => "legacy_phantomjs",
+ "renderer_unavailable" => true,
+ "blocking_issue_key" => "phantomjs_unavailable",
+ "primary_issue_key" => "phantomjs_unavailable",
+ "content_success" => false,
+ "retry" => true,
+ "cache" => false
+ },
+ hints: ["legacy_phantomjs", "phantomjs_unavailable"],
+ duration_ms: 0,
+ cache_hit: false,
+ cache_write: true,
+ cache_reason: "force_scrape",
+ uri_key: CGI.escape(stat.webpage.url),
+ normalized_url: stat.webpage.url,
+ fetch_path: "native"
+ )
+
+ Distillator::FetchCacheStore.expects(:fetch).returns(fetch)
+
+ result = run_dsl(
+ algorithm: "xpath=//title",
+ render_js: true,
+ url: stat.webpage.url,
+ scrape_options: {
+ use_phantomjs: true,
+ website: stat.source.website,
+ website_id: stat.source.website_id,
+ log_context: {
+ statement_id: stat.id,
+ source_id: stat.source_id,
+ webpage_id: stat.webpage_id,
+ website_id: stat.webpage.website_id
+ }
+ },
+ trace: false
+ )
+
+ assert_equal "abort_update", result.first
+ assert_equal "phantomjs_unavailable", result.last[:error_type]
+ assert_equal true, result.last[:retry]
+ assert_equal false, result.last[:cache]
+ end
+
+ test "refresh_statement_helper logs structured content failure context" do
+ stat = statements(:one)
+ stat.update!(cache: "Existing Event Title", cache_refreshed: 1.day.ago, status: "ok")
+ stat.source.algorithm_value = "xpath=//title"
+ logger = CapturingLogger.new
+ fetch = Struct.new(
+ :status,
+ :body,
+ :html,
+ :headers,
+ :final_url,
+ :redirect_chain,
+ :http_response_code,
+ :signals,
+ :hints,
+ :duration_ms,
+ :cache_hit,
+ :cache_write,
+ :cache_reason,
+ :uri_key,
+ :normalized_url,
+ :fetch_path,
+ keyword_init: true
+ ) do
+ def content_success?
+ false
+ end
+
+ def retry_policy
+ false
+ end
+
+ def cache_policy
+ false
+ end
+ end.new(
+ status: :ok,
+ body: "Recherche par titre ",
+ html: nil,
+ headers: { content_type: "text/html" },
+ final_url: "https://www.ovation.ca/Search/Title/",
+ redirect_chain: [stat.webpage.url, "https://www.ovation.ca/Search/Title/"],
+ http_response_code: 200,
+ signals: {
+ "network_status" => "ok",
+ "content_type" => "html",
+ "primary_issue_key" => "redirect_to_listing",
+ "blocking_issue_key" => "redirect_to_listing",
+ "content_success" => false
+ },
+ hints: ["redirect_to_listing"],
+ duration_ms: 0,
+ cache_hit: false,
+ cache_write: true,
+ cache_reason: "force_scrape",
+ uri_key: CGI.escape(stat.webpage.url),
+ normalized_url: stat.webpage.url,
+ fetch_path: "native"
+ )
+
+ self.stubs(:trace_enabled_for_request?).returns(false)
+ self.expects(:format_datatype).never
+ self.expects(:save_record?).never
+ Distillator::FetchCacheStore.expects(:fetch).returns(fetch)
+ Rails.stubs(:logger).returns(logger)
+
+ refresh_statement_helper(stat, force_scrape: true)
+
+ warning = logger.warnings.find { |payload| payload.is_a?(Hash) && payload[:event] == "dsl.fetch.content_failure" }
+ assert warning.present?
+ assert_equal stat.webpage.url, warning[:url]
+ assert_equal "redirect_to_listing", warning[:error_type]
+ assert_equal "https://www.ovation.ca/Search/Title/", warning[:final_url]
+ assert_equal 200, warning[:http_response_code]
+ assert_equal false, warning[:cache_hit]
+ assert_equal true, warning[:cache_write]
+ assert_equal "force_scrape", warning[:cache_reason]
+ assert_equal stat.id, warning[:statement_id]
+ assert_equal stat.source_id, warning[:source_id]
+ assert_equal stat.webpage_id, warning[:webpage_id]
+ assert_equal stat.webpage.website_id, warning[:website_id]
+ end
+
+ test "wringer_links_for_step includes mode-aware active cache link without fetching" do
+ previous_mode = ENV["DISTILLATOR_FETCH_MODE"]
+ ENV["DISTILLATOR_FETCH_MODE"] = "legacy"
+ Distillator::FetchCacheStore.expects(:fetch).never
+ Distillator::WringerEndpoint.stubs(:current).returns(
+ Distillator::WringerEndpoint::Result.new(
+ compatibility_base_url: "https://wringer.example",
+ legacy_lookup_base_url: "https://wringer.example",
+ compatibility_source: Distillator::WringerEndpoint::CANONICAL_COMPATIBILITY_ENV,
+ state: :remote_configured,
+ status_label: "Current Wringer: Remote configured",
+ status_detail: "https://wringer.example"
+ )
+ )
+ website = websites(:one)
+ website.update!(distillator_mode: "active")
+
+ links = wringer_links_for_step(
+ url_after: "http://example.org/page",
+ website_id: website.id,
+ wringer: { signals: { network_status: "ok" } }
+ )
+
+ assert_match "/websites?term=", links[:wringer_search]
+ assert_equal "http://example.org/page", links[:raw_url]
+ assert_equal "/condenser/cache?term=http%3A%2F%2Fexample.org%2Fpage", links.dig(:active_cache, :active_cache_url)
+ assert_equal "Inspect legacy Wringer", links.dig(:active_cache, :secondary_links, 0, :label)
+ ensure
+ ENV["DISTILLATOR_FETCH_MODE"] = previous_mode
+ end
+
+ test "run_dsl returns [result, trace] when trace is enabled" do
+ runner = mock("dsl_runner")
+ runner.expects(:run).with("manual=hello").returns(["hello"])
+
+ Dsl::Core::AlgorithmRunner.expects(:new).with do |ctx|
+ assert_equal "https://example.com", ctx[:url]
+ assert_equal false, ctx[:render_js]
+ assert_equal({}, ctx[:scrape_options])
+ assert_instance_of Dsl::Tracing::TraceCollector, ctx[:tracer]
+ true
+ end.returns(runner)
+
+ assert_equal [["hello"], []], run_dsl(algorithm: "manual=hello", url: "https://example.com", trace: true)
+ end
+
+ test "run_dsl returns result only when trace is disabled" do
+ runner = mock("dsl_runner")
+ runner.expects(:run).with("manual=hello").returns(["hello"])
+
+ Dsl::Core::AlgorithmRunner.expects(:new).with do |ctx|
+ assert_equal "https://example.com", ctx[:url]
+ assert_equal false, ctx[:render_js]
+ assert_equal({}, ctx[:scrape_options])
+ assert_instance_of Dsl::Tracing::NullTracer, ctx[:tracer]
+ true
+ end.returns(runner)
+
+ assert_equal ["hello"], run_dsl(algorithm: "manual=hello", url: "https://example.com", trace: false)
+ end
+
+ test "trace_enabled_for_request? has no implicit fallback" do
+ self.stubs(:cookies).raises(NoMethodError, "cookies unavailable")
+
+ assert_raises(NoMethodError) do
+ trace_enabled_for_request?
+ end
+ end
+
+ test "interactive_redirect_info returns redirected with final_url" do
+ step = {
+ url_before: "https://example.com/start",
+ wringer: {
+ final_url: "https://example.com/final"
+ }
+ }
+
+ assert_equal "Network: redirected -> https://example.com/final", interactive_redirect_info(step)
+ assert_equal "Network: redirected -> https://example.com/final", wringer_network_metadata(step)
+ end
+
+ test "interactive_redirect_info falls back to signals final_url" do
+ step = {
+ url_after: "https://example.com/base",
+ wringer: {
+ signals: {
+ final_url: "https://example.com/from-signals"
+ }
+ }
+ }
+
+ assert_equal "Network: redirected -> https://example.com/from-signals", interactive_redirect_info(step)
+ end
+
+ test "interactive_redirect_info reports redirect when only redirect_chain is present" do
+ step = {
+ url_after: "https://example.com/base",
+ wringer: {
+ redirect_chain: ["https://example.com/step-1"],
+ final_url: nil
+ }
+ }
+
+ assert_equal "Network: redirected", interactive_redirect_info(step)
+ end
# 'abort_update' in cache
- test "should refresh when abort_update in crawl data and in cache when status OK" do
+ test "should not refresh when abort_update in crawl data and in cache when status OK" do
stat = statements(:one)
stat.cache = 'There is an abort_update'
stat.status = "ok"
expected = stat.cache_refreshed
+ self.stubs(:trace_enabled_for_request?).returns(false)
+ self.stubs(:run_dsl).returns(["abort_update", { error_type: "SocketError", error: "Wringer unreachable" }])
refresh_statement_helper(stat)
- assert_not_equal expected, stat.cache_refreshed, "Cache refresh dates should have changed"
+ assert_equal expected, stat.cache_refreshed, "Cache refresh dates should NOT have changed"
end
diff --git a/test/helpers/statements_helper_search_cckg_test.rb b/test/helpers/statements_helper_search_cckg_test.rb
index b1132682..4384795e 100644
--- a/test/helpers/statements_helper_search_cckg_test.rb
+++ b/test/helpers/statements_helper_search_cckg_test.rb
@@ -113,5 +113,121 @@ class StatementsHelperSearchCckgTest < ActionView::TestCase
end
end
+ test "search_cckg: structured reconciliation for place with QC province returns Montreal result only" do
+ webpage = webpages(:four)
+ webpage.website.stubs(:respond_to?).with(:province).returns(true)
+ webpage.website.stubs(:province).returns("QC")
+
+ HTTParty.expects(:get).with do |url|
+ url.include?("queries=") &&
+ url.include?("schema%3Aaddress%2Fschema%3AaddressRegion") &&
+ url.include?("%22v%22%3A%22QC%22")
+ end.returns(
+ {
+ "q0" => {
+ "result" => [
+ { "id" => "K11-3", "name" => "Place des Arts", "match" => true, "score" => 95.0 }
+ ]
+ }
+ }
+ )
+
+ expected = { data: [["Place des Arts", "http://kg.artsdata.ca/resource/K11-3"]] }
+ actual = search_cckg("Place des Arts", "Place", webpage)
+ assert_equal expected, actual
+ end
+
+ test "search_cckg: structured reconciliation for place with ON province returns Sudbury result only" do
+ webpage = webpages(:four)
+ webpage.website.stubs(:respond_to?).with(:province).returns(true)
+ webpage.website.stubs(:province).returns("ON")
+
+ HTTParty.expects(:get).with do |url|
+ url.include?("queries=") &&
+ url.include?("schema%3Aaddress%2Fschema%3AaddressRegion") &&
+ url.include?("%22v%22%3A%22ON%22")
+ end.returns(
+ {
+ "q0" => {
+ "result" => [
+ { "id" => "K11-SUDBURY", "name" => "Place des Arts (Sudbury)", "match" => true, "score" => 96.0 }
+ ]
+ }
+ }
+ )
+
+ expected = { data: [["Place des Arts (Sudbury)", "http://kg.artsdata.ca/resource/K11-SUDBURY"]] }
+ actual = search_cckg("Place des Arts", "Place", webpage)
+ assert_equal expected, actual
+ end
+
+ test "search_cckg: no province falls back to legacy extraction behavior and can return multiple results" do
+ webpage = webpages(:four)
+ webpage.website.stubs(:respond_to?).with(:province).returns(true)
+ webpage.website.stubs(:province).returns(nil)
+
+ HTTParty.expects(:get).with do |url|
+ url.include?("?query=Place%20des%20Arts") && url.include?("&type=Place")
+ end.returns(
+ {
+ "result" => [
+ { "id" => "K11-3", "name" => "Place des Arts", "match" => true, "score" => 95.0 },
+ { "id" => "K11-SUDBURY", "name" => "Place des Arts (Sudbury)", "match" => false, "score" => 70.0 }
+ ]
+ }
+ )
+
+ expected = {
+ data: [
+ ["Place des Arts", "http://kg.artsdata.ca/resource/K11-3"],
+ ["Place des Arts (Sudbury)", "http://kg.artsdata.ca/resource/K11-SUDBURY"]
+ ]
+ }
+ actual = search_cckg("Place des Arts", "Place", webpage)
+ assert_equal expected, actual
+ end
+
+ test "search_cckg: clean query resolves to a single best match" do
+ HTTParty.expects(:get).with do |url|
+ url.include?("?query=Place%20des%20Arts") && url.include?("&type=Place")
+ end.returns(
+ {
+ "result" => [
+ { "id" => "K11-3", "name" => "Place des Arts", "match" => true, "score" => 95.0 },
+ { "id" => "K11-SUDBURY", "name" => "Place des Arts (Sudbury)", "match" => false, "score" => 70.0 }
+ ]
+ }
+ )
+
+ expected = { data: [["Place des Arts", "http://kg.artsdata.ca/resource/K11-3"]] }
+ actual = search_cckg("Place des Arts", "Place")
+ assert_equal expected, actual
+ end
+
+ test "search_cckg: noisy query uses extraction behavior and returns multiple matches" do
+ webpage = webpages(:four)
+ webpage.website.stubs(:respond_to?).with(:province).returns(true)
+ webpage.website.stubs(:province).returns("ON")
+
+ HTTParty.expects(:get).with do |url|
+ url.include?("?query=Bluma%20Appel%20Theatre%20and%20Berkeley%20Street%20Theatre") && url.include?("&type=Place")
+ end.returns(
+ {
+ "result" => [
+ { "id" => "K11-6", "name" => "St. Lawrence Centre for the Arts - Bluma Appel Theatre", "match" => false, "score" => 81.0 },
+ { "id" => "K11-14", "name" => "Canadian Stage - Berkeley Street Theatre", "match" => false, "score" => 80.0 }
+ ]
+ }
+ )
+
+ expected = {
+ data: [
+ ["St. Lawrence Centre for the Arts - Bluma Appel Theatre", "http://kg.artsdata.ca/resource/K11-6"],
+ ["Canadian Stage - Berkeley Street Theatre", "http://kg.artsdata.ca/resource/K11-14"]
+ ]
+ }
+ actual = search_cckg("Bluma Appel Theatre and Berkeley Street Theatre", "Place", webpage)
+ assert_equal expected, actual
+ end
end
diff --git a/test/helpers/statements_helper_search_test.rb b/test/helpers/statements_helper_search_test.rb
new file mode 100644
index 00000000..f19b134f
--- /dev/null
+++ b/test/helpers/statements_helper_search_test.rb
@@ -0,0 +1,35 @@
+require "test_helper"
+
+class StatementsHelperSearchTest < ActionView::TestCase
+ tests StatementsHelper
+
+ test "expected_classes_for splits comma separated classes" do
+ assert_equal ["Place", "VirtualLocation"], expected_classes_for("Place, VirtualLocation")
+ end
+
+ test "expected_classes_for preserves organization fallback to person" do
+ assert_equal ["Organization", "Person"], expected_classes_for("Organization")
+ end
+
+ test "search_for_uri searches every expected class and deduplicates uri hits" do
+ property = OpenStruct.new(expected_class: "Place, VirtualLocation")
+ current_webpage = OpenStruct.new(rdf_uri: "http://current.example/resource")
+
+ expects(:search_everywhere).with("Main Hall", "Place", current_webpage).returns(
+ ["Main Hall", "Place", ["Main Hall", "http://example.org/place-1"], ["Current", "http://current.example/resource"]]
+ )
+ expects(:search_everywhere).with("Main Hall", "VirtualLocation", current_webpage).returns(
+ ["Main Hall", "VirtualLocation", ["Main Hall", "http://example.org/place-1"], ["Main Hall Stream", "http://example.org/place-2"]]
+ )
+
+ assert_equal(
+ [
+ "Main Hall",
+ "Place",
+ ["Main Hall", "http://example.org/place-1"],
+ ["Main Hall Stream", "http://example.org/place-2"]
+ ],
+ search_for_uri("Main Hall", property, current_webpage)
+ )
+ end
+end
diff --git a/test/helpers/statements_helper_test.rb b/test/helpers/statements_helper_test.rb
index 41b4bf94..eac0988e 100644
--- a/test/helpers/statements_helper_test.rb
+++ b/test/helpers/statements_helper_test.rb
@@ -1,82 +1,40 @@
require 'test_helper'
# Part of StatmentsHelper. See other test files for search_cckg and format_datatype tests.
+# Keep this file unit-fast. Do not add recorded scrape/network tests here.
+# Put recorded scrape coverage in test/integration/statements_helper_scrape_integration_test.rb.
class StatementsHelperTest < ActionView::TestCase
+ include DslRunnerTestHelper
+ setup do
+ Distillator::FetchCacheStore.expects(:fetch).never
+ Dsl::Support::WringerClient.any_instance.expects(:fetch).never
+ end
#process algorithm
-test "process_algorithm sparql" do
- expected = ["DOMINIC PAQUET • LAISSE-MOI PARTIR"]
- VCR.use_cassette('StatementsHelper:complexeculturelfelixleclerc') do
- assert_equal expected, process_algorithm(algorithm: "sparql={?s a schema:Event. ?s schema:name ?answer}", url: "https://www.complexeculturelfelixleclerc.com/event-details/dominic-paquet-laisse-moi-partir")
- end
-end
-test "process_algorithm sparql with nested image" do
- expected = ["https://static.wixstatic.com/media/28fd07_aa7b850feecb4363878aa75c34296221~mv2.jpg/v1/fill/w_537,h_534,al_c,q_80/28fd07_aa7b850feecb4363878aa75c34296221~mv2.jpg"]
- VCR.use_cassette('StatementsHelper:complexeculturelfelixleclerc') do
- assert_equal expected, process_algorithm(algorithm: "sparql={?s a schema:Event. ?s schema:image/schema:url ?answer}", url: "https://www.complexeculturelfelixleclerc.com/event-details/dominic-paquet-laisse-moi-partir")
- end
-end
-test "process_algorithm sparql with filter" do
- expected = ["https://static.wixstatic.com/media/28fd07_aa7b850feecb4363878aa75c34296221~mv2.jpg/v1/fill/w_537,h_534,al_c,q_80/28fd07_aa7b850feecb4363878aa75c34296221~mv2.jpg"]
- VCR.use_cassette('StatementsHelper:complexeculturelfelixleclerc') do
- assert_equal expected, process_algorithm(algorithm: "sparql={?s a schema:Event. ?s schema:image/schema:url ?answer . FILTER isURI(?answer) }", url: "https://www.complexeculturelfelixleclerc.com/event-details/dominic-paquet-laisse-moi-partir")
- end
+test "process_algorithm sparql preserves structured captcha aborts" do
+ abort_payload = [
+ "abort_update",
+ {
+ error: "captcha detected",
+ error_type: "system_captcha",
+ source: "wringer",
+ step: "sparql"
+ }
+ ]
+ stubs(:safe_wringer_call).returns(abort_payload)
+
+ assert_equal abort_payload, process_algorithm(
+ algorithm: "sparql={?s a schema:Event. ?s schema:name ?answer}",
+ url: "https://example.org/captcha"
+ )
+ assert_no_wringer_requests
end
-
test "process_algorithm manual" do
expected = ["Test"]
assert_equal expected, process_algorithm(algorithm: "manual=Test", url: "http://culturecreates.com")
-end
-test "process_algorithm xpath" do
- expected = ["Culture Creates | Digital knowledge management for the arts"]
- VCR.use_cassette('StatementsHelper:culturecreates.com') do
- assert_equal expected, process_algorithm(algorithm: "xpath=//title", url: "http://culturecreates.com")
- end
-end
-test "process_algorithm if_xpath continue" do
- expected = ["Culture Creates | Digital knowledge management for the arts", "Arts metadata compatible with an AI-powered world"]
- VCR.use_cassette('StatementsHelper:culturecreates.com') do
- assert_equal expected, process_algorithm(algorithm: "if_xpath=//title;xpath=(//h1)[1]", url: "http://culturecreates.com")
- end
-end
-test "process_algorithm if_xpath break" do
- expected = []
- VCR.use_cassette('StatementsHelper:culturecreates.com') do
- assert_equal expected, process_algorithm(algorithm: "if_xpath=//nothing;xpath=(//h1)[1]", url: "http://culturecreates.com")
- end
-end
-test "process_algorithm unless_xpath break" do
- expected = []
- VCR.use_cassette('StatementsHelper:culturecreates.com') do
- assert_equal expected, process_algorithm(algorithm: "unless_xpath=//title;xpath=(//h1)[1]", url: "http://culturecreates.com")
- end
-end
-test "process_algorithm unless_xpath continue" do
- expected = ["Arts metadata compatible with an AI-powered world"]
- VCR.use_cassette('StatementsHelper:culturecreates.com') do
- assert_equal expected, process_algorithm(algorithm: "unless_xpath=//nothing;xpath=(//h1)[1]", url: "http://culturecreates.com")
- end
-end
-test "process_algorithm url and xpath" do
- expected = ["ArtsdataApi"]
- VCR.use_cassette('StatementsHelper:process_algorithm url and xpath') do
- assert_equal expected, process_algorithm(algorithm: "url='http://api.artsdata'+'.ca';xpath=//title", url: "http://culturecreates.com")
- end
-end
-test "process_algorithm double xpath" do
- expected = ["Culture Creates | Digital knowledge management for the arts", "IE=edge"]
- VCR.use_cassette('StatementsHelper:culturecreates.com') do
- assert_equal expected, process_algorithm(algorithm: "xpath=//title;xpath=(//meta/@content)[1]", url: "http://culturecreates.com")
- end
-end
-test "process_algorithm url and json and ruby" do
- expected = ["2021-04-10 10:00:00", "time_zone: 'Eastern Time (US & Canada)'"]
- algo = "url=$url + '.json';json=$json.dig('date','end');ruby=$array[0] ? [$json.dig('date','start')] : $array;time_zone='Eastern Time (US & Canada)'"
- VCR.use_cassette('StatementsHelper: process_algorithm url and json and ruby') do
- assert_equal expected, process_algorithm(algorithm: algo, url: "https://signelaval.com/fr/evenements/14650/du-fond-de-mon-garde-robe")
- end
+ assert_no_wringer_requests
end
#test "process_algorithm ruby syntax error" do
# expected = ["abort_update", {:error=>"(eval):1: syntax error, unexpected end-of-input, expecting '}'", :error_type=>SyntaxError, :results_prior=>[], :algorithm_rescued=>"ruby=$array.each {|a| a"}]
@@ -88,44 +46,98 @@ class StatementsHelperTest < ActionView::TestCase
assert_equal "abort_update", result[0]
details = result[1]
assert_kind_of Hash, details
- assert_equal SyntaxError, details[:error_type]
+ assert_equal "SyntaxError", details[:error_type]
assert_includes details[:error].downcase, "syntax error"
- assert_includes details[:algorithm_rescued], "ruby=$array.each {|a| a"
+ assert_no_wringer_requests
end
+
test "process_algorithm invalid algorithm prefix" do
- expected = [["abort_update", {:error=>"Missing DSL prefix", :algorithm=>"//title"}]]
algo = "//title"
- assert_equal expected, process_algorithm(algorithm: algo, url: "https://signelaval.com/fr/evenements/14650/du-fond-de-mon-garde-robe")
+ result = process_algorithm(algorithm: algo, url: "https://signelaval.com/fr/evenements/14650/du-fond-de-mon-garde-robe")
+ assert_equal "abort_update", result.first
+ assert_match(/Missing DSL prefix/, result.last[:error])
+ assert_no_wringer_requests
end
+test "process_algorithm_scrape_options adds wringer compatibility for plain helper calls" do
+ options = process_algorithm_scrape_options({})
-#scrape
- test "should scrape title from html" do
- source = sources(:one)
- source.algorithm_value = "xpath=//title"
- expected_output = ['Culture Creates | Digital knowledge management for the arts']
- VCR.use_cassette('StatementsHelper: should scrape title from html') do
- assert_equal expected_output, scrape(source, "http://culturecreates.com")
- end
- end
+ assert_equal true, options[:wringer_compatibility]
+end
- test "should scrape 2 items from html" do
- source = OpenStruct.new(algorithm_value: 'xpath=//title;xpath=//meta[@property="og:title"]/@content')
- expected_output = ['Culture Creates | Digital knowledge management for the arts', "Culture Creates Inc"]
- VCR.use_cassette('StatementsHelper: should scrape 2 items from html') do
- assert_equal expected_output, scrape(source, "http://culturecreates.com")
- end
- end
+test "process_algorithm_scrape_options preserves cache-backed refresh options" do
+ options = process_algorithm_scrape_options(
+ force_scrape_every_hrs: "1",
+ log_context: { statement_id: 1, website_id: 2 }
+ )
+
+ assert_nil options[:wringer_compatibility]
+ assert_equal "1", options["force_scrape_every_hrs"]
+ assert_equal({ "statement_id" => 1, "website_id" => 2 }, options["log_context"])
+end
+test "process_algorithm_uses_cache_path is true for statement refresh context" do
+ assert_equal true, process_algorithm_uses_cache_path?(
+ website_id: 2,
+ log_context: { statement_id: 1 }
+ )
+end
- test "should concatenate 2 items from html" do
- source = OpenStruct.new(algorithm_value: 'xpath=//title;xpath=//meta[@property="og:title"]/@content;ruby=$array[0]+ " | " + $array[1]')
- expected_output = "Culture Creates | Digital knowledge management for the arts | Culture Creates Inc"
- VCR.use_cassette('StatementsHelper: should concatenate 2 items from html') do
- assert_equal expected_output, scrape(source, "http://culturecreates.com")
- end
- end
+test "process_algorithm_uses_cache_path is false for plain helper extraction" do
+ assert_equal false, process_algorithm_uses_cache_path?({})
+end
+test "process_algorithm_uses_cache_path is true for force_scrape_every_hrs" do
+ assert_equal true, process_algorithm_uses_cache_path?(force_scrape_every_hrs: "24")
+end
+
+test "process_algorithm_uses_cache_path is true for explicit mode" do
+ assert_equal true, process_algorithm_uses_cache_path?(mode: :internal)
+end
+
+test "build_refresh_proxy preserves logger and route helper context" do
+ proxy = StatementsHelper.build_refresh_proxy(cookies: { dsl_trace: "false" })
+
+ assert_respond_to proxy, :logger
+ assert_respond_to proxy, :statement_path
+ assert_equal ["2020-05-31"], proxy.convert_datetime("2020-05-31")
+end
+
+test "search_everywhere aborts when cckg lookup fails and no local reconciliation is available" do
+ stubs(:search_condenser).returns({ data: [] })
+ stubs(:search_cckg).returns({ error: "No server running at http://example.invalid/reconcile" })
+ stubs(:logger).returns(stub(error: true))
+
+ result = search_everywhere("Broken Venue", "Place")
+
+ assert_equal "abort_update", result.first
+ assert_equal "LinkedDataLookupError", result.second[:error_type]
+ assert_equal "search_cckg", result.second[:source]
+ assert_equal "Broken Venue", result.second[:query]
+ assert_equal "Place", result.second[:expected_class]
+end
+
+test "refresh_statement_helper keeps existing good uri links when linked-data reconciliation aborts" do
+ @statement = statements(:four)
+ original_cache = "[\"Existing venue\",\"Place\",[\"Good Venue\",\"http://kg.artsdata.ca/resource/K11-3\"]]"
+ @statement.update!(status: "ok", cache: original_cache)
+
+ stubs(:run_dsl).returns(["Broken Venue"])
+ stubs(:format_datatype).returns(
+ ["abort_update", {
+ error: "No server running at http://example.invalid/reconcile",
+ error_type: "LinkedDataLookupError",
+ source: "search_cckg",
+ query: "Broken Venue",
+ expected_class: "Place"
+ }]
+ )
+
+ result = refresh_statement_helper(@statement)
+
+ assert_includes result[:errors].join(" "), "Scrape aborted (LinkedDataLookupError)"
+ assert_equal original_cache, @statement.reload.cache
+end
# french_to_english_month
test "french_to_english_month: should covert french month mai to english" do
@@ -334,4 +346,52 @@ class StatementsHelperTest < ActionView::TestCase
expected_output = ["The Show Reporté - again", "EventStatusType", ["EventRescheduled", "http://schema.org/EventRescheduled"]]
assert_equal expected_output, reconcile_event_status(scraped_data)
end
+
+ test "pipeline ok + missing yields extraction hint" do
+ diagnosis = { status: :ok }
+ hint = TracePresenter.new([]).diagnosis_relationship_hint(diagnosis, "missing")
+
+ assert_match(/no data extracted/i, hint)
+ end
+
+ test "pipeline error + missing yields failure hint" do
+ diagnosis = { status: :error }
+ hint = TracePresenter.new([]).diagnosis_relationship_hint(diagnosis, "missing")
+
+ assert_match(/pipeline failure/i, hint)
+ end
+
+ test "pipeline ok + problem yields content warning hint" do
+ diagnosis = { status: :ok }
+ hint = TracePresenter.new([]).diagnosis_relationship_hint(diagnosis, "problem")
+
+ assert_match(/problematic/i, hint)
+ end
+
+ test "first empty step is detected" do
+ steps = [
+ { step: 1, output_full: ["a"], type: "xpath" },
+ { step: 2, output_full: [], type: "xpath" }
+ ]
+
+ diagnosis = { status: :ok }
+
+ hint = TracePresenter.new([]).diagnosis_relationship_hint(diagnosis, "missing", steps)
+
+ assert_match(/step 2/i, hint)
+ end
+
+ test "freshness label for recent cache" do
+ statement = OpenStruct.new(cache_refreshed: Time.current - 1800)
+ label = cache_freshness_label(statement)
+
+ assert_equal "fresh", label
+ end
+
+ test "freshness label for old cache" do
+ statement = OpenStruct.new(cache_refreshed: Time.current - 40.days)
+ label = cache_freshness_label(statement)
+
+ assert_equal "stale", label
+ end
end
diff --git a/test/helpers/webpages_helper_test.rb b/test/helpers/webpages_helper_test.rb
new file mode 100644
index 00000000..6e9235d7
--- /dev/null
+++ b/test/helpers/webpages_helper_test.rb
@@ -0,0 +1,43 @@
+require "test_helper"
+
+class WebpagesHelperTest < ActionView::TestCase
+ test "webpages summary heading uses website scoped totals and filter labels" do
+ website = Website.create!(
+ name: "Helper webpage site",
+ seedurl: "helper-webpage-site",
+ graph_name: "https://example.org/helper-webpage-site",
+ default_language: "en"
+ )
+
+ heading = webpages_index_summary_heading(
+ website: website,
+ visible_count: 1,
+ filtered_count: 1,
+ total_count: 7,
+ filters: { publishable: "true" }
+ )
+
+ assert_equal "Showing 1 publishable event page for this website.", heading
+ end
+
+ test "webpages summary heading pluralizes publishable event page copy" do
+ heading = webpages_index_summary_heading(
+ website: websites(:one),
+ visible_count: 2,
+ filtered_count: 2,
+ total_count: 7,
+ filters: {}
+ )
+
+ assert_equal "Showing 2 publishable event pages for this website.", heading
+ end
+
+ test "webpages filter label falls back to matching for term filters" do
+ assert_equal "matching", webpages_filter_label(term: "festival")
+ end
+
+ test "selected rdfs class id resolves named class filters" do
+ assert_equal rdfs_classes(:one).id, webpages_selected_rdfs_class_id(rdfs_class: "Event")
+ assert_nil webpages_selected_rdfs_class_id(rdfs_class: "Other")
+ end
+end
diff --git a/test/helpers/websites_helper_test.rb b/test/helpers/websites_helper_test.rb
new file mode 100644
index 00000000..fe800809
--- /dev/null
+++ b/test/helpers/websites_helper_test.rb
@@ -0,0 +1,281 @@
+require "test_helper"
+
+class WebsitesHelperTest < ActionView::TestCase
+ test "legacy contract exposes move to shadow only" do
+ website = build_website("legacy", seedurl: "helper-legacy")
+ Distillator::TransitionRuntime.stubs(:allow_active_override?).returns(false)
+
+ contract = website_transition_contract(website)
+
+ assert_equal "legacy", contract[:current_mode]
+ assert_equal "Move to shadow", contract[:next_action_label]
+ assert_equal "shadow", contract[:next_mode]
+ assert_equal true, contract[:action_enabled]
+ assert_equal false, contract[:override_allowed]
+ assert_equal false, contract[:rollback_available]
+ end
+
+ test "shadow ready contract exposes promote to active" do
+ website = ready_shadow_website(seedurl: "helper-shadow-ready")
+
+ contract = website_transition_contract(website)
+
+ assert_equal "shadow", contract[:current_mode]
+ assert_equal "Promote to active", contract[:next_action_label]
+ assert_equal "active", contract[:next_mode]
+ assert_equal true, contract[:action_enabled]
+ assert_equal [], contract[:blockers]
+ end
+
+ test "shadow blocked contract exposes disabled promotion and blockers" do
+ website = blocked_shadow_website(seedurl: "helper-shadow-blocked")
+
+ contract = website_transition_contract(website)
+
+ assert_equal "shadow", contract[:current_mode]
+ assert_equal "Cannot promote yet", contract[:next_action_label]
+ assert_equal "active", contract[:next_mode]
+ assert_equal false, contract[:action_enabled]
+ assert_includes contract[:blockers], "Cannot activate yet: statements check failed."
+ end
+
+ test "shadow review contract exposes activate after review instead of promote or activate anyway" do
+ website = build_website("shadow", seedurl: "helper-shadow-review")
+ url = "https://example.org/helper-shadow-review/event"
+ website.webpages.create!(url: url, language: "en", rdf_uri: "rdf:helper-shadow-review", rdfs_class: rdfs_classes(:one))
+ Distillator::FetchCache.create!(
+ uri_key: CGI.escape(url),
+ normalized_url: url,
+ html: "ok",
+ body: "ok",
+ scrape_date: 1.hour.ago,
+ successful_refresh: 1.hour.ago,
+ headers: {},
+ signals: { "transport_success" => true, "content_success" => true },
+ final_url: url
+ )
+ website.transition_evidences.create!(url: url, check_kind: "statement_delta", status: "checked", statement_count_delta_acceptable: true, checked_at: 1.hour.ago)
+ website.transition_evidences.create!(url: url, check_kind: "export_diff", status: "checked", export_diff_checked: true, checked_at: 1.hour.ago)
+ website.transition_evidences.create!(
+ url: url,
+ check_kind: "fetch_parity",
+ status: "checked",
+ checked_at: 1.hour.ago,
+ details: { reason: "review_needed_difference", comparison_policy: "operator", compare_summary: { review_needed_diffs: %w[html_sha256] } }
+ )
+
+ contract = website_transition_contract(website)
+
+ assert_equal "Review before activating", contract[:next_action_label]
+ assert_equal false, contract[:action_enabled]
+ assert_empty contract[:secondary_actions].select { |action| action[:kind] == :button }
+ assert_equal ["Activate after review"], contract[:secondary_actions].select { |action| action[:kind] == :override }.map { |action| action[:label] }
+ end
+
+ test "shadow body omitted contract does not expose activate after review" do
+ website = build_website("shadow", seedurl: "helper-shadow-body-omitted")
+ url = "https://example.org/helper-shadow-body-omitted/event"
+ website.webpages.create!(url: url, language: "en", rdf_uri: "rdf:helper-shadow-body-omitted", rdfs_class: rdfs_classes(:one))
+ Distillator::FetchCache.create!(
+ uri_key: CGI.escape(url),
+ normalized_url: url,
+ html: "ok",
+ body: "ok",
+ scrape_date: 1.hour.ago,
+ successful_refresh: 1.hour.ago,
+ headers: {},
+ signals: { "transport_success" => true, "content_success" => true },
+ final_url: url
+ )
+ website.transition_evidences.create!(url: url, check_kind: "statement_delta", status: "checked", statement_count_delta_acceptable: true, checked_at: 1.hour.ago)
+ website.transition_evidences.create!(url: url, check_kind: "export_diff", status: "checked", export_diff_checked: true, checked_at: 1.hour.ago)
+ website.transition_evidences.create!(
+ url: url,
+ check_kind: "fetch_parity",
+ status: "checked",
+ checked_at: 1.hour.ago,
+ details: { reason: "legacy_lookup_body_omitted", comparison_policy: "operator" }
+ )
+
+ contract = website_transition_contract(website)
+
+ assert_equal "Cannot promote yet", contract[:next_action_label]
+ assert_empty contract[:secondary_actions].select { |action| action[:kind] == :override && action[:label] == "Activate after review" }
+ end
+
+ test "active contract exposes rollback" do
+ website = build_website("active", seedurl: "helper-active")
+
+ contract = website_transition_contract(website)
+
+ assert_equal "active", contract[:current_mode]
+ assert_equal "Rollback to Legacy Wringer", contract[:next_action_label]
+ assert_equal "legacy", contract[:next_mode]
+ assert_equal true, contract[:action_enabled]
+ assert_equal true, contract[:rollback_available]
+ end
+
+ test "website webpage summary cell links to scoped webpages filters" do
+ website = build_website("legacy", seedurl: "summary-cell")
+ summary = {
+ total: 7,
+ public_urls: 4,
+ internal_uris: 3,
+ by_class: {
+ "Event" => 2,
+ "Person" => 1,
+ "Place" => 1,
+ "ResourceList" => 1,
+ "WebPage" => 1,
+ "Other" => 1
+ },
+ publishable: 1,
+ not_publishable: 6
+ }
+
+ html = website_webpage_summary_cell(website, summary)
+
+ assert_includes html, "/webpages?seedurl=summary-cell"
+ assert_includes html, "/webpages?seedurl=summary-cell&url_kind=public"
+ assert_includes html, "/webpages?rdfs_class=Event&seedurl=summary-cell"
+ assert_match %r{/webpages\?(publishable=true&seedurl=summary-cell|seedurl=summary-cell&publishable=true)}, html
+ assert_includes html, "E2"
+ assert_includes html, "6 not publishable"
+ end
+
+ test "transition status summary reports readiness and check states" do
+ website = ready_shadow_website(seedurl: "summary-transition-ready")
+
+ summary = website_transition_status_summary(website)
+
+ assert_includes summary, "Ready"
+ assert_includes summary, "fetch passed"
+ assert_includes summary, "statements passed"
+ assert_includes summary, "export passed"
+ end
+
+ test "transition verdict line renders compact blocker text once" do
+ website = blocked_shadow_website(seedurl: "summary-transition-blocked-verdict")
+
+ assert_equal "Blocked: statements check failed.", website_transition_verdict_line(website)
+ end
+
+ test "transition secondary status line uses statements export fetch order" do
+ website = ready_shadow_website(seedurl: "summary-transition-secondary-order")
+
+ assert_equal "Statements passed | Export passed | Fetch passed", website_transition_secondary_status_line(website)
+ end
+
+ test "transition evidence summary mentions recorded evidence and report path" do
+ website = ready_shadow_website(seedurl: "summary-transition-evidence")
+
+ summary = website_transition_evidence_summary(website)
+ path = website_transition_report_path(website)
+
+ assert_includes summary, "Fetch parity checked"
+ assert_includes summary, "Statement coverage checked."
+ assert_includes summary, "Export comparison checked."
+ assert_equal distillator_shadow_report_site_path(website, anchor: "transition-report-summary"), path
+ end
+
+ test "transition evidence freshness text shows latest timestamp when evidence exists" do
+ website = ready_shadow_website(seedurl: "summary-transition-freshness")
+
+ text = website_transition_evidence_freshness_text(website)
+
+ assert_includes text, "Latest transition evidence:"
+ refute_includes text, "Waiting for new transition evidence"
+ end
+
+ test "transition evidence freshness text shows not recorded when no evidence exists" do
+ website = build_website("shadow", seedurl: "summary-transition-no-evidence")
+
+ assert_equal "Latest transition evidence: Not recorded yet.", website_transition_evidence_freshness_text(website)
+ end
+
+ test "transition evidence freshness text prefers pending message over stale timestamp" do
+ website = ready_shadow_website(seedurl: "summary-transition-pending")
+ website.update!(transition_check_requested_at: 5.minutes.ago)
+ website.transition_evidences.update_all(checked_at: 10.minutes.ago)
+
+ assert_equal "Batch check requested. Waiting for new transition evidence.", website_transition_evidence_freshness_text(website.reload)
+ end
+
+ test "transition drill down links render report cache and webpage targets once" do
+ website = ready_shadow_website(seedurl: "summary-transition-drilldown")
+
+ links = website_transition_drill_down_links(website)
+
+ assert_equal 3, links.length
+ assert_includes links.first, "Latest transition report"
+ assert_includes links.second, "Open cache diagnostics"
+ assert_includes links.third, "Inspect publishable event pages"
+ end
+
+ test "website transition secondary actions do not expose run transition check labels" do
+ website = ready_shadow_website(seedurl: "summary-transition-secondary-actions")
+
+ contract = website_transition_contract(website)
+
+ refute_includes contract[:secondary_actions].map { |action| action[:label] }, "Run transition check"
+ refute_includes contract[:secondary_actions].map { |action| action[:label] }, "Run transition check again"
+ end
+
+ private
+
+ def build_website(mode, seedurl:)
+ Website.create!(
+ name: "Helper website #{seedurl}",
+ seedurl: seedurl,
+ graph_name: "https://example.org/#{seedurl}",
+ default_language: "en",
+ distillator_mode: mode
+ )
+ end
+
+ def ready_shadow_website(seedurl:)
+ website = build_website("shadow", seedurl: seedurl)
+ url = "https://example.org/#{seedurl}/event"
+ website.webpages.create!(url: url, language: "en", rdf_uri: "rdf:#{seedurl}", rdfs_class: rdfs_classes(:one))
+ Distillator::FetchCache.create!(
+ uri_key: CGI.escape(url),
+ normalized_url: url,
+ html: "ok",
+ body: "ok",
+ scrape_date: 1.hour.ago,
+ successful_refresh: 1.hour.ago,
+ headers: {},
+ signals: { "transport_success" => true, "content_success" => true },
+ final_url: url
+ )
+ website.transition_evidences.create!(url: url, check_kind: "fetch_parity", status: "checked", details: { representative_urls_checked: true }, checked_at: 1.hour.ago)
+ website.transition_evidences.create!(url: url, check_kind: "statement_delta", status: "checked", statement_count_delta_acceptable: true, checked_at: 1.hour.ago)
+ website.transition_evidences.create!(url: url, check_kind: "export_diff", status: "checked", export_diff_checked: true, checked_at: 1.hour.ago)
+ website
+ end
+
+ def blocked_shadow_website(seedurl:)
+ website = build_website("shadow", seedurl: seedurl)
+ url = "https://example.org/#{seedurl}/event"
+ website.webpages.create!(url: url, language: "en", rdf_uri: "rdf:#{seedurl}", rdfs_class: rdfs_classes(:one))
+ Distillator::FetchCache.create!(
+ uri_key: CGI.escape(url),
+ normalized_url: url,
+ html: "ok",
+ body: "ok",
+ scrape_date: 1.hour.ago,
+ successful_refresh: 1.hour.ago,
+ headers: {},
+ signals: { "transport_success" => true, "content_success" => true },
+ final_url: url
+ )
+ website.transition_evidences.create!(
+ url: url,
+ check_kind: "statement_delta",
+ status: "failed",
+ statement_count_delta_acceptable: false,
+ checked_at: 1.hour.ago
+ )
+ website
+ end
+end
diff --git a/test/integration/distillator_json_post_export_test.rb b/test/integration/distillator_json_post_export_test.rb
new file mode 100644
index 00000000..f1f66f71
--- /dev/null
+++ b/test/integration/distillator_json_post_export_test.rb
@@ -0,0 +1,50 @@
+require "test_helper"
+require Rails.root.join("test/support/distillator_integration_event_factory")
+
+class DistillatorJsonPostExportTest < ActionDispatch::IntegrationTest
+ setup do
+ @old_fetch_mode = ENV["DISTILLATOR_FETCH_MODE"]
+ ENV["DISTILLATOR_FETCH_MODE"] = "internal"
+ Distillator::FetchCache.delete_all
+ end
+
+ teardown do
+ ENV["DISTILLATOR_FETCH_MODE"] = @old_fetch_mode
+ end
+
+ test "json_post source uses legacy fallback metadata and exports parsed json value" do
+ website, event_page, title_statement = build_publishable_event(
+ seedurl: "distillator-json-post-export",
+ rdf_uri: "footlight:json-post-event",
+ algorithm_value: "post_url=$url;json=$json.dig('name')",
+ render_js: false,
+ initial_title: "Old JSON Post Title",
+ json_post: true
+ )
+
+ Distillator::LegacyWringerFetch.expects(:fetch).once.returns(
+ status: :ok,
+ body: '{"name":"JSON Post Export Title"}',
+ raw_body: '{"name":"JSON Post Export Title"}',
+ headers: { content_type: "application/json" },
+ final_url: event_page.url,
+ redirect_chain: [event_page.url],
+ wringer: { signals: { network_status: "ok", content_type: "json" }, hints: ["json_detected"] },
+ http_code: 200
+ )
+
+ patch refresh_statement_path(title_statement)
+
+ assert_redirected_to statement_url(title_statement)
+ assert_equal "JSON Post Export Title", title_statement.reload.cache
+ cache = Distillator::FetchCache.find_by(normalized_url: event_page.url)
+ assert_equal "legacy", cache.signals["fetch_path"]
+ assert_equal "json", cache.signals["content_type"]
+ assert_includes cache.hints, "json_detected"
+ assert_includes ExportArtsdataService.call(seedurl: website.seedurl), "JSON Post Export Title"
+ end
+
+ private
+
+ include DistillatorIntegrationEventFactory
+end
diff --git a/test/integration/distillator_json_post_fallback_test.rb b/test/integration/distillator_json_post_fallback_test.rb
new file mode 100644
index 00000000..a0cda22b
--- /dev/null
+++ b/test/integration/distillator_json_post_fallback_test.rb
@@ -0,0 +1,60 @@
+require "test_helper"
+require Rails.root.join("test/support/distillator_integration_event_factory")
+
+class DistillatorJsonPostFallbackTest < ActionDispatch::IntegrationTest
+ include DistillatorIntegrationEventFactory
+
+ setup do
+ @old_fetch_mode = ENV["DISTILLATOR_FETCH_MODE"]
+ ENV["DISTILLATOR_FETCH_MODE"] = "legacy"
+ Distillator::FetchCache.delete_all
+ Distillator::FetchGuard.stubs(:check_url).returns(Distillator::FetchGuard::Result.new(allowed: true))
+ Distillator::FetchGuard.stubs(:check_response).returns(Distillator::FetchGuard::Result.new(allowed: true))
+ end
+
+ teardown do
+ ENV["DISTILLATOR_FETCH_MODE"] = @old_fetch_mode
+ end
+
+ test "active website post_url refresh uses native json post and caches post metadata" do
+ website, event_page, title_statement = build_publishable_event(
+ seedurl: "distillator-json-post-fallback",
+ rdf_uri: "footlight:json-post-fallback-event",
+ algorithm_value: "post_url=$url;json=$json.dig('name')",
+ render_js: false,
+ initial_title: "Old Native JSON Post Title",
+ json_post: true
+ )
+ website.update!(distillator_mode: "active")
+
+ Distillator::NativeFetch.expects(:call).once.with do |kwargs|
+ assert_equal event_page.url, kwargs[:url]
+ assert_equal false, kwargs[:render_js]
+ assert_equal true, kwargs[:scrape_options][:json_post]
+ true
+ end.returns(
+ status: :ok,
+ body: '{"name":"Native JSON Post Title"}',
+ raw_body: '{"name":"Native JSON Post Title"}',
+ headers: { content_type: "application/json" },
+ final_url: event_page.url,
+ redirect_chain: [event_page.url],
+ wringer: {
+ signals: { network_status: "ok", content_type: "json", request_method: "POST" },
+ hints: ["json_detected"]
+ },
+ http_code: 200
+ )
+
+ patch refresh_statement_path(title_statement)
+
+ assert_redirected_to statement_url(title_statement)
+ assert_equal "Native JSON Post Title", title_statement.reload.cache
+ cache = Distillator::FetchCache.find_by!(normalized_url: event_page.url)
+ assert_equal "native", cache.signals["fetch_path"]
+ assert_equal "POST", cache.signals["request_method"]
+ assert_equal "json", cache.signals["content_type"]
+ assert_includes cache.hints, "json_detected"
+ assert_includes ExportArtsdataService.call(seedurl: website.seedurl), "Native JSON Post Title"
+ end
+end
diff --git a/test/integration/distillator_manual_link_export_test.rb b/test/integration/distillator_manual_link_export_test.rb
new file mode 100644
index 00000000..da924767
--- /dev/null
+++ b/test/integration/distillator_manual_link_export_test.rb
@@ -0,0 +1,85 @@
+require "test_helper"
+
+class DistillatorManualLinkExportTest < ActionDispatch::IntegrationTest
+ test "manual linked data is export-visible and manual ok statements are not overwritten by refresh" do
+ website = Website.create!(
+ name: "Manual Link Fixture",
+ seedurl: "distillator-manual-link-export",
+ graph_name: "https://fixtures.example/manual-link-export",
+ default_language: "en"
+ )
+ event_page = Webpage.create!(
+ website: website,
+ rdfs_class: rdfs_classes(:one),
+ url: "https://fixtures.example/manual-link-export/event",
+ rdf_uri: "footlight:manual-link-event",
+ language: "en",
+ archive_date: Time.zone.parse("2026-06-01T00:00:00Z")
+ )
+ org_page = Webpage.create!(
+ website: website,
+ rdfs_class: rdfs_classes(:organization),
+ url: "https://fixtures.example/manual-link-export/org",
+ rdf_uri: "footlight:manual-link-org",
+ language: "en",
+ archive_date: Time.zone.parse("2026-06-01T00:00:00Z")
+ )
+
+ create_statement_with_source!(website:, webpage: org_page, property: properties(:organizationName), algorithm_value: "manual=Venue Org", cache: "Venue Org", status: "ok")
+ create_statement_with_source!(website:, webpage: event_page, property: properties(:four), algorithm_value: "manual=Manual Link Event", cache: "Manual Link Event", status: "ok", language: "en")
+ create_statement_with_source!(website:, webpage: event_page, property: properties(:ten), algorithm_value: "manual=2026-05-10", cache: "[\"2026-05-10T19:30:00-04:00\"]", status: "ok")
+ create_statement_with_source!(website:, webpage: event_page, property: properties(:location), algorithm_value: "manual=Main Hall", cache: "[\"Main Hall\",\"Place\",[\"Main Hall\",\"footlight:manual-link-place\"]]", status: "ok")
+
+ performer_statement = create_statement_with_source!(
+ website: website,
+ webpage: event_page,
+ property: properties(:nine),
+ algorithm_value: "manual=Venue Org",
+ cache: "[\"Manually added\",\"Organization\",[\"Venue Org\",\"footlight:manual-link-org\"]]",
+ status: "ok",
+ manual: true
+ )
+
+ export = ExportArtsdataService.call(seedurl: website.seedurl)
+ assert_includes export, "manual-link-org"
+
+ result = ApplicationController.helpers.refresh_statement_helper(performer_statement)
+ assert_includes result[:errors].join(" "), "No update unless status is 'initial', 'problem', or 'missing'."
+ assert_equal "[\"Manually added\",\"Organization\",[\"Venue Org\",\"footlight:manual-link-org\"]]", performer_statement.reload.cache
+
+ performer_statement.update!(
+ cache: "[\"Manually deleted\",\"Organization\",[\"Venue Org\",\"footlight:manual-link-org\"]]",
+ status: "updated",
+ status_origin: "manual_test"
+ )
+
+ export_after_delete = ExportArtsdataService.call(seedurl: website.seedurl)
+ refute_includes export_after_delete, "manual-link-org"
+ end
+
+ private
+
+ def create_statement_with_source!(website:, webpage:, property:, algorithm_value:, cache:, status:, language: nil, manual: false)
+ source = Source.create!(
+ website: website,
+ property: property,
+ algorithm_value: algorithm_value,
+ selected: true,
+ selected_by: "test",
+ render_js: false,
+ language: language
+ )
+
+ Statement.create!(
+ webpage: webpage,
+ source: source,
+ cache: cache,
+ status: status,
+ status_origin: "test",
+ cache_refreshed: 1.day.ago,
+ cache_changed: 1.day.ago,
+ selected_individual: true,
+ manual: manual
+ )
+ end
+end
diff --git a/test/integration/distillator_refresh_rdf_uri_cache_test.rb b/test/integration/distillator_refresh_rdf_uri_cache_test.rb
new file mode 100644
index 00000000..185fa765
--- /dev/null
+++ b/test/integration/distillator_refresh_rdf_uri_cache_test.rb
@@ -0,0 +1,56 @@
+require "test_helper"
+
+class DistillatorRefreshRdfUriCacheTest < ActionDispatch::IntegrationTest
+ setup do
+ @old_fetch_mode = ENV["DISTILLATOR_FETCH_MODE"]
+ ENV["DISTILLATOR_FETCH_MODE"] = "internal"
+ Distillator::FetchCache.delete_all
+ end
+
+ teardown do
+ ENV["DISTILLATOR_FETCH_MODE"] = @old_fetch_mode
+ end
+
+ test "refresh_rdf_uri with force_scrape_every_hrs writes raw distillator cache and refreshes statement cache" do
+ webpage = webpages(:culture3r_refresh_fixture)
+ webpage.website.update!(distillator_mode: "active")
+ statement = statements(:culture3r_refresh_statement)
+ previous_refreshed = statement.cache_refreshed
+ previous_changed = statement.cache_changed
+ html = "Gabrielle Caron Rodage "
+
+ Distillator::FetchGuard.stubs(:check_url).returns(Distillator::FetchGuard::Result.new(allowed: true))
+ Distillator::FetchGuard.stubs(:check_response).returns(Distillator::FetchGuard::Result.new(allowed: true))
+ Distillator::NativeFetch.expects(:call).once.returns(
+ status: :ok,
+ body: html,
+ raw_body: html,
+ headers: { content_type: "text/html" },
+ final_url: webpage.url,
+ redirect_chain: [webpage.url],
+ wringer: { signals: {}, hints: [] },
+ http_code: 200
+ )
+
+ patch refresh_rdf_uri_statements_path(format: :json), params: {
+ rdf_uri: webpage.rdf_uri,
+ force_scrape_every_hrs: "1"
+ }
+
+ assert_response :success
+ statement.reload
+ cache = Distillator::FetchCache.find_by(normalized_url: webpage.url)
+
+ assert_equal "Gabrielle Caron Rodage", statement.cache
+ assert_operator statement.cache_refreshed, :>, previous_refreshed
+ assert_operator statement.cache_changed, :>, previous_changed
+ assert cache.present?, "expected Distillator::FetchCache for #{webpage.url}"
+ assert_equal html, cache.html
+ assert_equal html, cache.body
+ assert_equal 200, cache.http_response_code
+ assert_equal [webpage.url], cache.redirect_chain
+ assert_equal({ "content_type" => "text/html" }, cache.headers)
+ assert_operator cache.scrape_date, :>, Time.zone.parse("2026-04-20 16:46:00 UTC")
+ assert_operator cache.successful_refresh, :>, Time.zone.parse("2026-04-20 16:46:00 UTC")
+ end
+end
diff --git a/test/integration/distillator_refresh_then_export_test.rb b/test/integration/distillator_refresh_then_export_test.rb
new file mode 100644
index 00000000..9c24d866
--- /dev/null
+++ b/test/integration/distillator_refresh_then_export_test.rb
@@ -0,0 +1,146 @@
+require "test_helper"
+
+class DistillatorRefreshThenExportTest < ActionDispatch::IntegrationTest
+ setup do
+ @old_fetch_mode = ENV["DISTILLATOR_FETCH_MODE"]
+ ENV["DISTILLATOR_FETCH_MODE"] = "internal"
+ Distillator::FetchCache.delete_all
+ end
+
+ teardown do
+ ENV["DISTILLATOR_FETCH_MODE"] = @old_fetch_mode
+ end
+
+ test "native refresh updates statement cache and export uses refreshed value" do
+ website = Website.create!(
+ name: "Refresh Export Fixture",
+ seedurl: "distillator-refresh-export",
+ graph_name: "https://fixtures.example/refresh-export",
+ default_language: "en",
+ distillator_mode: "active"
+ )
+ event_page = Webpage.create!(
+ website: website,
+ rdfs_class: rdfs_classes(:one),
+ url: "https://fixtures.example/refresh-export/event",
+ rdf_uri: "footlight:refresh-export-event",
+ language: "en",
+ archive_date: Time.zone.parse("2026-06-01T00:00:00Z")
+ )
+ place_page = Webpage.create!(
+ website: website,
+ rdfs_class: rdfs_classes(:place),
+ url: "https://fixtures.example/refresh-export/place",
+ rdf_uri: "footlight:refresh-export-place",
+ language: "en",
+ archive_date: Time.zone.parse("2026-06-01T00:00:00Z")
+ )
+
+ place_source = Source.create!(
+ algorithm_value: "manual=Main Hall",
+ selected: true,
+ selected_by: "test",
+ render_js: false,
+ property: properties(:two),
+ website: website
+ )
+ Statement.create!(
+ cache: "Main Hall",
+ status: "ok",
+ status_origin: "test",
+ cache_refreshed: 1.day.ago,
+ cache_changed: 1.day.ago,
+ source: place_source,
+ webpage: place_page,
+ selected_individual: true
+ )
+
+ title_source = Source.create!(
+ algorithm_value: "xpath=//h1/text()",
+ selected: true,
+ selected_by: "test",
+ language: "en",
+ render_js: false,
+ property: properties(:four),
+ website: website
+ )
+ dates_source = Source.create!(
+ algorithm_value: "manual=dates",
+ selected: true,
+ selected_by: "test",
+ render_js: false,
+ property: properties(:ten),
+ website: website
+ )
+ location_source = Source.create!(
+ algorithm_value: "manual=location",
+ selected: true,
+ selected_by: "test",
+ render_js: false,
+ property: properties(:location),
+ website: website
+ )
+
+ title_statement = Statement.create!(
+ cache: "Old Export Title",
+ status: "initial",
+ status_origin: "test",
+ cache_refreshed: 1.day.ago,
+ cache_changed: 1.day.ago,
+ source: title_source,
+ webpage: event_page,
+ selected_individual: true
+ )
+ previous_refreshed = title_statement.cache_refreshed
+ previous_changed = title_statement.cache_changed
+ Statement.create!(
+ cache: "[\"2026-05-10T19:30:00-04:00\"]",
+ status: "ok",
+ status_origin: "test",
+ cache_refreshed: 1.day.ago,
+ cache_changed: 1.day.ago,
+ source: dates_source,
+ webpage: event_page,
+ selected_individual: true
+ )
+ Statement.create!(
+ cache: "[\"Main Hall\",\"Place\",[\"Main Hall\",\"footlight:refresh-export-place\"]]",
+ status: "ok",
+ status_origin: "test",
+ cache_refreshed: 1.day.ago,
+ cache_changed: 1.day.ago,
+ source: location_source,
+ webpage: event_page,
+ selected_individual: true
+ )
+
+ html = "Refreshed Export Title "
+ Distillator::FetchGuard.stubs(:check_url).returns(Distillator::FetchGuard::Result.new(allowed: true))
+ Distillator::FetchGuard.stubs(:check_response).returns(Distillator::FetchGuard::Result.new(allowed: true))
+ Distillator::NativeFetch.expects(:call).once.returns(
+ status: :ok,
+ body: html,
+ raw_body: html,
+ headers: { content_type: "text/html" },
+ final_url: event_page.url,
+ redirect_chain: [event_page.url],
+ wringer: { signals: {}, hints: [] },
+ http_code: 200
+ )
+
+ patch refresh_rdf_uri_statements_path(format: :json), params: {
+ rdf_uri: event_page.rdf_uri,
+ force_scrape_every_hrs: "0"
+ }
+
+ assert_response :success
+ assert_equal "Refreshed Export Title", title_statement.reload.cache
+ assert_operator title_statement.cache_refreshed, :>, previous_refreshed
+ assert_operator title_statement.cache_changed, :>, previous_changed
+ export = ExportArtsdataService.call(seedurl: website.seedurl)
+
+ assert_includes export, "Refreshed Export Title"
+ refute_includes export, "Old Export Title"
+ assert Distillator::FetchCache.find_by(normalized_url: event_page.url).present?
+ end
+end
diff --git a/test/integration/distillator_render_js_export_test.rb b/test/integration/distillator_render_js_export_test.rb
new file mode 100644
index 00000000..a9862826
--- /dev/null
+++ b/test/integration/distillator_render_js_export_test.rb
@@ -0,0 +1,47 @@
+require "test_helper"
+require Rails.root.join("test/support/distillator_integration_event_factory")
+
+class DistillatorRenderJsExportTest < ActionDispatch::IntegrationTest
+ setup do
+ @old_fetch_mode = ENV["DISTILLATOR_FETCH_MODE"]
+ ENV["DISTILLATOR_FETCH_MODE"] = "internal"
+ Distillator::FetchCache.delete_all
+ end
+
+ teardown do
+ ENV["DISTILLATOR_FETCH_MODE"] = @old_fetch_mode
+ end
+
+ test "render_js source uses legacy fallback metadata and exports refreshed value" do
+ website, event_page, title_statement = build_publishable_event(
+ seedurl: "distillator-render-js-export",
+ rdf_uri: "footlight:render-js-event",
+ algorithm_value: "xpath=//h1/text()",
+ render_js: true,
+ initial_title: "Old Render JS Title"
+ )
+
+ Distillator::LegacyWringerFetch.expects(:fetch).once.returns(
+ status: :ok,
+ body: "Rendered Export Title ",
+ raw_body: "Rendered Export Title ",
+ headers: { content_type: "text/html" },
+ final_url: event_page.url,
+ redirect_chain: [event_page.url],
+ wringer: { signals: { network_status: "ok" }, hints: [] },
+ http_code: 200
+ )
+
+ patch refresh_statement_path(title_statement)
+
+ assert_redirected_to statement_url(title_statement)
+ assert_equal "Rendered Export Title", title_statement.reload.cache
+ cache = Distillator::FetchCache.find_by(normalized_url: event_page.url)
+ assert_equal "legacy", cache.signals["fetch_path"]
+ assert_includes ExportArtsdataService.call(seedurl: website.seedurl), "Rendered Export Title"
+ end
+
+ private
+
+ include DistillatorIntegrationEventFactory
+end
diff --git a/test/integration/distillator_rollout_rollback_test.rb b/test/integration/distillator_rollout_rollback_test.rb
new file mode 100644
index 00000000..c39d554f
--- /dev/null
+++ b/test/integration/distillator_rollout_rollback_test.rb
@@ -0,0 +1,61 @@
+require "test_helper"
+
+class DistillatorRolloutRollbackTest < ActionDispatch::IntegrationTest
+ test "active to legacy rollback keeps read only inspection and backend semantics safe" do
+ ApplicationController.helpers.stubs(:get_wringer_url_per_environment).returns("http://wringer.example")
+ website = Website.create!(
+ name: "Rollback site",
+ seedurl: "rollback-site",
+ graph_name: "https://example.org/rollback-site",
+ default_language: "en",
+ distillator_mode: "active"
+ )
+
+ payload = Distillator::CacheLinkResolver.call(url: "http://example.org/page", website: website)
+ assert_equal :condenser, payload[:active_backend]
+ assert_equal payload[:distillator_cache_url], payload[:active_cache_url]
+
+ patch website_url(website), params: { website: { distillator_mode: "legacy" } }
+
+ assert_redirected_to website_url(website)
+ website.reload
+ assert_equal "legacy", website.distillator_mode
+
+ payload = Distillator::CacheLinkResolver.call(url: "http://example.org/page", website: website)
+ assert_equal :wringer, payload[:active_backend]
+ assert_equal payload[:legacy_cache_url], payload[:active_cache_url]
+ assert_equal "Open Condenser cache", payload[:secondary_links].first[:label]
+
+ Distillator::FetchCacheStore.expects(:fetch).never
+ Distillator::FetchService.expects(:fetch).never
+ Distillator::NativeFetch.expects(:call).never
+ Distillator::FetchShadowComparator.expects(:call).never
+
+ get website_url(website)
+ assert_response :success
+ end
+
+ test "active transition detail page shows rollback guidance and remains read only" do
+ ApplicationController.helpers.stubs(:get_wringer_url_per_environment).returns("http://wringer.example")
+ website = Website.create!(
+ name: "Rollback detail site",
+ seedurl: "rollback-detail-site",
+ graph_name: "https://example.org/rollback-detail-site",
+ default_language: "en",
+ distillator_mode: "active"
+ )
+
+ Distillator::FetchCacheStore.expects(:fetch).never
+ Distillator::FetchService.expects(:fetch).never
+ Distillator::NativeFetch.expects(:call).never
+ Distillator::FetchShadowComparator.expects(:call).never
+
+ get distillator_shadow_report_site_path(website)
+
+ assert_response :success
+ assert_includes @response.body, "Rollback path"
+ assert_includes @response.body, "Set rollout mode to Legacy in website options."
+ assert_includes @response.body, "Wringer becomes production backend."
+ assert_includes @response.body, "Condenser cache remains available for inspection."
+ end
+end
diff --git a/test/integration/distillator_rollout_statement_refresh_test.rb b/test/integration/distillator_rollout_statement_refresh_test.rb
new file mode 100644
index 00000000..8bb7e514
--- /dev/null
+++ b/test/integration/distillator_rollout_statement_refresh_test.rb
@@ -0,0 +1,187 @@
+require "test_helper"
+
+class DistillatorRolloutStatementRefreshTest < ActionDispatch::IntegrationTest
+ class CapturingLogger < ::Logger
+ attr_reader :infos, :warnings, :debugs
+
+ def initialize
+ super(StringIO.new)
+ @infos = []
+ @warnings = []
+ @debugs = []
+ end
+
+ def info(payload = nil, &block)
+ payload = block.call if block
+ @infos << payload
+ super(payload)
+ end
+
+ def warn(payload = nil, &block)
+ payload = block.call if block
+ @warnings << payload
+ super(payload)
+ end
+
+ def debug(payload = nil, &block)
+ payload = block.call if block
+ @debugs << payload
+ super(payload)
+ end
+ end
+
+ setup do
+ Distillator::FetchCache.delete_all
+ @old_fetch_mode = ENV["DISTILLATOR_FETCH_MODE"]
+ ENV["DISTILLATOR_FETCH_MODE"] = "legacy"
+ Distillator::FetchGuard.stubs(:check_url).returns(Distillator::FetchGuard::Result.new(allowed: true))
+ Distillator::FetchGuard.stubs(:check_response).returns(Distillator::FetchGuard::Result.new(allowed: true))
+ Distillator::WringerEndpoint.stubs(:current).returns(
+ Distillator::WringerEndpoint::Result.new(
+ compatibility_base_url: "https://wringer.example",
+ legacy_lookup_base_url: "https://wringer.example",
+ compatibility_source: Distillator::WringerEndpoint::CANONICAL_COMPATIBILITY_ENV,
+ state: :remote_configured,
+ status_label: "Current Wringer: Remote configured",
+ status_detail: "https://wringer.example"
+ )
+ )
+ @logger = CapturingLogger.new
+ Rails.stubs(:logger).returns(@logger)
+ end
+
+ teardown do
+ ENV["DISTILLATOR_FETCH_MODE"] = @old_fetch_mode
+ end
+
+ test "active website refresh uses Distillator native fetch and trace links show legacy inspection" do
+ statement = build_statement_for_rollout(mode: "active", slug: "active")
+
+ Distillator::FetchService.expects(:internal_fetch).once.returns(fetch_result("Active Title ", url: statement.webpage.url))
+ Distillator::FetchService.expects(:legacy_fetch).never
+
+ patch refresh_statement_path(statement), headers: trace_headers
+ assert_redirected_to statement_url(statement)
+ follow_redirect_with_trace_visibility("always", "3")
+
+ assert_response :success
+ assert_equal "Active Title", statement.reload.cache
+ assert_includes response.body, "Open active cache"
+ assert_includes response.body, "Inspect legacy Wringer"
+ refute_includes response.body, "Shadow: Wringer serves production while Condenser is checked in the background."
+ assert_logged_context!("fetch.native", statement)
+ assert_logged_context!("cache.miss", statement)
+ end
+
+ test "shadow website refresh returns legacy body and records comparison with website context" do
+ statement = build_statement_for_rollout(mode: "shadow", slug: "shadow")
+ original_selected = statement.source.selected
+ original_selected_by = statement.source.selected_by
+ original_status = statement.status
+ original_status_origin = statement.status_origin
+
+ Distillator::FetchService.expects(:legacy_fetch).once.returns(fetch_result("Legacy Shadow ", url: statement.webpage.url, fetch_path: "legacy"))
+ Distillator::FetchService.expects(:internal_fetch).once.returns(fetch_result("Internal Shadow ", url: statement.webpage.url))
+ Distillator::FetchShadowComparator.expects(:compare).once
+
+ patch refresh_statement_path(statement), headers: trace_headers
+ assert_redirected_to statement_url(statement)
+ follow_redirect_with_trace_visibility("always", "3")
+
+ assert_response :success
+ assert_equal "Legacy Shadow", statement.reload.cache
+ refute_equal "Internal Shadow", statement.cache
+ assert_equal original_status, statement.status
+ assert_equal original_status_origin, statement.status_origin
+ assert_equal original_selected, statement.source.reload.selected
+ assert_equal original_selected_by, statement.source.selected_by
+ assert_includes response.body, "Compare Condenser vs Wringer"
+ assert_includes response.body, "Wringer serves production while Condenser is checked in the background."
+ assert_logged_context!("fetch.shadow_compare", statement)
+ end
+
+ test "legacy website refresh keeps legacy path and renders legacy warning" do
+ statement = build_statement_for_rollout(mode: "legacy", slug: "legacy")
+
+ Distillator::FetchService.expects(:legacy_fetch).once.returns(fetch_result("Legacy Title ", url: statement.webpage.url, fetch_path: "legacy"))
+ Distillator::FetchService.expects(:internal_fetch).never
+
+ patch refresh_statement_path(statement), headers: trace_headers
+ assert_redirected_to statement_url(statement)
+ follow_redirect_with_trace_visibility("always", "3")
+
+ assert_response :success
+ assert_equal "Legacy Title", statement.reload.cache
+ assert_includes response.body, "Wringer serves production."
+ assert_logged_context!("cache.miss", statement)
+ assert_logged_context!("fetch.legacy", statement)
+ end
+
+ private
+
+ def build_statement_for_rollout(mode:, slug:)
+ website = Website.create!(
+ name: "rollout-#{slug}",
+ seedurl: "rollout-#{slug}",
+ graph_name: "http://example.com/rollout-#{slug}",
+ default_language: "en",
+ distillator_mode: mode
+ )
+ webpage = Webpage.create!(
+ url: "https://example.com/#{slug}",
+ language: "en",
+ rdf_uri: "http://example.com/rdf/#{slug}",
+ rdfs_class: rdfs_classes(:one),
+ website: website
+ )
+ source = Source.create!(
+ algorithm_value: "url='#{webpage.url}';xpath=//title/text()",
+ selected: true,
+ selected_by: "test",
+ language: "en",
+ render_js: false,
+ property: properties(:two),
+ website: website
+ )
+ Statement.create!(
+ cache: "old",
+ status: "initial",
+ status_origin: "test",
+ cache_refreshed: 1.day.ago,
+ cache_changed: 1.day.ago,
+ source: source,
+ webpage: webpage
+ )
+ end
+
+ def fetch_result(body, url:, fetch_path: "native")
+ {
+ status: :ok,
+ body: body,
+ raw_body: body,
+ headers: { content_type: "text/html" },
+ final_url: url,
+ redirect_chain: [url],
+ wringer: { signals: {}, hints: [] },
+ http_code: 200,
+ fetch_path: fetch_path
+ }
+ end
+
+ def trace_headers
+ { "Cookie" => "dsl_trace=true; trace_visibility=always" }
+ end
+
+ def follow_redirect_with_trace_visibility(visibility, mode)
+ get response.redirect_url, headers: { "Cookie" => "trace_visibility=#{visibility}; trace_view_mode=#{mode}" }
+ end
+
+ def assert_logged_context!(event_name, statement)
+ payload = @logger.infos.find { |entry| entry.is_a?(Hash) && entry[:event] == event_name }
+ assert payload, "Expected log event #{event_name.inspect}, got #{@logger.infos.map { |entry| entry[:event] if entry.is_a?(Hash) }.compact.inspect}"
+ assert_equal statement.id, payload[:statement_id]
+ assert_equal statement.source_id, payload[:source_id]
+ assert_equal statement.webpage_id, payload[:webpage_id]
+ assert_equal statement.webpage.website_id, payload[:website_id]
+ end
+end
diff --git a/test/integration/distillator_transition_copy_test.rb b/test/integration/distillator_transition_copy_test.rb
new file mode 100644
index 00000000..dbbd3cd8
--- /dev/null
+++ b/test/integration/distillator_transition_copy_test.rb
@@ -0,0 +1,135 @@
+require "cgi"
+require "test_helper"
+
+class DistillatorTransitionCopyTest < ActiveSupport::TestCase
+ LIVE_TRANSITION_FILES = [
+ Rails.root.join("app/views/shared/_transition_context.html.erb"),
+ Rails.root.join("app/views/websites/show.html.erb"),
+ Rails.root.join("app/views/websites/index.html.erb"),
+ Rails.root.join("app/views/distillator/shadow_reports/index.html.erb"),
+ Rails.root.join("app/views/distillator/shadow_reports/show.html.erb")
+ ].freeze
+
+ FORBIDDEN_PHRASES = [
+ "distillator rollout",
+ "new cache"
+ ].freeze
+
+ test "live transition workflow files do not reintroduce retired rollout wording" do
+ contents = LIVE_TRANSITION_FILES.to_h { |path| [path, File.read(path)] }
+
+ FORBIDDEN_PHRASES.each do |phrase|
+ contents.each do |path, content|
+ refute_includes content, phrase, "#{path} unexpectedly included #{phrase.inspect}"
+ end
+ end
+ end
+end
+
+class DistillatorTransitionContextIntegrationTest < ActionDispatch::IntegrationTest
+ FORBIDDEN_VISIBLE_TERMS = [
+ "internal rollout",
+ "replay diagnostic",
+ "phase i",
+ "phase ii",
+ "phase iii",
+ "preview only",
+ "new cache",
+ "distillator rollout"
+ ].freeze
+
+ test "webpages index renders compact transition context from website cookie" do
+ website = Website.create!(
+ name: "Context website",
+ seedurl: "context-website",
+ graph_name: "https://example.org/context-website",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+
+ get website_url(website)
+ assert_response :success
+
+ get webpages_url
+
+ assert_response :success
+ assert_includes @response.body, "Transition context"
+ assert_includes @response.body, website.name
+ assert_includes @response.body, "Current mode: Shadow"
+ assert_includes @response.body, "Production backend: Wringer"
+ assert_includes @response.body, "Transition report"
+ end
+
+ test "webpages index stays compact when no website context exists" do
+ get webpages_url
+
+ assert_response :success
+ assert_not_includes @response.body, "Transition context"
+ end
+
+ test "live operator pages avoid retired rollout terms" do
+ website = Website.create!(
+ name: "Operator website",
+ seedurl: "operator-website",
+ graph_name: "https://example.org/operator-website",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+ webpage = website.webpages.create!(
+ url: "https://example.org/operator-website/event",
+ language: "en",
+ rdf_uri: "rdf:operator-website:event",
+ rdfs_class: rdfs_classes(:one)
+ )
+ cache = Distillator::FetchCache.create!(
+ uri_key: CGI.escape(webpage.url),
+ normalized_url: webpage.url,
+ name: website.name,
+ html: "cached",
+ body: "cached",
+ http_response_code: 200,
+ scrape_date: 1.hour.ago,
+ successful_refresh: 1.hour.ago,
+ headers: {},
+ signals: { "transport_success" => true, "content_success" => true, "export_diff_checked" => true },
+ final_url: webpage.url,
+ redirect_chain: [],
+ health_status: "healthy",
+ health_severity: "ok"
+ )
+
+ Distillator::CacheCompare.stubs(:call).returns(
+ {
+ uri: webpage.url,
+ uri_key: CGI.escape(webpage.url),
+ legacy_cache: { html: "legacy" },
+ legacy_source: "wringer_lookup",
+ legacy_lookup_error: nil,
+ condenser_cache: { html: "condenser" },
+ condenser_source: "local_fetch_cache",
+ diffs: {},
+ summary: {},
+ missing: { legacy: false, condenser: false }
+ }
+ )
+
+ [
+ websites_url,
+ website_url(website),
+ distillator_shadow_report_path,
+ distillator_shadow_report_site_path(website),
+ distillator_cache_index_path,
+ distillator_cache_path(cache),
+ condenser_cache_compare_path(uri: webpage.url),
+ preview_distillator_cache_index_path(uri: webpage.url)
+ ].each do |path|
+ get path
+ assert_response :success
+
+ visible = ActionView::Base.full_sanitizer.sanitize(@response.body).downcase
+ FORBIDDEN_VISIBLE_TERMS.each do |term|
+ refute_includes visible, term, "#{path} unexpectedly included #{term.inspect}"
+ end
+ end
+ end
+end
diff --git a/test/integration/harmonized_card_partials_test.rb b/test/integration/harmonized_card_partials_test.rb
new file mode 100644
index 00000000..bc184044
--- /dev/null
+++ b/test/integration/harmonized_card_partials_test.rb
@@ -0,0 +1,55 @@
+require "test_helper"
+
+class HarmonizedCardPartialsTest < ActionDispatch::IntegrationTest
+ setup do
+ Distillator::FetchCache.delete_all
+ end
+
+ test "cache index renders harmonized summary card hooks" do
+ cache = create_cache
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get distillator_cache_index_path(term: cache.normalized_url)
+
+ assert_response :success
+ assert_select ".harmonized-card-grid", minimum: 1
+ assert_select ".harmonized-card", minimum: 1
+ assert_select ".harmonized-card-title", minimum: 1
+ assert_select ".harmonized-card-value", minimum: 1
+ assert_select ".harmonized-card-status", minimum: 1
+ end
+
+ test "cache show renders harmonized record card hooks" do
+ cache = create_cache
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get distillator_cache_path(cache)
+
+ assert_response :success
+ assert_select ".harmonized-card-grid", minimum: 1
+ assert_select ".harmonized-card", minimum: 1
+ assert_select ".harmonized-card-title", minimum: 1
+ assert_select ".harmonized-card-value", minimum: 1
+ assert_select ".harmonized-card-status", minimum: 1
+ assert_select ".harmonized-card-actions", minimum: 1
+ end
+
+ private
+
+ def create_cache
+ Distillator::FetchCache.create!(
+ uri_key: CGI.escape("http://example.org/cached"),
+ normalized_url: "http://example.org/cached",
+ html: "cached",
+ body: "cached",
+ name: "Cached",
+ scrape_date: Time.zone.now,
+ successful_refresh: Time.zone.now,
+ http_response_code: 200,
+ headers: {},
+ signals: { "network_status" => "ok", "transport_success" => true, "content_success" => true, "content_type" => "html" },
+ hints: [],
+ redirect_chain: []
+ )
+ end
+end
diff --git a/test/integration/harmonized_table_partials_test.rb b/test/integration/harmonized_table_partials_test.rb
new file mode 100644
index 00000000..fa69696c
--- /dev/null
+++ b/test/integration/harmonized_table_partials_test.rb
@@ -0,0 +1,166 @@
+require "test_helper"
+
+class HarmonizedTablePartialsTest < ActionDispatch::IntegrationTest
+ setup do
+ Distillator::FetchCache.delete_all
+ end
+
+ test "cache index renders harmonized table hooks" do
+ create_cache
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get distillator_cache_index_path
+
+ assert_response :success
+ assert_select ".harmonized-table-toolbar", 1
+ assert_select ".harmonized-table-filters", 1
+ assert_select ".harmonized-table-shell", 1
+ assert_select ".harmonized-table-actions", minimum: 1
+ assert_select ".harmonized-table-empty-state", 0
+ end
+
+ test "cache index renders harmonized empty state hook when no rows match" do
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ get distillator_cache_index_path, params: { term: "missing-row" }
+
+ follow_redirect! if response.redirect?
+ assert_response :success
+ assert_select ".harmonized-table-shell", 1
+ assert_select ".harmonized-table-empty-state", 1
+ end
+
+ test "table shell renders empty state when records are empty" do
+ html = ApplicationController.render(
+ partial: "shared/tables/table_shell",
+ locals: {
+ table_key: :properties,
+ records: [],
+ row_partial: "properties/harmonized_row",
+ row_name: :property,
+ headers: [{ label: "Properties" }],
+ filters: {},
+ pagination: nil,
+ sort: nil,
+ direction: nil,
+ sortable_filters: {},
+ empty_message: "No properties found."
+ }
+ )
+
+ assert_includes html, "harmonized-table-empty-state"
+ assert_includes html, "No properties found."
+ end
+
+ test "table shell renders rows using row_partial and row_name" do
+ html = ApplicationController.render(
+ partial: "shared/tables/table_shell",
+ locals: {
+ table_key: :properties,
+ records: [properties(:one)],
+ row_partial: "properties/harmonized_row",
+ row_name: :property,
+ headers: [{ label: "Properties" }],
+ filters: {},
+ pagination: nil,
+ sort: nil,
+ direction: nil,
+ sortable_filters: {}
+ }
+ )
+
+ assert_includes html, properties(:one).label
+ refute_includes html, "harmonized-table-empty-state"
+ end
+
+ test "table shell handles nil pagination safely" do
+ html = ApplicationController.render(
+ partial: "shared/tables/table_shell",
+ locals: {
+ table_key: :properties,
+ records: [properties(:one)],
+ row_partial: "properties/harmonized_row",
+ row_name: :property,
+ headers: [{ label: "Properties" }],
+ filters: {},
+ pagination: nil,
+ sort: "label",
+ direction: "asc",
+ sortable_filters: {}
+ }
+ )
+
+ assert_includes html, "harmonized-table-shell"
+ end
+
+ test "table shell fails clearly when row_partial is missing" do
+ error = assert_raises(ActionView::Template::Error) do
+ ApplicationController.render(
+ partial: "shared/tables/table_shell",
+ locals: {
+ table_key: :properties,
+ records: [],
+ row_name: :property,
+ headers: [{ label: "Properties" }],
+ filters: {},
+ pagination: nil,
+ sort: nil,
+ direction: nil,
+ sortable_filters: {}
+ }
+ )
+ end
+
+ assert_includes error.message, "requires :row_partial"
+ end
+
+ test "toolbar renders source quick filters when table_key is sources" do
+ html = ApplicationController.render(
+ partial: "shared/tables/toolbar",
+ locals: {
+ table_key: :sources,
+ quick_filters: [{ label: "Website defaults", params: { seedurl: "one" } }],
+ seedurl: "one"
+ }
+ )
+
+ assert_includes html, "Website defaults"
+ assert_includes html, "Clear filters"
+ refute_includes html, "Layout:"
+ refute_includes html, "Fetch URL"
+ end
+
+ test "toolbar renders cache controls only for cache table use" do
+ html = ApplicationController.render(
+ partial: "shared/tables/toolbar",
+ locals: {
+ preserved_index_params: {},
+ refresh_ui_enabled: true,
+ view_mode: "rich"
+ }
+ )
+
+ assert_includes html, "Layout:"
+ assert_includes html, "Fetch URL"
+ assert_includes html, "Quick filters"
+ end
+
+ private
+
+ def create_cache
+ Distillator::FetchCache.create!(
+ uri_key: CGI.escape("http://example.org/cached"),
+ normalized_url: "http://example.org/cached",
+ html: "cached",
+ body: "cached",
+ name: "Cached",
+ scrape_date: Time.zone.now,
+ successful_refresh: Time.zone.now,
+ http_response_code: 200,
+ headers: {},
+ signals: { "network_status" => "ok", "transport_success" => true, "content_success" => true, "content_type" => "html" },
+ hints: [],
+ redirect_chain: []
+ )
+ end
+end
diff --git a/test/integration/harmonized_ui_inventory_test.rb b/test/integration/harmonized_ui_inventory_test.rb
new file mode 100644
index 00000000..cc2306ff
--- /dev/null
+++ b/test/integration/harmonized_ui_inventory_test.rb
@@ -0,0 +1,282 @@
+require "test_helper"
+
+class HarmonizedUiInventoryTest < ActionDispatch::IntegrationTest
+ InventoryTarget = Struct.new(
+ :label,
+ :helper,
+ :sample_path,
+ :controller,
+ :action,
+ :current_view,
+ :master_view,
+ keyword_init: true
+ )
+
+ CURRENT_ROOT = Rails.root
+ MASTER_ROOT = Rails.root.join("..", "master", "footlight-condenser")
+
+ INDEX_TARGETS = [
+ InventoryTarget.new(
+ label: "distillator cache index",
+ helper: :distillator_cache_index,
+ sample_path: "/distillator/cache",
+ controller: "distillator/cache",
+ action: "index",
+ current_view: "app/views/distillator/cache/index.html.erb",
+ master_view: nil
+ ),
+ InventoryTarget.new(
+ label: "condenser cache alias",
+ helper: :condenser_cache_index,
+ sample_path: "/condenser/cache",
+ controller: "distillator/cache",
+ action: "index",
+ current_view: "app/views/distillator/cache/index.html.erb",
+ master_view: nil
+ ),
+ InventoryTarget.new(
+ label: "webpages index",
+ helper: :webpages,
+ sample_path: "/webpages",
+ controller: "webpages",
+ action: "index",
+ current_view: "app/views/webpages/index.html.erb",
+ master_view: "app/views/webpages/index.html.erb"
+ ),
+ InventoryTarget.new(
+ label: "sources index",
+ helper: :sources,
+ sample_path: "/sources",
+ controller: "sources",
+ action: "index",
+ current_view: "app/views/sources/index.html.erb",
+ master_view: "app/views/sources/index.html.erb"
+ ),
+ InventoryTarget.new(
+ label: "website resources index",
+ helper: :website_all_resources,
+ sample_path: "/websites/example-seed/resources",
+ controller: "resources",
+ action: "index",
+ current_view: nil,
+ master_view: nil
+ ),
+ InventoryTarget.new(
+ label: "statements index",
+ helper: :statements,
+ sample_path: "/statements",
+ controller: "statements",
+ action: "index",
+ current_view: "app/views/statements/index.html.erb",
+ master_view: "app/views/statements/index.html.erb"
+ ),
+ InventoryTarget.new(
+ label: "properties index",
+ helper: :properties,
+ sample_path: "/properties",
+ controller: "properties",
+ action: "index",
+ current_view: "app/views/properties/index.html.erb",
+ master_view: "app/views/properties/index.html.erb"
+ ),
+ InventoryTarget.new(
+ label: "rdfs classes index",
+ helper: :rdfs_classes,
+ sample_path: "/rdfs_classes",
+ controller: "rdfs_classes",
+ action: "index",
+ current_view: "app/views/rdfs_classes/index.html.erb",
+ master_view: "app/views/rdfs_classes/index.html.erb"
+ ),
+ InventoryTarget.new(
+ label: "website events index",
+ helper: :website_events,
+ sample_path: "/websites/example-seed/events",
+ controller: "events",
+ action: "index",
+ current_view: "app/views/events/index.html.erb",
+ master_view: "app/views/events/index.html.erb"
+ ),
+ InventoryTarget.new(
+ label: "website events by property index",
+ helper: :website_events_by_property,
+ sample_path: "/websites/example-seed/events_by_property",
+ controller: "events",
+ action: "index_by_property",
+ current_view: nil,
+ master_view: nil
+ ),
+ InventoryTarget.new(
+ label: "places index",
+ helper: :places,
+ sample_path: "/places",
+ controller: "places",
+ action: "index",
+ current_view: "app/views/places/index.html.erb",
+ master_view: "app/views/places/index.html.erb"
+ ),
+ InventoryTarget.new(
+ label: "reports source index",
+ helper: :source_reports,
+ sample_path: "/reports/source",
+ controller: "reports",
+ action: "source",
+ current_view: "app/views/reports/source.html.erb",
+ master_view: "app/views/reports/source.html.erb"
+ )
+ ].freeze
+
+ CARD_TARGETS = [
+ InventoryTarget.new(
+ label: "distillator cache show",
+ helper: :distillator_cache,
+ sample_path: "/distillator/cache/123",
+ controller: "distillator/cache",
+ action: "show",
+ current_view: "app/views/distillator/cache/show.html.erb",
+ master_view: nil
+ ),
+ InventoryTarget.new(
+ label: "webpage show",
+ helper: :webpage,
+ sample_path: "/webpages/123",
+ controller: "webpages",
+ action: "show",
+ current_view: "app/views/webpages/show.html.erb",
+ master_view: "app/views/webpages/show.html.erb"
+ ),
+ InventoryTarget.new(
+ label: "source show",
+ helper: :source,
+ sample_path: "/sources/123",
+ controller: "sources",
+ action: "show",
+ current_view: "app/views/sources/show.html.erb",
+ master_view: "app/views/sources/show.html.erb"
+ ),
+ InventoryTarget.new(
+ label: "resource show",
+ helper: :show_resources,
+ sample_path: "/resources/sample-rdf-uri",
+ controller: "resources",
+ action: "show",
+ current_view: "app/views/resources/show.html.erb",
+ master_view: "app/views/resources/show.html.erb"
+ ),
+ InventoryTarget.new(
+ label: "statement show",
+ helper: :statement,
+ sample_path: "/statements/123",
+ controller: "statements",
+ action: "show",
+ current_view: "app/views/statements/show.html.erb",
+ master_view: "app/views/statements/show.html.erb"
+ ),
+ InventoryTarget.new(
+ label: "property show",
+ helper: :property,
+ sample_path: "/properties/123",
+ controller: "properties",
+ action: "show",
+ current_view: "app/views/properties/show.html.erb",
+ master_view: "app/views/properties/show.html.erb"
+ ),
+ InventoryTarget.new(
+ label: "rdfs class show",
+ helper: :rdfs_class,
+ sample_path: "/rdfs_classes/123",
+ controller: "rdfs_classes",
+ action: "show",
+ current_view: "app/views/rdfs_classes/show.html.erb",
+ master_view: "app/views/rdfs_classes/show.html.erb"
+ )
+ ].freeze
+
+ CURRENT_BRANCH_ONLY_FILES = [
+ "app/controllers/distillator/cache_controller.rb",
+ "app/views/shared/_transition_context.html.erb",
+ "app/views/distillator/cache/index.html.erb",
+ "app/views/distillator/cache/show.html.erb",
+ "test/controllers/distillator/cache_controller_test.rb"
+ ].freeze
+
+ SHARED_CONTROLLER_TESTS = [
+ "test/controllers/webpages_controller_test.rb",
+ "test/controllers/sources_controller_test.rb",
+ "test/controllers/resources_controller_test.rb",
+ "test/controllers/statements_controller_test.rb",
+ "test/controllers/properties_controller_test.rb",
+ "test/controllers/rdfs_classes_controller_test.rb",
+ "test/controllers/events_controller_test.rb",
+ "test/controllers/places_controller_test.rb",
+ "test/controllers/reports_controller_test.rb"
+ ].freeze
+
+ test "inventory confirms condenser and distillator index routes" do
+ INDEX_TARGETS.each do |target|
+ assert_named_get_route(target)
+ assert_view_presence(target.current_view, root: CURRENT_ROOT, label: target.label, expected: !target.current_view.nil?)
+ assert_view_presence(target.master_view, root: MASTER_ROOT, label: target.label, expected: !target.master_view.nil?)
+ end
+ end
+
+ test "inventory confirms condenser and distillator card targets" do
+ CARD_TARGETS.each do |target|
+ assert_named_get_route(target)
+ assert_view_presence(target.current_view, root: CURRENT_ROOT, label: target.label, expected: !target.current_view.nil?)
+ assert_view_presence(target.master_view, root: MASTER_ROOT, label: target.label, expected: !target.master_view.nil?)
+ end
+ end
+
+ test "inventory shows distillator cache is current branch only and excludes wringer console files" do
+ CURRENT_BRANCH_ONLY_FILES.each do |relative_path|
+ assert_file_exists CURRENT_ROOT.join(relative_path), "#{relative_path} should exist in the current branch"
+ assert_file_missing MASTER_ROOT.join(relative_path), "#{relative_path} should not exist in the master reference tree"
+ end
+
+ SHARED_CONTROLLER_TESTS.each do |relative_path|
+ assert_file_exists CURRENT_ROOT.join(relative_path), "#{relative_path} should exist in the current branch"
+ assert_file_exists MASTER_ROOT.join(relative_path), "#{relative_path} should exist in the master reference tree"
+ end
+
+ inventory_strings.each do |value|
+ assert_no_match(/footlight-wringer/i, value)
+ assert_no_match(/footlight-console/i, value)
+ assert_no_match(%r{/(?:wringer|console)(?:/|_|$)}i, value)
+ end
+ end
+
+ private
+
+ def assert_named_get_route(target)
+ recognized = Rails.application.routes.recognize_path(target.sample_path, method: :get)
+ assert_equal target.controller, recognized[:controller], "#{target.label} controller mismatch"
+ assert_equal target.action, recognized[:action], "#{target.label} action mismatch"
+ end
+
+ def assert_view_presence(relative_path, root:, label:, expected:)
+ if expected
+ assert_file_exists root.join(relative_path), "#{label} should render #{relative_path}"
+ else
+ if relative_path
+ assert_file_missing root.join(relative_path), "#{label} should not have an HTML view in #{root}"
+ else
+ assert_nil relative_path, "#{label} should not declare an HTML view path"
+ end
+ end
+ end
+
+ def assert_file_exists(path, message)
+ assert path.exist?, message
+ end
+
+ def assert_file_missing(path, message)
+ assert_not path.exist?, message
+ end
+
+ def inventory_strings
+ (INDEX_TARGETS + CARD_TARGETS).flat_map do |target|
+ [target.label, target.helper.to_s, target.sample_path, target.controller, target.action, target.current_view.to_s, target.master_view.to_s]
+ end + CURRENT_BRANCH_ONLY_FILES + SHARED_CONTROLLER_TESTS
+ end
+end
diff --git a/test/integration/statements_helper_scrape_integration_test.rb b/test/integration/statements_helper_scrape_integration_test.rb
new file mode 100644
index 00000000..6d1e6dbe
--- /dev/null
+++ b/test/integration/statements_helper_scrape_integration_test.rb
@@ -0,0 +1,154 @@
+require "test_helper"
+require "ostruct"
+
+# VCR-backed scrape coverage lives here so StatementsHelperTest remains unit-fast.
+# These tests may exercise recorded HTTP/VCR behavior and are intentionally not part
+# of the fast unit preflight.
+class StatementsHelperScrapeIntegrationTest < ActionView::TestCase
+ include StatementsHelper
+
+ setup do
+ @distillator_config = Rails.application.config.x.distillator
+ @old_compatibility_base_url = @distillator_config.compatibility_base_url
+ @old_legacy_wringer_base_url = @distillator_config.legacy_wringer_base_url
+ @old_allow_localhost = @distillator_config.allow_localhost_compatibility
+
+ @distillator_config.compatibility_base_url = "http://localhost:3000"
+ @distillator_config.legacy_wringer_base_url = "http://localhost:3009"
+ @distillator_config.allow_localhost_compatibility = false
+ end
+
+ teardown do
+ @distillator_config.compatibility_base_url = @old_compatibility_base_url
+ @distillator_config.legacy_wringer_base_url = @old_legacy_wringer_base_url
+ @distillator_config.allow_localhost_compatibility = @old_allow_localhost
+ end
+
+ test "process_algorithm sparql" do
+ expected = ["DOMINIC PAQUET • LAISSE-MOI PARTIR"]
+
+ VCR.use_cassette("StatementsHelper:complexeculturelfelixleclerc") do
+ assert_equal expected, process_algorithm(
+ algorithm: "sparql={?s a schema:Event. ?s schema:name ?answer}",
+ url: "https://www.complexeculturelfelixleclerc.com/event-details/dominic-paquet-laisse-moi-partir"
+ )
+ end
+ end
+
+ test "process_algorithm sparql with nested image" do
+ expected = ["https://static.wixstatic.com/media/28fd07_aa7b850feecb4363878aa75c34296221~mv2.jpg/v1/fill/w_537,h_534,al_c,q_80/28fd07_aa7b850feecb4363878aa75c34296221~mv2.jpg"]
+
+ VCR.use_cassette("StatementsHelper:complexeculturelfelixleclerc") do
+ assert_equal expected, process_algorithm(
+ algorithm: "sparql={?s a schema:Event. ?s schema:image/schema:url ?answer}",
+ url: "https://www.complexeculturelfelixleclerc.com/event-details/dominic-paquet-laisse-moi-partir"
+ )
+ end
+ end
+
+ test "process_algorithm sparql with filter" do
+ expected = ["https://static.wixstatic.com/media/28fd07_aa7b850feecb4363878aa75c34296221~mv2.jpg/v1/fill/w_537,h_534,al_c,q_80/28fd07_aa7b850feecb4363878aa75c34296221~mv2.jpg"]
+
+ VCR.use_cassette("StatementsHelper:complexeculturelfelixleclerc") do
+ assert_equal expected, process_algorithm(
+ algorithm: "sparql={?s a schema:Event. ?s schema:image/schema:url ?answer . FILTER isURI(?answer) }",
+ url: "https://www.complexeculturelfelixleclerc.com/event-details/dominic-paquet-laisse-moi-partir"
+ )
+ end
+ end
+
+ test "process_algorithm xpath" do
+ expected = ["Culture Creates | Digital knowledge management for the arts"]
+
+ VCR.use_cassette("StatementsHelper:culturecreates.com") do
+ assert_equal expected, process_algorithm(algorithm: "xpath=//title", url: "http://culturecreates.com")
+ end
+ end
+
+ test "process_algorithm if_xpath continue" do
+ expected = ["Arts metadata compatible with an AI-powered world"]
+
+ VCR.use_cassette("StatementsHelper:culturecreates.com") do
+ assert_equal expected, process_algorithm(algorithm: "if_xpath=//title;xpath=(//h1)[1]", url: "http://culturecreates.com")
+ end
+ end
+
+ test "process_algorithm if_xpath break" do
+ VCR.use_cassette("StatementsHelper:culturecreates.com") do
+ assert_equal [], process_algorithm(algorithm: "if_xpath=//nothing;xpath=(//h1)[1]", url: "http://culturecreates.com")
+ end
+ end
+
+ test "process_algorithm unless_xpath break" do
+ VCR.use_cassette("StatementsHelper:culturecreates.com") do
+ assert_equal [], process_algorithm(algorithm: "unless_xpath=//title;xpath=(//h1)[1]", url: "http://culturecreates.com")
+ end
+ end
+
+ test "process_algorithm unless_xpath continue" do
+ expected = ["Arts metadata compatible with an AI-powered world"]
+
+ VCR.use_cassette("StatementsHelper:culturecreates.com") do
+ assert_equal expected, process_algorithm(algorithm: "unless_xpath=//nothing;xpath=(//h1)[1]", url: "http://culturecreates.com")
+ end
+ end
+
+ test "process_algorithm url and xpath" do
+ VCR.use_cassette("StatementsHelper:process_algorithm url and xpath") do
+ assert_equal ["ArtsdataApi"], process_algorithm(
+ algorithm: "url='http://api.artsdata'+'.ca';xpath=//title",
+ url: "http://culturecreates.com"
+ )
+ end
+ end
+
+ test "process_algorithm double xpath" do
+ VCR.use_cassette("StatementsHelper:culturecreates.com") do
+ assert_equal ["IE=edge"], process_algorithm(
+ algorithm: "xpath=//title;xpath=(//meta/@content)[1]",
+ url: "http://culturecreates.com"
+ )
+ end
+ end
+
+ test "process_algorithm url and json and ruby" do
+ algorithm = "url=$url + '.json';json=$json.dig('date','end');ruby=$array[0] ? [$json.dig('date','start')] : $array;time_zone='Eastern Time (US & Canada)'"
+
+ VCR.use_cassette("StatementsHelper: process_algorithm url and json and ruby") do
+ assert_equal ["time_zone: 'Eastern Time (US & Canada)'"], process_algorithm(
+ algorithm: algorithm,
+ url: "https://signelaval.com/fr/evenements/14650/du-fond-de-mon-garde-robe"
+ )
+ end
+ end
+
+ test "should scrape title from html" do
+ source = sources(:one)
+ source.algorithm_value = "xpath=//title"
+
+ VCR.use_cassette("StatementsHelper: should scrape title from html") do
+ assert_equal ["Culture Creates | Digital knowledge management for the arts"], scrape(source, "http://culturecreates.com")
+ end
+ end
+
+ test "should scrape 2 items from html" do
+ source = OpenStruct.new(algorithm_value: 'xpath=//title;xpath=//meta[@property="og:title"]/@content')
+
+ VCR.use_cassette("StatementsHelper: should scrape 2 items from html") do
+ assert_equal ["Culture Creates Inc"], scrape(source, "http://culturecreates.com")
+ end
+ end
+
+ test "should concatenate 2 items from html" do
+ source = OpenStruct.new(algorithm_value: 'xpath=//title | //meta[@property="og:title"]/@content;ruby=$array[0]+ " | " + $array[1]')
+
+ VCR.use_cassette("StatementsHelper: should concatenate 2 items from html") do
+ actual_output = scrape(source, "http://culturecreates.com")
+ expected_variants = [
+ "Culture Creates | Digital knowledge management for the arts | Culture Creates Inc",
+ "Culture Creates Inc | Culture Creates | Digital knowledge management for the arts"
+ ]
+ assert_includes expected_variants, actual_output
+ end
+ end
+end
diff --git a/test/integration/wringer_contract_distillator_test.rb b/test/integration/wringer_contract_distillator_test.rb
new file mode 100644
index 00000000..50b6c60e
--- /dev/null
+++ b/test/integration/wringer_contract_distillator_test.rb
@@ -0,0 +1,247 @@
+require "test_helper"
+
+class WringerContractDistillatorTest < ActionDispatch::IntegrationTest
+ setup do
+ Distillator::FetchCache.delete_all
+ @old_fetch_mode = ENV["DISTILLATOR_FETCH_MODE"]
+ ENV["DISTILLATOR_FETCH_MODE"] = "internal"
+ Distillator::FetchGuard.stubs(:check_url).returns(Distillator::FetchGuard::Result.new(allowed: true))
+ Distillator::FetchGuard.stubs(:check_response).returns(Distillator::FetchGuard::Result.new(allowed: true))
+ end
+
+ teardown do
+ ENV["DISTILLATOR_FETCH_MODE"] = @old_fetch_mode
+ end
+
+ test "raw response creates a cache row and returns stored html" do
+ Distillator::NativeFetch.expects(:call).once.returns(
+ native_fetch_result(
+ body: "First ok",
+ final_url: "https://example.org/final"
+ )
+ )
+
+ get wring_websites_url, params: { uri: "http://example.org/wring-raw", format: "raw", force_scrape: "false" }
+
+ assert_response :success
+ assert_equal "First ok", @response.body
+
+ cache = Distillator::FetchCache.find_by!(uri_key: CGI.escape("http://example.org/wring-raw"))
+ assert_equal "First ok", cache.html
+ assert_equal "First ok", cache.body
+ assert_equal "First", cache.name
+ assert_equal 200, cache.http_response_code
+ end
+
+ test "html response redirects with wringer-compatible notice" do
+ Distillator::NativeFetch.expects(:call).once.returns(native_fetch_result(body: "ok"))
+
+ get wring_websites_url, params: { uri: "http://example.org/wring-html", format: "html" }
+
+ assert_redirected_to websites_path
+ follow_redirect!
+ assert_equal "Website was successfully wrung.", flash[:notice]
+ end
+
+ test "json response returns stored metadata from the real cache row" do
+ Distillator::NativeFetch.expects(:call).once.returns(
+ native_fetch_result(
+ body: '{"result":"ok"}',
+ headers: { "content_type" => "application/json" },
+ final_url: "https://example.org/wring-post/final",
+ redirect_chain: ["http://example.org/wring-post", "https://example.org/wring-post/final"],
+ signals: { network_status: "ok", content_type: "json", redirect_type: "normal" },
+ hints: ["json_detected"]
+ )
+ )
+
+ get wring_websites_url, params: { uri: "http://example.org/wring-post", format: "json", json_post: "true" }
+
+ assert_response :success
+ payload = JSON.parse(@response.body)
+ assert_equal %w[final_url hints html http_code redirect_chain signals].sort, payload.keys.sort
+ assert_equal "json", payload.dig("signals", "content_type")
+ assert_equal ["json_detected"], payload["hints"]
+ assert_equal "https://example.org/wring-post/final", payload["final_url"]
+ assert_equal ["http://example.org/wring-post", "https://example.org/wring-post/final"], payload["redirect_chain"]
+ assert_equal 200, payload["http_code"]
+
+ cache = Distillator::FetchCache.find_by!(uri_key: CGI.escape("http://example.org/wring-post"))
+ assert_equal "json", cache.signals["content_type"]
+ assert_equal ["json_detected"], cache.hints
+ assert_equal "https://example.org/wring-post/final", cache.final_url
+ assert_equal ["http://example.org/wring-post", "https://example.org/wring-post/final"], cache.redirect_chain
+ end
+
+ test "second call without force uses the cache and does not fetch again" do
+ Distillator::NativeFetch.expects(:call).once.returns(native_fetch_result(body: "cached once"))
+
+ 2.times do
+ get wring_websites_url, params: { uri: "http://example.org/cached-once", format: "raw" }
+ assert_response :success
+ assert_equal "cached once", @response.body
+ end
+
+ assert_equal 1, Distillator::FetchCache.where(uri_key: CGI.escape("http://example.org/cached-once")).count
+ end
+
+ test "force_scrape true performs a new fetch and updates cache metadata" do
+ Distillator::NativeFetch.expects(:call).twice.returns(
+ native_fetch_result(body: "Old old"),
+ native_fetch_result(body: "New new", final_url: "https://example.org/forced")
+ )
+
+ get wring_websites_url, params: { uri: "http://example.org/forced", format: "raw" }
+ get wring_websites_url, params: { uri: "http://example.org/forced", format: "json", force_scrape: "true" }
+
+ payload = JSON.parse(@response.body)
+ cache = Distillator::FetchCache.find_by!(uri_key: CGI.escape("http://example.org/forced"))
+
+ assert_equal "New new", payload["html"]
+ assert_equal "New new", cache.html
+ assert_equal "New", cache.name
+ assert_equal "https://example.org/forced", cache.final_url
+ end
+
+ test "force_scrape_every_hrs zero performs a new fetch" do
+ Distillator::NativeFetch.expects(:call).twice.returns(
+ native_fetch_result(body: "before zero"),
+ native_fetch_result(body: "after zero")
+ )
+
+ get wring_websites_url, params: { uri: "http://example.org/zero-threshold", format: "raw" }
+ get wring_websites_url, params: { uri: "http://example.org/zero-threshold", format: "raw", force_scrape_every_hrs: "0" }
+
+ assert_response :success
+ assert_equal "after zero", @response.body
+ end
+
+ test "force_scrape_every_hrs large value uses fresh cache" do
+ Distillator::NativeFetch.expects(:call).once.returns(native_fetch_result(body: "fresh cache"))
+
+ get wring_websites_url, params: { uri: "http://example.org/fresh-cache", format: "raw" }
+ get wring_websites_url, params: { uri: "http://example.org/fresh-cache", format: "raw", force_scrape_every_hrs: "999999" }
+
+ assert_response :success
+ assert_equal "fresh cache", @response.body
+ end
+
+ test "use_phantomjs true routes through phantomjs fetcher and caches rendered html" do
+ Distillator::PhantomjsFetcher.expects(:call).once.returns(
+ phantomjs_fetch_result(body: "rendered by phantom")
+ )
+
+ get wring_websites_url, params: { uri: "http://example.org/rendered", format: "raw", use_phantomjs: "true" }
+
+ assert_response :success
+ assert_equal "rendered by phantom", @response.body
+
+ cache = Distillator::FetchCache.find_by!(uri_key: CGI.escape("http://example.org/rendered"))
+ assert_equal "rendered by phantom", cache.html
+ assert_equal "legacy_phantomjs", cache.signals["renderer"]
+ assert_equal "phantomjs", cache.signals["fetch_backend"]
+ assert_equal true, cache.signals["use_phantomjs"]
+ end
+
+ test "iframe url forces phantomjs even when use_phantomjs is false" do
+ Distillator::PhantomjsFetcher.expects(:call).with do |kwargs|
+ assert_equal true, kwargs[:iframe]
+ true
+ end.returns(
+ phantomjs_fetch_result(body: "iframe child", iframe: true)
+ )
+
+ get wring_websites_url, params: { uri: "http://example.org/eventiframe", format: "json", use_phantomjs: "false" }
+
+ assert_response :success
+ payload = JSON.parse(@response.body)
+ assert_equal "iframe child", payload["html"]
+ assert_equal true, payload.dig("signals", "phantomjs_iframe_extraction")
+ end
+
+ test "invalid uri returns no_content" do
+ Distillator::NativeFetch.expects(:call).never
+
+ get wring_websites_url, params: { uri: "http://[invalid", format: "json" }
+
+ assert_response :no_content
+ end
+
+ test "include_fragment false excludes the fragment from the cache key" do
+ Distillator::NativeFetch.expects(:call).once.returns(native_fetch_result(body: "fragmentless"))
+
+ get wring_websites_url, params: { uri: "http://example.org/page#one", format: "raw", include_fragment: "false" }
+ get wring_websites_url, params: { uri: "http://example.org/page#two", format: "raw", include_fragment: "false" }
+
+ assert_equal [CGI.escape("http://example.org/page")], Distillator::FetchCache.order(:uri_key).pluck(:uri_key)
+ end
+
+ test "include_fragment true keeps the fragment in the cache key" do
+ Distillator::NativeFetch.expects(:call).twice.returns(native_fetch_result(body: "fragmentful"))
+
+ get wring_websites_url, params: { uri: "http://example.org/page#one", format: "raw", include_fragment: "true" }
+ get wring_websites_url, params: { uri: "http://example.org/page#two", format: "raw", include_fragment: "true" }
+
+ assert_equal(
+ [CGI.escape("http://example.org/page#one"), CGI.escape("http://example.org/page#two")],
+ Distillator::FetchCache.order(:uri_key).pluck(:uri_key)
+ )
+ end
+
+ test "wringer url-key contract exact cases match legacy outputs" do
+ assert_equal "http%3A%2F%2Fculturecreates.com%2F", Distillator::WringerUrlKey.call("http://culturecreates.com/").uri_key
+ assert_equal "https%3A%2F%2Fculturecreates.com%2Fpeople", Distillator::WringerUrlKey.call("https://culturecreates.com/people#gregory").uri_key
+ assert_equal "https%3A%2F%2Fculturecreates.com%2Fpeople%23gregory", Distillator::WringerUrlKey.call("https://culturecreates.com/people#gregory", include_fragment: true).uri_key
+ assert_equal "http%3A%2F%2Fculturecreates.com%2Fpeople%2F", Distillator::WringerUrlKey.call("culturecreates.com/people/").uri_key
+ end
+
+ test "wring route stores cache using the exact wringer-compatible key" do
+ Distillator::NativeFetch.expects(:call).once.returns(native_fetch_result(body: "contract key"))
+
+ get wring_websites_url, params: { uri: "culturecreates.com/people/", format: "raw" }
+
+ assert_response :success
+ assert_equal ["http%3A%2F%2Fculturecreates.com%2Fpeople%2F"], Distillator::FetchCache.order(:uri_key).pluck(:uri_key)
+ end
+
+ private
+
+ def native_fetch_result(body:, headers: { "content_type" => "text/html" }, final_url: nil, redirect_chain: nil, signals: {}, hints: [])
+ normalized_final_url = final_url || "http://example.org/final"
+ {
+ status: :ok,
+ body: body,
+ headers: headers,
+ final_url: normalized_final_url,
+ redirect_chain: redirect_chain || [normalized_final_url],
+ wringer: { signals: signals, hints: hints },
+ http_code: 200,
+ raw_body: body
+ }
+ end
+
+ def phantomjs_fetch_result(body:, iframe: false)
+ {
+ status: :ok,
+ body: body,
+ headers: { "content_type" => "text/html" },
+ final_url: "http://example.org/rendered",
+ redirect_chain: ["http://example.org/rendered"],
+ wringer: {
+ signals: {
+ network_status: "ok",
+ content_type: "html",
+ renderer: "legacy_phantomjs",
+ fetch_backend: "phantomjs",
+ request_method: "GET",
+ use_phantomjs: true,
+ phantomjs_iframe_extraction: iframe
+ },
+ hints: ["legacy_phantomjs"]
+ },
+ http_code: 200,
+ raw_body: body,
+ fetch_path: "native"
+ }
+ end
+end
diff --git a/test/jobs/add_webpages_job_test.rb b/test/jobs/add_webpages_job_test.rb
new file mode 100644
index 00000000..d0b12897
--- /dev/null
+++ b/test/jobs/add_webpages_job_test.rb
@@ -0,0 +1,81 @@
+require "test_helper"
+
+class AddWebpagesJobTest < ActiveJob::TestCase
+ setup do
+ @previous_queue_adapter = ActiveJob::Base.queue_adapter
+ ActiveJob::Base.queue_adapter = :test
+ @resource_list_page = webpages(:resource_list_page)
+ @rdf_class_statement = statements(:resource_list_rdf_class_statement)
+ @uri_list_statement = statements(:resource_list_uri_list_statement)
+ @url_list_statement = statements(:resource_list_url_list_statement)
+
+ sources(:resource_list_rdf_class_source).property.update!(label: AddWebpagesJob::RDF_CLASS_LABEL)
+ sources(:resource_list_uri_list_source).property.update!(label: AddWebpagesJob::URI_LIST_LABEL)
+ sources(:resource_list_url_list_source).property.update!(label: AddWebpagesJob::WEBPAGE_URL_LIST_LABEL)
+ end
+
+ teardown do
+ ActiveJob::Base.queue_adapter = @previous_queue_adapter
+ end
+
+ test "valid list creates webpages and enqueues refreshes" do
+ assert_difference("Webpage.count", 2) do
+ AddWebpagesJob.perform_now(@resource_list_page.url)
+ end
+
+ assert_equal 2, enqueued_jobs.count { |job| job[:job] == RefreshWebpageJob }
+ assert Webpage.exists?(url: "https://example.org/events/1", website: @resource_list_page.website)
+ assert Webpage.exists?(url: "https://example.org/events/2", website: @resource_list_page.website)
+ end
+
+ test "mismatched uri and url counts creates none and raises" do
+ @url_list_statement.update!(cache: '["https://example.org/events/1"]')
+
+ assert_raises(AddWebpagesJob::InvalidResourceListError) do
+ AddWebpagesJob.perform_now(@resource_list_page.url)
+ end
+
+ assert_equal 0, Webpage.where(url: ["https://example.org/events/1", "https://example.org/events/2"], website: @resource_list_page.website).count
+ assert_equal 0, enqueued_jobs.count { |job| job[:job] == RefreshWebpageJob }
+ end
+
+ test "invalid json array creates none and raises" do
+ @uri_list_statement.update!(cache: '{"uri":"adr:event-1"}')
+
+ assert_raises(AddWebpagesJob::InvalidResourceListError) do
+ AddWebpagesJob.perform_now(@resource_list_page.url)
+ end
+
+ assert_equal 0, Webpage.where(url: ["https://example.org/events/1", "https://example.org/events/2"], website: @resource_list_page.website).count
+ assert_equal 0, enqueued_jobs.count { |job| job[:job] == RefreshWebpageJob }
+ end
+
+ test "missing rdf class creates none and raises" do
+ @rdf_class_statement.update!(cache: "")
+
+ assert_raises(AddWebpagesJob::InvalidResourceListError) do
+ AddWebpagesJob.perform_now(@resource_list_page.url)
+ end
+
+ assert_equal 0, Webpage.where(url: ["https://example.org/events/1", "https://example.org/events/2"], website: @resource_list_page.website).count
+ assert_equal 0, enqueued_jobs.count { |job| job[:job] == RefreshWebpageJob }
+ end
+
+ test "duplicate webpage does not duplicate or enqueue refresh twice" do
+ Webpage.create!(
+ url: "https://example.org/events/1",
+ rdf_uri: "adr:event-1",
+ language: @resource_list_page.language,
+ rdfs_class: rdfs_classes(:one),
+ website: @resource_list_page.website
+ )
+
+ assert_difference("Webpage.count", 1) do
+ AddWebpagesJob.perform_now(@resource_list_page.url)
+ end
+
+ assert_equal 1, Webpage.where(url: "https://example.org/events/1", website: @resource_list_page.website).count
+ assert_equal 1, enqueued_jobs.count { |job| job[:job] == RefreshWebpageJob }
+ assert Webpage.exists?(url: "https://example.org/events/2", website: @resource_list_page.website)
+ end
+end
diff --git a/test/jobs/distillator/transition_check_job_test.rb b/test/jobs/distillator/transition_check_job_test.rb
new file mode 100644
index 00000000..4a3d6a17
--- /dev/null
+++ b/test/jobs/distillator/transition_check_job_test.rb
@@ -0,0 +1,17 @@
+require "test_helper"
+
+class Distillator::TransitionCheckJobTest < ActiveJob::TestCase
+ test "perform calls transition check runner for the website id" do
+ website = Website.create!(
+ name: "Transition job website",
+ seedurl: "transition-job-website",
+ graph_name: "https://example.org/transition-job-website",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+
+ Distillator::TransitionCheckRunner.expects(:call).with(website: website.id).once
+
+ Distillator::TransitionCheckJob.perform_now(website.id)
+ end
+end
diff --git a/test/jobs/refresh_website_job_test.rb b/test/jobs/refresh_website_job_test.rb
index c9326260..84242fc9 100644
--- a/test/jobs/refresh_website_job_test.rb
+++ b/test/jobs/refresh_website_job_test.rb
@@ -1,7 +1,64 @@
require 'test_helper'
-class RefreshWebsiteJobTest < ActiveJob::TestCase
- # test "the truth" do
- # assert true
- # end
+class RefreshWebpageJobTest < ActiveJob::TestCase
+ test "perform delegates refresh to service object" do
+ webpage = webpages(:six)
+ Distillator::WebpageRemovalCandidate.stubs(:call).returns(
+ Distillator::WebpageRemovalCandidate::Result.new(delete: false, reason: :missing_cache)
+ )
+
+ Distillator::RefreshRunner.expects(:call).with(
+ webpage: webpage,
+ refresh_helper: ApplicationController.helpers,
+ scrape_options: {}
+ ).returns([])
+
+ RefreshWebpageJob.perform_now(webpage.url)
+ end
+
+ test "perform destroys webpage when latest distillator fetch cache is 404" do
+ webpage = webpages(:six)
+ Distillator::RefreshRunner.stubs(:call).returns([])
+ Distillator::WebpageRemovalCandidate.stubs(:call).returns(
+ Distillator::WebpageRemovalCandidate::Result.new(delete: true, reason: :delete_candidate)
+ )
+
+ assert_difference("Webpage.count", -1) do
+ RefreshWebpageJob.perform_now(webpage.url)
+ end
+ end
+
+ test "perform preserves scrape options through refresh runner" do
+ webpage = webpages(:six)
+ Distillator::WebpageRemovalCandidate.stubs(:call).returns(
+ Distillator::WebpageRemovalCandidate::Result.new(delete: false, reason: :missing_cache)
+ )
+
+ Distillator::RefreshRunner.expects(:call).with(
+ webpage: webpage,
+ refresh_helper: ApplicationController.helpers,
+ scrape_options: {
+ force_scrape: true,
+ force_scrape_every_hrs: 0,
+ render_js: true,
+ use_phantomjs: true,
+ json_post: true,
+ absolute_src: true,
+ include_fragment: true
+ }
+ ).returns([])
+
+ RefreshWebpageJob.perform_now(
+ webpage.url,
+ {
+ force_scrape: true,
+ force_scrape_every_hrs: 0,
+ render_js: true,
+ use_phantomjs: true,
+ json_post: true,
+ absolute_src: true,
+ include_fragment: true
+ }
+ )
+ end
end
diff --git a/test/models/distillator/fetch_cache_test.rb b/test/models/distillator/fetch_cache_test.rb
new file mode 100644
index 00000000..8c3a3feb
--- /dev/null
+++ b/test/models/distillator/fetch_cache_test.rb
@@ -0,0 +1,15 @@
+require "test_helper"
+
+class Distillator::FetchCacheTest < ActiveSupport::TestCase
+ setup do
+ Distillator::FetchCache.delete_all
+ end
+
+ test "requires unique uri_key" do
+ Distillator::FetchCache.create!(uri_key: "http%3A%2F%2Fexample.com", normalized_url: "http://example.com")
+ duplicate = Distillator::FetchCache.new(uri_key: "http%3A%2F%2Fexample.com", normalized_url: "http://example.com")
+
+ assert_not duplicate.valid?
+ assert_includes duplicate.errors[:uri_key], "has already been taken"
+ end
+end
diff --git a/test/models/distillator/rollout_event_test.rb b/test/models/distillator/rollout_event_test.rb
new file mode 100644
index 00000000..6005b27b
--- /dev/null
+++ b/test/models/distillator/rollout_event_test.rb
@@ -0,0 +1,10 @@
+require "test_helper"
+
+class Distillator::RolloutEventTest < ActiveSupport::TestCase
+ test "requires a target mode" do
+ event = Distillator::RolloutEvent.new(website: websites(:one))
+
+ assert_not event.valid?
+ assert_includes event.errors[:to_mode], "can't be blank"
+ end
+end
diff --git a/test/models/distillator/transition_evidence_test.rb b/test/models/distillator/transition_evidence_test.rb
new file mode 100644
index 00000000..afe65210
--- /dev/null
+++ b/test/models/distillator/transition_evidence_test.rb
@@ -0,0 +1,39 @@
+require "test_helper"
+
+class Distillator::TransitionEvidenceTest < ActiveSupport::TestCase
+ test "supports latest evidence lookup by website and kind" do
+ website = websites(:one)
+ older = Distillator::TransitionEvidence.create!(
+ website: website,
+ url: "https://example.org/event",
+ check_kind: "export_diff",
+ status: "checked",
+ export_diff_checked: true,
+ checked_at: 2.days.ago
+ )
+ newer = Distillator::TransitionEvidence.create!(
+ website: website,
+ url: "https://example.org/event",
+ check_kind: "export_diff",
+ status: "accepted",
+ export_diff_accepted: true,
+ checked_at: 1.day.ago
+ )
+
+ assert_equal newer, Distillator::TransitionEvidence.latest_for_website(website, "export_diff")
+ assert_equal newer, Distillator::TransitionEvidence.latest_for_website_ids([website.id])[website.id]["export_diff"]
+ end
+
+ test "reports derived satisfaction helpers" do
+ evidence = Distillator::TransitionEvidence.new(
+ website: websites(:one),
+ url: "https://example.org/event",
+ check_kind: "statement_delta",
+ status: "checked",
+ statement_count_delta_acceptable: true,
+ checked_at: Time.current
+ )
+
+ assert_equal true, evidence.acceptable_statement_delta?
+ end
+end
diff --git a/test/models/webpage_test.rb b/test/models/webpage_test.rb
index 471ca87e..f06b79fb 100644
--- a/test/models/webpage_test.rb
+++ b/test/models/webpage_test.rb
@@ -1,7 +1,79 @@
require 'test_helper'
class WebpageTest < ActiveSupport::TestCase
- # test "the truth" do
- # assert true
- # end
+ test "public and internal url scopes classify webpages by url scheme" do
+ website = build_website("scope-kind")
+ public_page = create_webpage(website, suffix: "public", url: "https://example.org/page", rdf_uri: "rdf:scope:public", rdfs_class: rdfs_classes(:one))
+ internal_page = create_webpage(website, suffix: "internal", url: "footlight:scope:internal", rdf_uri: "rdf:scope:internal", rdfs_class: rdfs_classes(:person))
+
+ assert_includes Webpage.public_source_urls, public_page
+ assert_not_includes Webpage.public_source_urls, internal_page
+ assert_includes Webpage.internal_uris, internal_page
+ assert_not_includes Webpage.internal_uris, public_page
+ end
+
+ test "publishable and not_publishable scopes use existing selected statement rules" do
+ website = build_website("scope-publishable")
+ publishable_page = create_webpage(website, suffix: "publishable", url: "https://example.org/publishable", rdf_uri: "rdf:scope:publishable", rdfs_class: rdfs_classes(:one))
+ create_publishable_statements_for(publishable_page)
+
+ blocked_page = create_webpage(website, suffix: "blocked", url: "https://example.org/blocked", rdf_uri: "rdf:scope:blocked", rdfs_class: rdfs_classes(:one))
+ source = Source.create!(
+ website: website,
+ property: properties(:four),
+ language: "en",
+ selected: true,
+ algorithm_value: "scope-test"
+ )
+ Statement.create!(webpage: blocked_page, source: source, cache: "", status: "missing")
+
+ assert_includes Webpage.publishable, publishable_page
+ assert_not_includes Webpage.publishable, blocked_page
+ assert_includes Webpage.not_publishable, blocked_page
+ end
+
+ private
+
+ def build_website(seedurl)
+ Website.create!(
+ name: "Webpage scope #{seedurl}",
+ seedurl: seedurl,
+ graph_name: "https://example.org/#{seedurl}",
+ default_language: "en"
+ )
+ end
+
+ def create_webpage(website, suffix:, url:, rdf_uri:, rdfs_class:)
+ Webpage.create!(
+ url: "#{url}-#{suffix}",
+ language: "en",
+ rdf_uri: rdf_uri,
+ rdfs_class: rdfs_class,
+ website: website
+ )
+ end
+
+ def create_publishable_statements_for(webpage)
+ [
+ [properties(:four), "Publishable title"],
+ [properties(:location), '[["Salle","uri:place"]]'],
+ [properties(:six), '["2026-06-01T20:00:00-04:00"]']
+ ].each do |property, cache|
+ source = Source.create!(
+ website: webpage.website,
+ property: property,
+ language: "en",
+ selected: true,
+ algorithm_value: "scope-test"
+ )
+
+ Statement.create!(
+ webpage: webpage,
+ source: source,
+ cache: cache,
+ status: "ok"
+ )
+ Statement.where(webpage: webpage, source: source).update_all(status: "ok")
+ end
+ end
end
diff --git a/test/models/website_test.rb b/test/models/website_test.rb
index 9f038971..317ee4f9 100644
--- a/test/models/website_test.rb
+++ b/test/models/website_test.rb
@@ -1,7 +1,89 @@
require 'test_helper'
class WebsiteTest < ActiveSupport::TestCase
- # test "the truth" do
- # assert true
- # end
+ test "defaults distillator rollout mode to legacy" do
+ website = Website.new(
+ name: "Rollout default",
+ seedurl: "rollout-default",
+ graph_name: "http://example.com/rollout-default",
+ default_language: "en"
+ )
+
+ website.valid?
+
+ assert_equal "legacy", website.distillator_mode
+ assert_equal :legacy, website.distillator_fetch_mode
+ end
+
+ test "maps active website rollout to the canonical active fetch mode" do
+ website = websites(:one)
+ website.distillator_mode = "active"
+
+ assert_equal :active, website.distillator_fetch_mode
+ end
+
+ test "website rollout modes stay separate from fetch execution modes" do
+ assert_equal %w[legacy shadow active], Website::DISTILLATOR_MODES
+ assert_not_includes Website::DISTILLATOR_MODES, "internal"
+ end
+
+ test "rejects unsupported distillator rollout modes" do
+ website = websites(:one)
+ website.distillator_mode = "preview"
+
+ assert_not website.valid?
+ assert_includes website.errors[:distillator_mode], "is not included in the list"
+ end
+
+ test "rejects replay distillator rollout mode" do
+ website = websites(:one)
+ website.distillator_mode = "replay"
+
+ assert_not website.valid?
+ assert_includes website.errors[:distillator_mode], "is not included in the list"
+ end
+
+ test "rejects blank distillator rollout mode" do
+ website = websites(:one)
+ website.distillator_mode = ""
+
+ assert_not website.valid?
+ assert_includes website.errors[:distillator_mode], "is not included in the list"
+ end
+
+ test "rejects random distillator rollout mode values" do
+ website = websites(:one)
+ website.distillator_mode = "banana"
+
+ assert_not website.valid?
+ assert_includes website.errors[:distillator_mode], "is not included in the list"
+ end
+
+ test "reports la vitrine cohort membership from the matcher" do
+ website = Website.new(
+ name: "Tout Culture",
+ seedurl: "outside-seed",
+ graph_name: "http://example.com/tout-culture",
+ default_language: "en"
+ )
+
+ assert_equal true, website.lavitrine_pipeline?
+ assert_equal "lavitrine_pipeline", website.distillator_primary_cohort_key
+ assert_equal "La Vitrine pipeline", website.distillator_primary_cohort_label
+ end
+
+ test "website exposes latest transition evidence helpers" do
+ website = websites(:one)
+ evidence = website.transition_evidences.create!(
+ url: "https://example.org/export",
+ check_kind: "export_diff",
+ status: "checked",
+ export_diff_checked: true,
+ checked_at: Time.current
+ )
+
+ assert_equal evidence, website.latest_transition_evidence("export_diff")
+ assert_equal evidence, website.latest_transition_evidences_by_kind["export_diff"]
+ end
+
end
diff --git a/test/presenters/harmonized_table_headers_test.rb b/test/presenters/harmonized_table_headers_test.rb
new file mode 100644
index 00000000..4008fd8c
--- /dev/null
+++ b/test/presenters/harmonized_table_headers_test.rb
@@ -0,0 +1,116 @@
+require "test_helper"
+
+class HarmonizedTableHeadersTest < ActiveSupport::TestCase
+ test "events headers expose sortable title date and archive date" do
+ assert_equal(
+ [
+ { label: "Title", sort_key: "title" },
+ { label: "Date", sort_key: "date" },
+ { label: "Archive date", sort_key: "archive_date" },
+ { label: "Status" },
+ { label: "Actions" }
+ ],
+ HarmonizedTableHeaders.events
+ )
+ end
+
+ test "resources headers expose sortable rdf uri class name and archive date" do
+ assert_equal(
+ [
+ { label: "Rdf uri", sort_key: "rdf_uri" },
+ { label: "Class", sort_key: "rdfs_class_name" },
+ { label: "Name", sort_key: "name" },
+ { label: "Archive date", sort_key: "archive_date" },
+ { label: "Actions" }
+ ],
+ HarmonizedTableHeaders.resources
+ )
+ end
+
+ test "sources headers expose readable inventory columns" do
+ assert_equal(
+ [
+ { label: "ID", sort_key: "id" },
+ { label: "Property", sort_key: "property_id" },
+ { label: "Label", sort_key: "label" },
+ { label: "Algorithm value", sort_key: "algorithm_value" },
+ { label: "Selected", sort_key: "selected" },
+ { label: "Render JS", sort_key: "render_js" },
+ { label: "Auto review", sort_key: "auto_review" },
+ { label: "Last test", sort_key: "updated_at" },
+ { label: "Actions" }
+ ],
+ HarmonizedTableHeaders.sources
+ )
+ end
+
+ test "webpages headers support optional condenser cache column" do
+ without_condenser = HarmonizedTableHeaders.webpages(show_distillator_cache_column: false)
+ with_condenser = HarmonizedTableHeaders.webpages(show_distillator_cache_column: true)
+
+ refute_includes without_condenser.map { |header| header[:label] }, "Condenser Cache"
+ assert_includes with_condenser.map { |header| header[:label] }, "Condenser Cache"
+ assert_includes with_condenser, { label: "Updated", sort_key: "updated_at" }
+ end
+
+ test "reports headers expose sortable event title cache archive date webpage id and rdf uri" do
+ assert_equal(
+ [
+ { label: "Event title", sort_key: "event_title" },
+ { label: "Cache", sort_key: "cache" },
+ { label: "Webpage id", sort_key: "webpage_id" },
+ { label: "Archive date", sort_key: "archive_date" },
+ { label: "Rdf uri", sort_key: "rdf_uri" }
+ ],
+ HarmonizedTableHeaders.reports
+ )
+ end
+
+ test "places headers expose sortable uri based_on linked name and linked uri" do
+ assert_equal(
+ [
+ { label: "Event Series URI", sort_key: "rdf_uri" },
+ { label: "Based on", sort_key: "based_on" },
+ { label: "Class" },
+ { label: "Linked Name", sort_key: "linked_name" },
+ { label: "Linked URI", sort_key: "linked_uri" }
+ ],
+ HarmonizedTableHeaders.places
+ )
+ end
+
+ test "properties headers expose sortable id class label datatype expected class and uri" do
+ assert_equal(
+ [
+ { label: "ID", sort_key: "id" },
+ { label: "Rdfs class", sort_key: "rdfs_class_id" },
+ { label: "Label", sort_key: "label" },
+ { label: "Value datatype", sort_key: "value_datatype" },
+ { label: "Expected Class", sort_key: "expected_class" },
+ { label: "Uri", sort_key: "uri" },
+ { label: "Actions" }
+ ],
+ HarmonizedTableHeaders.properties
+ )
+ end
+
+ test "rdfs classes headers expose sortable name" do
+ assert_equal(
+ [
+ { label: "Name", sort_key: "name" },
+ { label: "Actions" }
+ ],
+ HarmonizedTableHeaders.rdfs_classes
+ )
+ end
+
+ test "statements headers support optional website column" do
+ with_website = HarmonizedTableHeaders.statements(show_seedurl_col: true)
+ without_website = HarmonizedTableHeaders.statements(show_seedurl_col: false)
+
+ assert_equal "Website", with_website[1][:label]
+ refute_includes without_website.map { |header| header[:label] }, "Website"
+ assert_includes with_website, { label: "Cache", sort_key: "cache" }
+ assert_includes with_website, { label: "Updated", sort_key: "updated_at" }
+ end
+end
diff --git a/test/presenters/trace_presenter_test.rb b/test/presenters/trace_presenter_test.rb
new file mode 100644
index 00000000..a1c75a49
--- /dev/null
+++ b/test/presenters/trace_presenter_test.rb
@@ -0,0 +1,78 @@
+require "test_helper"
+
+class TracePresenterTest < ActiveSupport::TestCase
+ test "trace_presenter detects errors" do
+ presenter = TracePresenter.new([
+ { error: "boom" }
+ ])
+
+ assert presenter.has_error?
+ end
+
+ test "trace_presenter returns steps from hash trace" do
+ presenter = TracePresenter.new({ steps: [{ step: 1 }] })
+
+ assert_equal 1, presenter.steps.size
+ end
+
+ test "visible? returns true when always" do
+ presenter = TracePresenter.new([])
+ cookies = { trace_visibility: "always" }
+
+ assert presenter.visible?(cookies)
+ end
+
+ test "visible? returns false when hidden" do
+ presenter = TracePresenter.new([])
+ cookies = { trace_visibility: "hidden" }
+
+ refute presenter.visible?(cookies)
+ end
+
+ test "visible? uses has_error? when auto" do
+ presenter = TracePresenter.new([{ error: "boom" }])
+ cookies = { trace_visibility: "auto" }
+
+ assert presenter.visible?(cookies)
+ end
+
+ test "mode defaults to 3" do
+ presenter = TracePresenter.new([])
+ cookies = {}
+
+ assert_equal 3, presenter.mode(cookies)
+ end
+
+ test "mode reads from cookies" do
+ presenter = TracePresenter.new([])
+ cookies = { trace_view_mode: "2" }
+
+ assert_equal 2, presenter.mode(cookies)
+ end
+
+ test "mode falls back when invalid value" do
+ presenter = TracePresenter.new([])
+ cookies = { trace_view_mode: "999" }
+
+ assert_equal 3, presenter.mode(cookies)
+ end
+
+ test "visibility is case insensitive" do
+ presenter = TracePresenter.new([])
+ cookies = { trace_visibility: "ALWAYS" }
+
+ assert presenter.visible?(cookies)
+ end
+
+ test "trace visibility matrix is deterministic across cookie modes" do
+ no_error = TracePresenter.new([])
+ with_error = TracePresenter.new([{ error: "boom" }])
+
+ assert_equal 3, no_error.mode({})
+ refute no_error.visible?({})
+ refute no_error.visible?({ "trace_visibility" => "hidden" })
+ assert with_error.visible?({ "trace_visibility" => "always" })
+ assert with_error.visible?({ "trace_visibility" => "auto" })
+ refute no_error.visible?({ "trace_visibility" => "auto" })
+ end
+end
diff --git a/test/queries/events/index_query_test.rb b/test/queries/events/index_query_test.rb
new file mode 100644
index 00000000..7d4cc18a
--- /dev/null
+++ b/test/queries/events/index_query_test.rb
@@ -0,0 +1,28 @@
+require "test_helper"
+
+class Events::IndexQueryTest < ActiveSupport::TestCase
+ test "filters by date range and sorts by title" do
+ results = Events::IndexQuery.call(
+ filters: { seedurl: "one", startDate: "2018-01-01", endDate: "2030-01-01" },
+ sort: "title",
+ direction: "asc",
+ page: 1,
+ per_page: 50
+ )
+
+ assert results.all? { |row| row[:title].present? }
+ end
+
+ test "falls back on invalid sort and direction" do
+ results = Events::IndexQuery.call(
+ filters: { seedurl: "one" },
+ sort: "bogus",
+ direction: "sideways",
+ page: 1,
+ per_page: 50
+ )
+
+ titles = results.map { |row| row[:title] }
+ assert_equal titles.sort, titles
+ end
+end
diff --git a/test/queries/places/index_query_test.rb b/test/queries/places/index_query_test.rb
new file mode 100644
index 00000000..9192395a
--- /dev/null
+++ b/test/queries/places/index_query_test.rb
@@ -0,0 +1,92 @@
+require "test_helper"
+
+class Places::IndexQueryTest < ActiveSupport::TestCase
+ def build_results_for(raw_places)
+ query = Places::IndexQuery.new(
+ filters: { seedurl: websites(:one).seedurl },
+ sort: "rdf_uri",
+ direction: "asc",
+ page: 1,
+ per_page: 50
+ )
+ query.stubs(:raw_places).returns(raw_places)
+ query.call
+ end
+
+ test "returns place rows for a seedurl" do
+ results = Places::IndexQuery.call(
+ filters: { seedurl: websites(:one).seedurl },
+ sort: "rdf_uri",
+ direction: "asc",
+ page: 1,
+ per_page: 50
+ )
+
+ assert results.is_a?(Enumerable)
+ end
+
+ test "falls back on invalid sort and direction" do
+ results = Places::IndexQuery.call(
+ filters: { seedurl: websites(:one).seedurl },
+ sort: "bogus",
+ direction: "sideways",
+ page: 1,
+ per_page: 50
+ )
+
+ uris = results.map { |row| row[:rdf_uri] }
+ assert_equal uris.sort, uris
+ end
+
+ test "returns place row for cache containing simple linked place array" do
+ results = build_results_for([
+ ["uri:simple", '["Wednesday @ Salle Andre-Mathieu", "Place", ["Salle Andre-Mathieu", "adr:salle-andre-mathieu"]]', "en", "http://example.com/simple"]
+ ])
+
+ row = results.first
+ assert_equal "uri:simple", row[:rdf_uri]
+ assert_equal "Wednesday @ Salle Andre-Mathieu", row[:linked_name]
+ assert_equal "adr:salle-andre-mathieu", row[:linked_uri]
+ end
+
+ test "returns place row for cache containing nested linked place arrays" do
+ results = build_results_for([
+ ["uri:nested", '[["Saturday @ Theatre des Muses", "Place", ["Theatre des Muses", "http://example.com/muses"]], ["Monday @ Theatre des Muses", "Place", ["Theatre des Muses", "http://example.com/muses"]]]', "en", "http://example.com/nested"]
+ ])
+
+ assert_equal 2, results.length
+ assert_equal ["Monday @ Theatre des Muses", "Saturday @ Theatre des Muses"], results.map { |row| row[:linked_name] }.sort
+ end
+
+ test "returns place row for cache containing JSON string payload" do
+ results = build_results_for([
+ ["uri:json-string", '"Main Hall"', "en", "http://example.com/json-string"]
+ ])
+
+ row = results.first
+ assert_equal "Main Hall", row[:linked_name]
+ assert_equal "", row[:linked_uri]
+ assert_equal "Place", row[:place_class]
+ end
+
+ test "returns place row for cache containing raw string fallback" do
+ results = build_results_for([
+ ["uri:raw-string", "Fallback Hall", "en", "http://example.com/raw-string"]
+ ])
+
+ row = results.first
+ assert_equal "Fallback Hall", row[:linked_name]
+ assert_equal "", row[:linked_uri]
+ end
+
+ test "returns place row for cache containing array fallback" do
+ results = build_results_for([
+ ["uri:array-fallback", ["Array Hall", "Place", ["Main Hall", "adr:array-hall"]], "en", "http://example.com/array-fallback"]
+ ])
+
+ row = results.first
+ assert_equal "Array Hall", row[:linked_name]
+ assert_equal "adr:array-hall", row[:linked_uri]
+ assert_equal "Place", row[:place_class]
+ end
+end
diff --git a/test/queries/properties/index_query_test.rb b/test/queries/properties/index_query_test.rb
new file mode 100644
index 00000000..623a7f76
--- /dev/null
+++ b/test/queries/properties/index_query_test.rb
@@ -0,0 +1,16 @@
+require "test_helper"
+
+class Properties::IndexQueryTest < ActiveSupport::TestCase
+ test "sorts by label and paginates" do
+ results = Properties::IndexQuery.call(filters: {}, sort: "label", direction: "asc", page: 1, per_page: 2)
+
+ assert_operator results.length, :<=, 2
+ assert_equal results.map(&:label).sort, results.map(&:label)
+ end
+
+ test "falls back on invalid sort and direction" do
+ results = Properties::IndexQuery.call(filters: {}, sort: "bogus", direction: "sideways", page: 1, per_page: 50)
+
+ assert_equal Property.order(:label).limit(results.length).pluck(:label), results.map(&:label)
+ end
+end
diff --git a/test/queries/rdfs_classes/index_query_test.rb b/test/queries/rdfs_classes/index_query_test.rb
new file mode 100644
index 00000000..23d71349
--- /dev/null
+++ b/test/queries/rdfs_classes/index_query_test.rb
@@ -0,0 +1,16 @@
+require "test_helper"
+
+class RdfsClasses::IndexQueryTest < ActiveSupport::TestCase
+ test "sorts by name and paginates" do
+ results = RdfsClasses::IndexQuery.call(filters: {}, sort: "name", direction: "asc", page: 1, per_page: 2)
+
+ assert_operator results.length, :<=, 2
+ assert_equal results.map(&:name).sort, results.map(&:name)
+ end
+
+ test "falls back on invalid sort and direction" do
+ results = RdfsClasses::IndexQuery.call(filters: {}, sort: "bogus", direction: "sideways", page: 1, per_page: 50)
+
+ assert_equal RdfsClass.order(:name).limit(results.length).pluck(:name), results.map(&:name)
+ end
+end
diff --git a/test/queries/reports/index_query_test.rb b/test/queries/reports/index_query_test.rb
new file mode 100644
index 00000000..e4eb338d
--- /dev/null
+++ b/test/queries/reports/index_query_test.rb
@@ -0,0 +1,46 @@
+require "test_helper"
+
+class Reports::IndexQueryTest < ActiveSupport::TestCase
+ test "filters by source and sorts by event title" do
+ results = Reports::IndexQuery.call(
+ filters: { source_id: sources(:one).id, startDate: "2018-01-01", endDate: "2030-01-01" },
+ sort: "event_title",
+ direction: "asc",
+ page: 1,
+ per_page: 50
+ )
+
+ assert results.is_a?(Enumerable)
+ end
+
+ test "falls back on invalid sort and direction" do
+ results = Reports::IndexQuery.call(
+ filters: { source_id: sources(:one).id },
+ sort: "bogus",
+ direction: "sideways",
+ page: 1,
+ per_page: 50
+ )
+
+ titles = results.map { |row| row[:event_title].to_s }
+ assert_equal titles.sort, titles
+ end
+
+ test "reports index query returns event title cache webpage archive date and rdf uri" do
+ results = Reports::IndexQuery.call(
+ filters: { source_id: sources(:one).id },
+ sort: "event_title",
+ direction: "asc",
+ page: 1,
+ per_page: 50
+ )
+
+ row = results.first
+ assert row.present?
+ assert_includes row.keys, :event_title
+ assert_includes row.keys, :cache
+ assert_includes row.keys, :webpage_id
+ assert_includes row.keys, :archive_date
+ assert_includes row.keys, :rdf_uri
+ end
+end
diff --git a/test/queries/resources/index_query_test.rb b/test/queries/resources/index_query_test.rb
new file mode 100644
index 00000000..f9ff805d
--- /dev/null
+++ b/test/queries/resources/index_query_test.rb
@@ -0,0 +1,29 @@
+require "test_helper"
+
+class Resources::IndexQueryTest < ActiveSupport::TestCase
+ test "returns resource rows for a seedurl" do
+ results = Resources::IndexQuery.call(
+ filters: { seedurl: websites(:one).seedurl },
+ sort: "rdf_uri",
+ direction: "asc",
+ page: 1,
+ per_page: 50
+ )
+
+ assert results.any?
+ assert results.all? { |row| row[:rdf_uri].present? }
+ end
+
+ test "falls back on invalid sort and direction" do
+ results = Resources::IndexQuery.call(
+ filters: { seedurl: websites(:one).seedurl },
+ sort: "bogus",
+ direction: "sideways",
+ page: 1,
+ per_page: 50
+ )
+
+ sorted = results.map { |row| row[:rdf_uri] }
+ assert_equal sorted.sort, sorted
+ end
+end
diff --git a/test/queries/sources/index_query_test.rb b/test/queries/sources/index_query_test.rb
new file mode 100644
index 00000000..6493051b
--- /dev/null
+++ b/test/queries/sources/index_query_test.rb
@@ -0,0 +1,160 @@
+require "test_helper"
+
+class Sources::IndexQueryTest < ActiveSupport::TestCase
+ setup do
+ @source_a = Source.create!(
+ algorithm_value: "query alpha selector",
+ selected: true,
+ selected_by: "Operator",
+ auto_review: true,
+ language: "en",
+ render_js: false,
+ property: properties(:one),
+ website: websites(:one),
+ updated_at: 3.days.ago
+ )
+
+ @source_b = Source.create!(
+ algorithm_value: "query beta rendered",
+ selected: false,
+ selected_by: "Operator",
+ auto_review: false,
+ language: "fr",
+ render_js: true,
+ property: properties(:two),
+ website: websites(:two),
+ updated_at: 1.day.ago
+ )
+
+ @source_c = Source.create!(
+ algorithm_value: "query gamma newest",
+ selected: true,
+ selected_by: "Operator",
+ auto_review: false,
+ language: "en",
+ render_js: false,
+ property: properties(:two),
+ website: websites(:two),
+ updated_at: Time.zone.now
+ )
+
+ @source_d = Source.create!(
+ algorithm_value: "query delta same property",
+ selected: false,
+ selected_by: "Operator",
+ auto_review: true,
+ language: "en",
+ render_js: true,
+ property: properties(:two),
+ website: websites(:one),
+ updated_at: 2.days.ago
+ )
+ end
+
+ test "filters by algorithm value term" do
+ records = Sources::IndexQuery.call(filters: { term: "rendered" }, sort: "algorithm_value", direction: "asc", page: 1, per_page: 50)
+
+ assert_equal [@source_b.id], records.map(&:id)
+ end
+
+ test "filters by selected" do
+ records = Sources::IndexQuery.call(filters: { term: "query", selected: "false" }, sort: "algorithm_value", direction: "asc", page: 1, per_page: 50)
+
+ assert_equal [@source_b.id, @source_d.id].sort, records.map(&:id).sort
+ end
+
+ test "filters by auto review" do
+ records = Sources::IndexQuery.call(filters: { term: "query", auto_review: "true" }, sort: "algorithm_value", direction: "asc", page: 1, per_page: 50)
+
+ assert_equal [@source_a.id, @source_d.id].sort, records.map(&:id).sort
+ end
+
+ test "filters by language" do
+ records = Sources::IndexQuery.call(filters: { term: "query", language: "fr" }, sort: "algorithm_value", direction: "asc", page: 1, per_page: 50)
+
+ assert_equal [@source_b.id], records.map(&:id)
+ end
+
+ test "filters by render js" do
+ records = Sources::IndexQuery.call(filters: { term: "query", render_js: "true" }, sort: "algorithm_value", direction: "asc", page: 1, per_page: 50)
+
+ assert_equal [@source_b.id, @source_d.id].sort, records.map(&:id).sort
+ end
+
+ test "filters by website id" do
+ records = Sources::IndexQuery.call(filters: { term: "query", website_id: websites(:one).id }, sort: "algorithm_value", direction: "asc", page: 1, per_page: 50)
+
+ assert_includes records.map(&:id), @source_a.id
+ assert_not_includes records.map(&:id), @source_b.id
+ end
+
+ test "filters by property id" do
+ records = Sources::IndexQuery.call(filters: { term: "query", property_id: properties(:one).id }, sort: "algorithm_value", direction: "asc", page: 1, per_page: 50)
+
+ assert_equal [@source_a.id], records.map(&:id)
+ end
+
+ test "defaults to property language and selected ordering" do
+ records = Sources::IndexQuery.call(filters: { term: "query" }, sort: "property_id", direction: "asc", page: 1, per_page: 50)
+
+ assert_operator records.index(@source_a), :<, records.index(@source_b) if @source_a.property_id < @source_b.property_id
+ assert_operator records.index(@source_b), :<, records.index(@source_a) if @source_b.property_id < @source_a.property_id
+ assert_operator records.index(@source_c), :<, records.index(@source_b)
+ assert_operator records.index(@source_c), :<, records.index(@source_d)
+ end
+
+ test "sorts by algorithm value" do
+ records = Sources::IndexQuery.call(filters: { term: "query" }, sort: "algorithm_value", direction: "asc", page: 1, per_page: 50)
+
+ assert_operator records.index(@source_a), :<, records.index(@source_b)
+ end
+
+ test "sorts by selected" do
+ records = Sources::IndexQuery.call(filters: { term: "query" }, sort: "selected", direction: "asc", page: 1, per_page: 50)
+
+ assert_equal false, records.first.selected
+ end
+
+ test "sorts by auto review" do
+ records = Sources::IndexQuery.call(filters: { term: "query" }, sort: "auto_review", direction: "desc", page: 1, per_page: 50)
+
+ assert_equal true, records.first.auto_review
+ end
+
+ test "sorts by language" do
+ records = Sources::IndexQuery.call(filters: { term: "query" }, sort: "language", direction: "asc", page: 1, per_page: 50)
+
+ assert_operator records.index(@source_a), :<, records.index(@source_b)
+ end
+
+ test "sorts by updated at" do
+ records = Sources::IndexQuery.call(filters: { term: "query" }, sort: "updated_at", direction: "desc", page: 1, per_page: 50)
+
+ assert_equal @source_c.id, records.first.id
+ end
+
+ test "falls back on invalid sort" do
+ records = Sources::IndexQuery.call(filters: { term: "query" }, sort: "bogus", direction: "asc", page: 1, per_page: 50)
+
+ expected_first = [@source_a, @source_b, @source_c, @source_d].min_by do |source|
+ [source.property_id, source.language.to_s, source.selected? ? 0 : 1, source.id]
+ end
+
+ assert_equal expected_first.id, records.first.id
+ end
+
+ test "falls back on invalid direction" do
+ records = Sources::IndexQuery.call(filters: { term: "query" }, sort: "updated_at", direction: "sideways", page: 1, per_page: 50)
+
+ assert_equal @source_a.id, records.first.id
+ end
+
+ test "paginates" do
+ page_one = Sources::IndexQuery.call(filters: { term: "query" }, sort: "property_id", direction: "asc", page: 1, per_page: 1)
+ page_two = Sources::IndexQuery.call(filters: { term: "query" }, sort: "property_id", direction: "asc", page: 2, per_page: 1)
+
+ assert_equal 1, page_one.length
+ assert_equal 1, page_two.length
+ assert_not_equal page_one.first.id, page_two.first.id
+ end
+end
diff --git a/test/queries/statements/index_query_test.rb b/test/queries/statements/index_query_test.rb
new file mode 100644
index 00000000..5a09689a
--- /dev/null
+++ b/test/queries/statements/index_query_test.rb
@@ -0,0 +1,22 @@
+require "test_helper"
+
+class Statements::IndexQueryTest < ActiveSupport::TestCase
+ test "filters by cache and status" do
+ results = Statements::IndexQuery.call(
+ filters: { cache: "MyString", status: "initial" },
+ sort: "cache",
+ direction: "asc",
+ page: 1,
+ per_page: 50
+ )
+
+ assert results.all? { |statement| statement.cache.include?("MyString") }
+ assert results.all? { |statement| statement.status == "initial" }
+ end
+
+ test "falls back on invalid sort and direction" do
+ results = Statements::IndexQuery.call(filters: {}, sort: "bogus", direction: "sideways", page: 1, per_page: 50)
+
+ assert_equal Statement.order(:id).limit(results.length).pluck(:id), results.map(&:id)
+ end
+end
diff --git a/test/queries/webpages/index_query_test.rb b/test/queries/webpages/index_query_test.rb
new file mode 100644
index 00000000..efb6b9f0
--- /dev/null
+++ b/test/queries/webpages/index_query_test.rb
@@ -0,0 +1,324 @@
+require "test_helper"
+
+class Webpages::IndexQueryTest < ActiveSupport::TestCase
+ setup do
+ @website_one = Website.create!(
+ name: "Query website one",
+ seedurl: "query-website-one",
+ graph_name: "https://example.org/query-website-one",
+ default_language: "en"
+ )
+ @website_two = Website.create!(
+ name: "Query website two",
+ seedurl: "query-website-two",
+ graph_name: "https://example.org/query-website-two",
+ default_language: "en"
+ )
+ @event_class = rdfs_classes(:one)
+ @place_class = rdfs_classes(:place)
+ @other_class = rdfs_classes(:two)
+
+ @active_publishable_event = Webpage.create!(
+ url: "https://example.org/query-active-publishable",
+ language: "en",
+ rdf_uri: "footlight:active-publishable-query",
+ rdfs_class: @event_class,
+ website: @website_one,
+ archive_date: 8.days.from_now,
+ updated_at: 1.day.ago
+ )
+ create_publishable_statements_for(@active_publishable_event)
+
+ @archived_publishable_event = Webpage.create!(
+ url: "https://example.org/query-archived-publishable",
+ language: "fr",
+ rdf_uri: "footlight:archived-publishable-query",
+ rdfs_class: @event_class,
+ website: @website_one,
+ archive_date: 2.days.ago,
+ updated_at: 3.days.ago
+ )
+ create_publishable_statements_for(@archived_publishable_event)
+
+ @internal_event = Webpage.create!(
+ url: "footlight:internal-event-query",
+ language: "en",
+ rdf_uri: "footlight:internal-event-query",
+ rdfs_class: @event_class,
+ website: @website_one,
+ archive_date: 8.days.from_now,
+ updated_at: 2.days.ago
+ )
+
+ @other_page = Webpage.create!(
+ url: "footlight:other-query",
+ language: "en",
+ rdf_uri: "footlight:other-query",
+ rdfs_class: @other_class,
+ website: @website_one,
+ archive_date: 8.days.from_now,
+ updated_at: 4.days.ago
+ )
+
+ @place_page = Webpage.create!(
+ url: "http://example.org/query-place",
+ language: "en",
+ rdf_uri: "footlight:place-query",
+ rdfs_class: @place_class,
+ website: @website_two,
+ archive_date: 5.days.from_now,
+ updated_at: Time.zone.now
+ )
+
+ @website_two_publishable_event = Webpage.create!(
+ url: "https://example.org/query-site-two-publishable",
+ language: "en",
+ rdf_uri: "footlight:site-two-publishable-query",
+ rdfs_class: @event_class,
+ website: @website_two,
+ archive_date: 10.days.from_now,
+ updated_at: 30.minutes.ago
+ )
+ create_publishable_statements_for(@website_two_publishable_event)
+ end
+
+ test "default query returns active publishable event webpages only" do
+ records = Webpages::IndexQuery.call(
+ filters: { website_id: @website_one.id },
+ sort: "url",
+ direction: "asc",
+ page: 1,
+ per_page: 50
+ )
+
+ assert_equal [@active_publishable_event.id], records.map(&:id)
+ end
+
+ test "default query excludes non publishable non event and archived webpages" do
+ records = Webpages::IndexQuery.call(
+ filters: {},
+ sort: "url",
+ direction: "asc",
+ page: 1,
+ per_page: 50
+ )
+
+ assert_includes records.map(&:id), @active_publishable_event.id
+ assert_includes records.map(&:id), @website_two_publishable_event.id
+ assert_not_includes records.map(&:id), @archived_publishable_event.id
+ assert_not_includes records.map(&:id), @internal_event.id
+ assert_not_includes records.map(&:id), @other_page.id
+ assert_not_includes records.map(&:id), @place_page.id
+ end
+
+ test "scope all returns all webpages for selected website" do
+ records = Webpages::IndexQuery.call(
+ filters: { website_id: @website_one.id, scope: "all" },
+ sort: "url",
+ direction: "asc",
+ page: 1,
+ per_page: 50
+ )
+
+ assert_equal [
+ @active_publishable_event.id,
+ @archived_publishable_event.id,
+ @internal_event.id,
+ @other_page.id
+ ].sort, records.map(&:id).sort
+ end
+
+ test "scope all publishable false returns only non publishable webpages" do
+ records = Webpages::IndexQuery.call(
+ filters: { website_id: @website_one.id, scope: "all", publishable: "false" },
+ sort: "url",
+ direction: "asc",
+ page: 1,
+ per_page: 50
+ )
+
+ assert_equal [@internal_event.id, @other_page.id].sort, records.map(&:id).sort
+ end
+
+ test "publishable false outside scope all falls back to default publishable scope" do
+ records = Webpages::IndexQuery.call(
+ filters: { website_id: @website_one.id, publishable: "false" },
+ sort: "url",
+ direction: "asc",
+ page: 1,
+ per_page: 50
+ )
+
+ assert_equal [@active_publishable_event.id], records.map(&:id)
+ end
+
+ test "scope all filters by publishable true" do
+ records = Webpages::IndexQuery.call(
+ filters: { website_id: @website_one.id, scope: "all", publishable: "true" },
+ sort: "url",
+ direction: "asc",
+ page: 1,
+ per_page: 50
+ )
+
+ assert_equal [@active_publishable_event.id, @archived_publishable_event.id].sort, records.map(&:id).sort
+ end
+
+ test "scope all archive filter still applies within the broader base scope" do
+ records = Webpages::IndexQuery.call(
+ filters: { website_id: @website_one.id, scope: "all", archive_state: "archived" },
+ sort: "url",
+ direction: "asc",
+ page: 1,
+ per_page: 50
+ )
+
+ assert_equal [@archived_publishable_event.id], records.map(&:id)
+ end
+
+ test "scope all filters by public source urls" do
+ records = Webpages::IndexQuery.call(
+ filters: { website_id: @website_one.id, scope: "all", url_kind: "public" },
+ sort: "url",
+ direction: "asc",
+ page: 1,
+ per_page: 50
+ )
+
+ assert_includes records.map(&:id), @active_publishable_event.id
+ assert_includes records.map(&:id), @archived_publishable_event.id
+ assert_not_includes records.map(&:id), @internal_event.id
+ end
+
+ test "scope all filters by internal uris" do
+ records = Webpages::IndexQuery.call(
+ filters: { website_id: @website_one.id, scope: "all", url_kind: "internal" },
+ sort: "url",
+ direction: "asc",
+ page: 1,
+ per_page: 50
+ )
+
+ assert_equal [@internal_event.id, @other_page.id].sort, records.map(&:id).sort
+ end
+
+ test "scope all filters by rdfs class name" do
+ records = Webpages::IndexQuery.call(
+ filters: { website_id: @website_one.id, scope: "all", rdfs_class: "Event" },
+ sort: "url",
+ direction: "asc",
+ page: 1,
+ per_page: 50
+ )
+
+ assert_equal [
+ @active_publishable_event.id,
+ @archived_publishable_event.id,
+ @internal_event.id
+ ].sort, records.map(&:id).sort
+ end
+
+ test "paginate false returns all matching rows" do
+ 30.times do |index|
+ webpage = Webpage.create!(
+ url: "https://example.org/query-many-#{index}",
+ language: "en",
+ rdf_uri: "footlight:many-query:#{index}",
+ rdfs_class: @event_class,
+ website: @website_one,
+ archive_date: 10.days.from_now
+ )
+ create_publishable_statements_for(webpage, algorithm_value: "paginate-false-#{index}")
+ end
+
+ records = Webpages::IndexQuery.call(
+ filters: { website_id: @website_one.id },
+ sort: "url",
+ direction: "asc",
+ page: 2,
+ per_page: 1,
+ paginate: false
+ )
+
+ assert_equal 31, records.length
+ end
+
+ test "paginate true still paginates when requested" do
+ page_one = Webpages::IndexQuery.call(
+ filters: { website_id: @website_one.id, scope: "all" },
+ sort: "url",
+ direction: "asc",
+ page: 1,
+ per_page: 1
+ )
+ page_two = Webpages::IndexQuery.call(
+ filters: { website_id: @website_one.id, scope: "all" },
+ sort: "url",
+ direction: "asc",
+ page: 2,
+ per_page: 1
+ )
+
+ assert_equal 1, page_one.length
+ assert_equal 1, page_two.length
+ assert_not_equal page_one.first.id, page_two.first.id
+ end
+
+ test "falls back on invalid sort and direction" do
+ records = Webpages::IndexQuery.call(
+ filters: { website_id: @website_one.id, scope: "all" },
+ sort: "bogus",
+ direction: "sideways",
+ page: 1,
+ per_page: 50
+ )
+ expected = Webpages::IndexQuery.call(
+ filters: { website_id: @website_one.id, scope: "all" },
+ sort: Webpages::IndexQuery::DEFAULT_SORT,
+ direction: Webpages::IndexQuery::DEFAULT_DIRECTION,
+ page: 1,
+ per_page: 50
+ )
+
+ assert_equal expected.map(&:id), records.map(&:id)
+ end
+
+ test "public normalize filters keeps website internal and ignores publishable false outside all scope" do
+ normalized = Webpages::IndexQuery.normalize_filters(
+ filters: { publishable: "false", page: "2", per_page: "1" },
+ website: @website_one
+ )
+
+ assert_equal @website_one.id, normalized[:website_id]
+ assert_nil normalized[:publishable]
+ assert_nil normalized[:scope]
+ assert_equal "2", normalized[:page]
+ assert_equal "1", normalized[:per_page]
+ end
+
+ private
+
+ def create_publishable_statements_for(webpage, algorithm_value: "query-test")
+ [
+ [properties(:four), "Publishable title"],
+ [properties(:location), '[["Salle","uri:place"]]'],
+ [properties(:six), '["2026-06-01T20:00:00-04:00"]']
+ ].each do |property, cache|
+ source = Source.create!(
+ website: webpage.website,
+ property: property,
+ language: "en",
+ selected: true,
+ algorithm_value: algorithm_value
+ )
+
+ Statement.create!(
+ webpage: webpage,
+ source: source,
+ cache: cache,
+ status: "ok"
+ )
+ Statement.where(webpage: webpage, source: source).update_all(status: "ok")
+ end
+ end
+end
diff --git a/test/scripts/distillator_shadow_log_summary_test.rb b/test/scripts/distillator_shadow_log_summary_test.rb
new file mode 100644
index 00000000..f0354816
--- /dev/null
+++ b/test/scripts/distillator_shadow_log_summary_test.rb
@@ -0,0 +1,44 @@
+require "test_helper"
+require "open3"
+require_relative "../../script/distillator_shadow_log_summary"
+
+class DistillatorShadowLogSummaryTest < ActiveSupport::TestCase
+ FIXTURE = Rails.root.join("test", "fixtures", "files", "distillator_shadow_logs", "sample.log")
+
+ test "summarizes shadow observation log metrics" do
+ summary = DistillatorShadowLogSummary.call(FIXTURE)
+
+ assert_equal 5, summary[:total_comparisons]
+ assert_equal 2, summary[:matched_count]
+ assert_equal 3, summary[:mismatch_count]
+ assert_equal 1, summary[:shadow_errors_count]
+ assert_equal 1, summary[:internal_ineligible_count]
+ assert_equal 1, summary[:blocked_fetch_count]
+ assert_equal 2, summary[:mismatch_fields]["body_hash"]
+ assert_equal 2, summary[:mismatch_fields]["final_url"]
+ assert_equal 1, summary[:mismatch_fields]["wringer_error_type"]
+ end
+
+ test "prints concise report without full body content" do
+ report = DistillatorShadowLogSummary.new(FIXTURE).report
+
+ assert_includes report, "Total comparisons: 5"
+ assert_includes report, "Matched: 2"
+ assert_includes report, "Mismatched: 3"
+ assert_includes report, "body_hash: 2"
+ assert_includes report, "final_url: 2"
+ refute_includes report, "cached",
+ body: "cached",
+ name: "Cached",
+ scrape_date: Time.zone.now,
+ successful_refresh: Time.zone.now,
+ http_response_code: 200,
+ signals: {
+ "network_status" => "ok",
+ "content_type" => "html",
+ "redirect_type" => "normal",
+ "fetch_path" => "legacy",
+ "native_ineligible_reason" => "json_post"
+ },
+ hints: nil,
+ final_url: "https://example.org/final",
+ redirect_chain: nil,
+ created_at: Time.zone.now,
+ updated_at: Time.zone.now
+ )
+
+ payload = Distillator::FetchCacheCompatSerializer.new(cache).as_json
+
+ assert_equal cache.id, payload[:id]
+ assert_equal cache.uri_key, payload[:uri]
+ assert_equal cache.uri_key, payload[:uri_key]
+ assert_equal "http://example.org/cached", payload[:normalized_url]
+ assert_equal "Cached", payload[:name]
+ assert_equal true, payload[:has_html]
+ assert_equal "cached".bytesize, payload[:html_bytes]
+ assert_equal "cached".bytesize, payload[:body_bytes]
+ assert_equal "ok", payload[:network_status]
+ assert_equal "html", payload[:content_type]
+ assert_equal "normal", payload[:redirect_type]
+ assert_equal "legacy", payload[:fetch_path]
+ assert_equal "json_post", payload[:native_ineligible_reason]
+ assert_equal [], payload[:hints]
+ assert_equal [], payload[:redirect_chain]
+ assert_equal "healthy", payload[:health_status]
+ assert_equal "Healthy", payload[:health_label]
+ end
+end
diff --git a/test/services/distillator/cache_compare_test.rb b/test/services/distillator/cache_compare_test.rb
new file mode 100644
index 00000000..62ff175c
--- /dev/null
+++ b/test/services/distillator/cache_compare_test.rb
@@ -0,0 +1,497 @@
+require "test_helper"
+
+class Distillator::CacheCompareTest < ActiveSupport::TestCase
+ setup do
+ Distillator::FetchCache.delete_all
+ end
+
+ test "handles injected lookup and missing cache cases" do
+ key = CGI.escape("http://example.org/page")
+ Distillator::FetchCache.create!(
+ uri_key: key,
+ normalized_url: "http://example.org/page",
+ html: "condenser",
+ body: "condenser",
+ scrape_date: Time.zone.parse("2026-04-01 10:00:00"),
+ successful_refresh: Time.zone.parse("2026-04-01 09:00:00"),
+ http_response_code: 200,
+ headers: {},
+ signals: { "network_status" => "ok" },
+ hints: [],
+ final_url: "http://example.org/page",
+ redirect_chain: []
+ )
+
+ both = Distillator::CacheCompare.call(
+ uri: "http://example.org/page",
+ legacy_lookup: ->(_uri_key) do
+ {
+ html: "legacy",
+ scrape_date: "2026-04-01T10:00:00Z",
+ successful_refresh: "2026-04-01T09:00:00Z",
+ http_code: 200,
+ signals: { network_status: "ok" },
+ hints: [],
+ final_url: "http://example.org/page",
+ redirect_chain: []
+ }
+ end
+ )
+ assert_equal false, both.dig(:missing, :legacy)
+ assert_equal false, both.dig(:missing, :condenser)
+ assert_equal "injected_lookup", both[:legacy_source]
+ assert_equal "ok", both[:legacy_lookup_status]
+ assert_nil both[:legacy_lookup_error]
+ assert_equal "local_fetch_cache", both[:condenser_source]
+ assert_equal true, both.dig(:summary, :html_hash_difference)
+ assert_includes both.dig(:summary, :review_needed_diffs), :html_sha256
+ assert_equal "review", both.dig(:summary, :outcome)
+
+ legacy_missing = Distillator::CacheCompare.call(uri: "http://example.org/page", legacy_lookup: ->(_uri_key) { nil })
+ assert_equal true, legacy_missing.dig(:missing, :legacy)
+ assert_equal "injected_lookup", legacy_missing[:legacy_source]
+ assert_equal false, legacy_missing.dig(:summary, :promotable)
+ assert_equal "unknown", legacy_missing.dig(:summary, :outcome)
+ assert_equal "Legacy cache missing.", legacy_missing.dig(:summary, :primary_reason)
+
+ Distillator::FetchCache.delete_all
+ distillator_missing = Distillator::CacheCompare.call(uri: "http://example.org/page", legacy_lookup: ->(_uri_key) { { html: "legacy" } })
+ assert_equal true, distillator_missing.dig(:missing, :condenser)
+ assert_equal false, distillator_missing.dig(:summary, :promotable)
+ end
+
+ test "labels successful remote wringer lookup" do
+ endpoint = Distillator::WringerEndpoint::Result.new(
+ compatibility_base_url: "http://compat.example",
+ legacy_lookup_base_url: "http://wringer.example",
+ state: :remote_configured,
+ status_label: "Current Wringer: Remote configured",
+ status_detail: "http://wringer.example"
+ )
+ HTTParty.stubs(:get).returns(Struct.new(:body).new([{ html: "legacy" }].to_json))
+
+ result = Distillator::CacheCompare.call(uri: "http://example.org/page", wringer_endpoint: endpoint)
+
+ assert_equal "remote_wringer", result[:legacy_source]
+ assert_equal "ok", result[:legacy_lookup_status]
+ assert_nil result[:legacy_lookup_error]
+ end
+
+ test "hydrates remote wringer html and title from compatibility endpoint when search payload omits body" do
+ endpoint = Distillator::WringerEndpoint::Result.new(
+ compatibility_base_url: "https://compat.example",
+ legacy_lookup_base_url: "https://wringer.example",
+ state: :remote_configured,
+ status_label: "Current Wringer: Remote configured",
+ status_detail: "https://wringer.example"
+ )
+ search_payload = [{
+ "http_response_code" => 200,
+ "successful_refresh" => "2026-03-06T16:47:36Z",
+ "name" => "Legacy listing title"
+ }]
+ wring_payload = {
+ "html" => "Legacy hydrated title cached",
+ "http_code" => 200,
+ "successful_refresh" => "2026-03-06T16:47:36Z",
+ "final_url" => "https://example.org/events/match-dimprovisation",
+ "signals" => { "content_success" => true }
+ }
+
+ HTTParty.expects(:get).with(
+ "https://wringer.example/websites.json",
+ query: { term: CGI.escape("https://example.org/events/match-dimprovisation?lang=fr") }
+ ).returns(Struct.new(:body).new(search_payload.to_json))
+ HTTParty.expects(:get).with(
+ "https://compat.example/websites/wring.json",
+ query: { uri: "https://example.org/events/match-dimprovisation?lang=fr" }
+ ).returns(Struct.new(:body).new(wring_payload.to_json))
+
+ result = Distillator::CacheCompare.call(
+ uri: "https://example.org/events/match-dimprovisation?lang=fr",
+ wringer_endpoint: endpoint
+ )
+
+ assert_equal "ok", result[:legacy_lookup_status]
+ assert_equal "Legacy hydrated title cached", result.dig(:legacy_cache, :html)
+ assert_equal "Legacy hydrated title", result.dig(:legacy_cache, :title)
+ assert_equal false, result.dig(:missing, :legacy)
+ end
+
+ test "hydration request stays read only and sends only the normalized url query" do
+ endpoint = Distillator::WringerEndpoint::Result.new(
+ compatibility_base_url: "https://compat.example",
+ legacy_lookup_base_url: "https://wringer.example",
+ state: :remote_configured,
+ status_label: "Current Wringer: Remote configured",
+ status_detail: "https://wringer.example"
+ )
+ normalized_url = "https://example.org/evenements/cafe?lang=fr&ville=trois-rivieres"
+ uri_key = CGI.escape(normalized_url)
+
+ sequence = sequence("read_only_hydration")
+
+ HTTParty.expects(:get).in_sequence(sequence).with(
+ "https://wringer.example/websites.json",
+ query: { term: uri_key }
+ ).returns(Struct.new(:body).new([{ "http_response_code" => 200 }].to_json))
+ HTTParty.expects(:get).in_sequence(sequence).with(
+ "https://compat.example/websites/wring.json",
+ query: { uri: normalized_url }
+ ).returns(Struct.new(:body).new({ "html" => "Hydrated " }.to_json))
+
+ result = Distillator::CacheCompare.call(uri: normalized_url, wringer_endpoint: endpoint)
+
+ assert_equal "ok", result[:legacy_lookup_status]
+ assert_equal "Hydrated", result.dig(:legacy_cache, :title)
+ end
+
+ test "hydration request ignores compare options and never sends legacy refresh flags" do
+ endpoint = Distillator::WringerEndpoint::Result.new(
+ compatibility_base_url: "https://compat.example",
+ legacy_lookup_base_url: "https://wringer.example",
+ state: :remote_configured,
+ status_label: "Current Wringer: Remote configured",
+ status_detail: "https://wringer.example"
+ )
+ normalized_url = "https://example.org/evenements/cafe?lang=fr"
+ HTTParty.expects(:get).with(
+ "https://wringer.example/websites.json",
+ query: { term: CGI.escape(normalized_url) }
+ ).returns(Struct.new(:body).new([{ "http_response_code" => 200 }].to_json))
+ HTTParty.expects(:get).with(
+ "https://compat.example/websites/wring.json",
+ query: { uri: normalized_url }
+ ).returns(Struct.new(:body).new({ "html" => "Hydrated " }.to_json))
+
+ result = Distillator::CacheCompare.call(
+ uri: normalized_url,
+ include_fragment: true,
+ wringer_endpoint: endpoint
+ )
+
+ assert_equal "ok", result[:legacy_lookup_status]
+ assert_equal "Hydrated", result.dig(:legacy_cache, :title)
+ assert_equal false, result.dig(:summary, :promotable)
+ end
+
+ test "keeps legacy row visible when wringer body endpoint omits html" do
+ endpoint = Distillator::WringerEndpoint::Result.new(
+ compatibility_base_url: "https://compat.example",
+ legacy_lookup_base_url: "https://wringer.example",
+ state: :remote_configured,
+ status_label: "Current Wringer: Remote configured",
+ status_detail: "https://wringer.example"
+ )
+ key = CGI.escape("https://example.org/events/body-omitted")
+ fresh_cache = Distillator::FetchCache.create!(
+ uri_key: key,
+ normalized_url: "https://example.org/events/body-omitted",
+ html: "Condenser title fresh",
+ body: "Condenser title fresh",
+ scrape_date: Time.zone.now,
+ successful_refresh: Time.zone.now,
+ http_response_code: 200,
+ headers: {},
+ signals: { "content_success" => true, "transport_success" => true },
+ hints: [],
+ final_url: "https://example.org/events/body-omitted/final",
+ redirect_chain: []
+ )
+ HTTParty.stubs(:get).with("https://wringer.example/websites.json", query: { term: key }).returns(
+ Struct.new(:body).new([{
+ "http_response_code" => 200,
+ "successful_refresh" => "2026-03-06T16:47:36Z",
+ "final_url" => "https://example.org/events/body-omitted",
+ "signals" => { "transport_success" => true }
+ }].to_json)
+ )
+ HTTParty.stubs(:get).with("https://compat.example/websites/wring.json", query: { uri: "https://example.org/events/body-omitted" }).returns(
+ Struct.new(:body).new({ "http_code" => 200, "successful_refresh" => "2026-03-06T16:47:36Z" }.to_json)
+ )
+
+ result = Distillator::CacheCompare.call(
+ uri: "https://example.org/events/body-omitted",
+ wringer_endpoint: endpoint
+ )
+
+ assert_equal "body_omitted", result[:legacy_lookup_status]
+ assert_equal "legacy_body_omitted", result[:legacy_lookup_error]
+ assert_equal false, result.dig(:missing, :legacy)
+ assert_not_includes result.dig(:summary, :blocking_regressions), :html_sha256
+ assert_not_includes result.dig(:summary, :blocking_regressions), :content_success
+ assert_not_includes result.dig(:summary, :blocking_regressions), :final_url
+ assert_includes result.dig(:summary, :unknown_diffs), :html_sha256
+ assert_includes result.dig(:summary, :unknown_diffs), :content_success
+ assert_includes result.dig(:summary, :unknown_diffs), :final_url
+ assert_equal "Condenser title fresh", fresh_cache.reload.html
+ assert_equal false, result.dig(:summary, :promotable)
+ assert_equal "unknown", result.dig(:summary, :outcome)
+ assert_equal "Legacy Wringer body was omitted from the comparison endpoint.", result.dig(:summary, :primary_reason)
+ end
+
+ test "metadata enrichments are review-needed rather than blocking when legacy fields are null" do
+ key = CGI.escape("https://example.org/events/review-needed")
+ Distillator::FetchCache.create!(
+ uri_key: key,
+ normalized_url: "https://example.org/events/review-needed",
+ html: "Shared title cached html differs",
+ body: "",
+ scrape_date: Time.zone.now,
+ successful_refresh: Time.zone.now,
+ http_response_code: 200,
+ headers: {},
+ signals: {
+ "content_type" => "html",
+ "transport_success" => true,
+ "content_success" => true,
+ "network_status" => "ok"
+ },
+ hints: [],
+ final_url: "https://example.org/events/review-needed/final",
+ redirect_chain: []
+ )
+
+ result = Distillator::CacheCompare.call(
+ uri: "https://example.org/events/review-needed",
+ legacy_lookup: ->(_uri_key) do
+ {
+ html: "Shared title legacy html differs",
+ http_code: 200,
+ successful_refresh: "2026-03-06T16:47:36Z",
+ signals: { "network_status" => "ok" }
+ }
+ end
+ )
+
+ assert_includes result.dig(:summary, :review_needed_diffs), :html_sha256
+ assert_not_includes result.dig(:summary, :blocking_regressions), :content_type
+ assert_not_includes result.dig(:summary, :blocking_regressions), :final_url
+ assert_not_includes result.dig(:summary, :blocking_regressions), :transport_success
+ assert_not_includes result.dig(:summary, :blocking_regressions), :content_success
+ assert_includes result.dig(:summary, :review_needed_diffs), :content_type
+ assert_includes result.dig(:summary, :review_needed_diffs), :final_url
+ assert_includes result.dig(:summary, :review_needed_diffs), :transport_success
+ assert_includes result.dig(:summary, :review_needed_diffs), :content_success
+ assert_equal false, result.dig(:summary, :promotable)
+ assert_equal "Shared title", result.dig(:legacy_cache, :title)
+ assert_equal "Shared title", result.dig(:condenser_cache, :title)
+ end
+
+ test "strict comparison policy keeps raw html drift blocking" do
+ key = CGI.escape("https://example.org/events/strict-review-needed")
+ Distillator::FetchCache.create!(
+ uri_key: key,
+ normalized_url: "https://example.org/events/strict-review-needed",
+ html: "Shared title cached html differs",
+ body: "",
+ scrape_date: Time.zone.now,
+ successful_refresh: Time.zone.now,
+ http_response_code: 200,
+ headers: {},
+ signals: { "content_type" => "html", "transport_success" => true, "content_success" => true, "network_status" => "ok" },
+ hints: [],
+ final_url: "https://example.org/events/strict-review-needed",
+ redirect_chain: []
+ )
+
+ result = Distillator::CacheCompare.call(
+ uri: "https://example.org/events/strict-review-needed",
+ comparison_policy: :strict,
+ legacy_lookup: ->(_uri_key) do
+ {
+ html: "Shared title legacy html differs",
+ http_code: 200,
+ successful_refresh: "2026-03-06T16:47:36Z",
+ signals: { "network_status" => "ok" }
+ }
+ end
+ )
+
+ assert_includes result.dig(:summary, :blocking_regressions), :html_sha256
+ assert_equal "blocked", result.dig(:summary, :outcome)
+ end
+
+ test "search uses uri_key once and hydration uses normalized url with encoded inputs" do
+ endpoint = Distillator::WringerEndpoint::Result.new(
+ compatibility_base_url: "https://compat.example",
+ legacy_lookup_base_url: "https://wringer.example",
+ state: :remote_configured,
+ status_label: "Current Wringer: Remote configured",
+ status_detail: "https://wringer.example"
+ )
+ raw_url = "https://example.org/evenements/caf%C3%A9?categorie=arts%20vivants&lang=fr"
+ key = Distillator::WringerUrlKey.call(raw_url)
+
+ HTTParty.expects(:get).with(
+ "https://wringer.example/websites.json",
+ query: { term: key.uri_key }
+ ).returns(Struct.new(:body).new([{ "http_response_code" => 200 }].to_json))
+ HTTParty.expects(:get).with(
+ "https://compat.example/websites/wring.json",
+ query: { uri: key.normalized_url }
+ ).returns(Struct.new(:body).new({ "html" => "Cafe " }.to_json))
+
+ result = Distillator::CacheCompare.call(uri: raw_url, wringer_endpoint: endpoint)
+
+ assert_equal "ok", result[:legacy_lookup_status]
+ assert_equal "Cafe", result.dig(:legacy_cache, :title)
+ end
+
+ test "labels failed remote wringer lookup without raising" do
+ endpoint = Distillator::WringerEndpoint::Result.new(
+ compatibility_base_url: "http://compat.example",
+ legacy_lookup_base_url: "http://wringer.example",
+ state: :remote_configured,
+ status_label: "Current Wringer: Remote configured",
+ status_detail: "http://wringer.example"
+ )
+ HTTParty.stubs(:get).raises(SocketError, "wringer unavailable")
+
+ result = Distillator::CacheCompare.call(uri: "http://example.org/page", wringer_endpoint: endpoint)
+
+ assert_equal "remote_wringer", result[:legacy_source]
+ assert_equal "unreachable", result[:legacy_lookup_status]
+ assert_match "wringer unavailable", result[:legacy_lookup_error]
+ assert_equal true, result.dig(:missing, :legacy)
+ assert_not_equal "review", result.dig(:summary, :outcome)
+ end
+
+ test "records missing config without attempting localhost" do
+ endpoint = Distillator::WringerEndpoint::Result.new(
+ compatibility_base_url: nil,
+ legacy_lookup_base_url: nil,
+ state: :missing_config,
+ status_label: "Current Wringer: Missing staging config",
+ status_detail: "comparisons disabled"
+ )
+
+ HTTParty.expects(:get).never
+
+ result = Distillator::CacheCompare.call(uri: "http://example.org/page", wringer_endpoint: endpoint)
+
+ assert_equal "missing_config", result[:legacy_source]
+ assert_equal "missing_config", result[:legacy_lookup_status]
+ assert_equal "missing_config", result[:legacy_lookup_error]
+ assert_equal true, result.dig(:missing, :legacy)
+ end
+
+ test "uses the provided fresh condenser fetch result instead of reloading stale local cache" do
+ key = CGI.escape("http://example.org/page")
+ stale_cache = Distillator::FetchCache.create!(
+ uri_key: key,
+ normalized_url: "http://example.org/page",
+ html: "stale",
+ body: "stale",
+ scrape_date: 2.days.ago,
+ successful_refresh: 2.days.ago,
+ http_response_code: 200,
+ headers: {},
+ signals: { "network_status" => "ok" },
+ hints: [],
+ final_url: "http://example.org/page",
+ redirect_chain: []
+ )
+ fresh_cache = Distillator::FetchCache.new(
+ uri_key: key,
+ normalized_url: "http://example.org/page",
+ html: "fresh",
+ body: "fresh",
+ scrape_date: Time.zone.now,
+ successful_refresh: Time.zone.now,
+ http_response_code: 200,
+ headers: {},
+ signals: { "network_status" => "ok" },
+ hints: [],
+ final_url: "http://example.org/page",
+ redirect_chain: []
+ )
+ condenser_result = Distillator::FetchCacheStore::Result.new(
+ status: :ok,
+ body: "fresh",
+ html: "fresh",
+ headers: {},
+ final_url: "http://example.org/page",
+ redirect_chain: [],
+ http_response_code: 200,
+ signals: { "network_status" => "ok" },
+ hints: [],
+ duration_ms: 10,
+ cache_hit: false,
+ cache_write: true,
+ cache_reason: "force_scrape",
+ uri_key: key,
+ normalized_url: "http://example.org/page",
+ fetch_path: "native",
+ name: "fresh",
+ scrape_date: Time.zone.now,
+ successful_refresh: Time.zone.now,
+ cache: fresh_cache
+ )
+
+ result = Distillator::CacheCompare.call(
+ uri: "http://example.org/page",
+ condenser_result: condenser_result,
+ legacy_lookup: ->(_uri_key) { { html: "legacy" } }
+ )
+
+ assert_equal "fresh", result.dig(:condenser_cache, :html)
+ assert_equal "stale", stale_cache.reload.html
+ end
+
+ test "metadata-only differences stay ready with metadata notes" do
+ key = CGI.escape("https://example.org/events/metadata-only")
+ now = Time.zone.now
+ Distillator::FetchCache.create!(
+ uri_key: key,
+ normalized_url: "https://example.org/events/metadata-only",
+ html: "Shared title same",
+ body: "Shared title same",
+ scrape_date: now,
+ successful_refresh: now,
+ http_response_code: 200,
+ headers: {},
+ signals: {
+ "content_type" => "html",
+ "transport_success" => true,
+ "content_success" => true,
+ "network_status" => "ok"
+ },
+ hints: ["condenser-extra-note"],
+ final_url: "https://example.org/events/metadata-only",
+ redirect_chain: ["https://example.org/events/metadata-only"]
+ )
+
+ result = Distillator::CacheCompare.call(
+ uri: "https://example.org/events/metadata-only",
+ legacy_lookup: ->(_uri_key) do
+ {
+ html: "Shared title same",
+ http_code: 200,
+ scrape_date: now.iso8601,
+ successful_refresh: now.iso8601,
+ signals: {
+ "content_type" => "html",
+ "transport_success" => true,
+ "content_success" => true,
+ "network_status" => "ok"
+ },
+ hints: [],
+ final_url: "https://example.org/events/metadata-only",
+ redirect_chain: []
+ }
+ end
+ )
+
+ assert_equal [], result.dig(:summary, :blocking_regressions)
+ assert_equal [], result.dig(:summary, :review_needed_diffs)
+ assert_equal [], result.dig(:summary, :unknown_diffs)
+ assert_includes result.dig(:summary, :metadata_only_diffs), :hints
+ assert_includes result.dig(:summary, :metadata_only_diffs), :redirect_chain
+ assert_equal true, result.dig(:summary, :promotable)
+ assert_equal "ready_with_metadata_notes", result.dig(:summary, :outcome)
+ assert_match(/\AMetadata notes:/, result.dig(:summary, :primary_reason))
+ assert_includes result.dig(:summary, :primary_reason), "redirect_chain"
+ assert_includes result.dig(:summary, :primary_reason), "hints"
+ end
+end
diff --git a/test/services/distillator/cache_fetch_command_test.rb b/test/services/distillator/cache_fetch_command_test.rb
new file mode 100644
index 00000000..0153cc62
--- /dev/null
+++ b/test/services/distillator/cache_fetch_command_test.rb
@@ -0,0 +1,166 @@
+require "test_helper"
+
+class Distillator::CacheFetchCommandTest < ActiveSupport::TestCase
+ test "normal command maps to force scrape without phantom or post" do
+ fetch_result = Struct.new(:cache).new(nil)
+ fetcher = mock("fetcher")
+ guard = stub(check_url: Distillator::FetchGuard::Result.new(allowed: true))
+ fetcher.expects(:fetch).with do |kwargs|
+ assert_equal "https://example.org/events", kwargs[:uri]
+ assert_equal true, kwargs[:force_scrape]
+ assert_equal false, kwargs[:use_phantomjs]
+ assert_equal false, kwargs[:json_post]
+ assert_equal false, kwargs[:include_fragment]
+ true
+ end.returns(fetch_result)
+
+ result = Distillator::CacheFetchCommand.new(
+ params: { uri: "https://example.org/events", fetch_kind: "normal" },
+ fetcher: fetcher,
+ guard: guard
+ ).call
+
+ assert_equal true, result.ok?
+ assert_equal [], result.errors
+ assert_equal fetch_result, result.fetch_result
+ end
+
+ test "missing fetch kind defaults to normal" do
+ fetcher = mock("fetcher")
+ guard = stub(check_url: Distillator::FetchGuard::Result.new(allowed: true))
+ fetcher.expects(:fetch).with do |kwargs|
+ assert_equal false, kwargs[:use_phantomjs]
+ assert_equal false, kwargs[:json_post]
+ true
+ end.returns(Struct.new(:cache).new(nil))
+
+ result = Distillator::CacheFetchCommand.new(
+ params: { uri: "https://example.org/default" },
+ fetcher: fetcher,
+ guard: guard
+ ).call
+
+ assert_equal true, result.ok?
+ end
+
+ test "blank fetch kind defaults to normal" do
+ fetcher = mock("fetcher")
+ guard = stub(check_url: Distillator::FetchGuard::Result.new(allowed: true))
+ fetcher.expects(:fetch).with do |kwargs|
+ assert_equal false, kwargs[:use_phantomjs]
+ assert_equal false, kwargs[:json_post]
+ true
+ end.returns(Struct.new(:cache).new(nil))
+
+ result = Distillator::CacheFetchCommand.new(
+ params: { uri: "https://example.org/default", fetch_kind: "" },
+ fetcher: fetcher,
+ guard: guard
+ ).call
+
+ assert_equal true, result.ok?
+ end
+
+ test "rendered command maps to phantom and include fragment" do
+ fetcher = mock("fetcher")
+ guard = stub(check_url: Distillator::FetchGuard::Result.new(allowed: true))
+ fetcher.expects(:fetch).with do |kwargs|
+ assert_equal true, kwargs[:force_scrape]
+ assert_equal true, kwargs[:use_phantomjs]
+ assert_equal true, kwargs[:include_fragment]
+ assert_equal false, kwargs[:json_post]
+ true
+ end.returns(Struct.new(:cache).new(nil))
+
+ result = Distillator::CacheFetchCommand.new(
+ params: { uri: "https://example.org/rendered", fetch_kind: "rendered", include_fragment: "false" },
+ fetcher: fetcher,
+ guard: guard
+ ).call
+
+ assert_equal true, result.ok?
+ end
+
+ test "post command maps to json post" do
+ fetcher = mock("fetcher")
+ guard = stub(check_url: Distillator::FetchGuard::Result.new(allowed: true))
+ fetcher.expects(:fetch).with do |kwargs|
+ assert_equal true, kwargs[:force_scrape]
+ assert_equal false, kwargs[:use_phantomjs]
+ assert_equal true, kwargs[:json_post]
+ true
+ end.returns(Struct.new(:cache).new(nil))
+
+ result = Distillator::CacheFetchCommand.new(
+ params: { uri: "https://example.org/post", fetch_kind: "post" },
+ fetcher: fetcher,
+ guard: guard
+ ).call
+
+ assert_equal true, result.ok?
+ end
+
+ test "invalid explicit fetch kind still fails" do
+ fetcher = mock("fetcher")
+ fetcher.expects(:fetch).never
+
+ result = Distillator::CacheFetchCommand.new(
+ params: { uri: "https://example.org/events", fetch_kind: "bogus" },
+ fetcher: fetcher
+ ).call
+
+ assert_equal false, result.ok?
+ assert_includes result.errors, "Fetch kind is invalid"
+ end
+
+ test "blank uri returns validation error and does not fetch" do
+ fetcher = mock("fetcher")
+ fetcher.expects(:fetch).never
+
+ result = Distillator::CacheFetchCommand.new(
+ params: { uri: "", fetch_kind: "normal" },
+ fetcher: fetcher
+ ).call
+
+ assert_equal false, result.ok?
+ assert_includes result.errors, "URI is invalid"
+ end
+
+ test "invalid uri returns validation error and does not fetch" do
+ fetcher = mock("fetcher")
+ fetcher.expects(:fetch).never
+
+ result = Distillator::CacheFetchCommand.new(
+ params: { uri: "http://[invalid", fetch_kind: "normal" },
+ fetcher: fetcher
+ ).call
+
+ assert_equal false, result.ok?
+ assert_includes result.errors, "URI is invalid"
+ end
+
+ test "optional fields preserve explicit user intent" do
+ fetcher = mock("fetcher")
+ guard = stub(check_url: Distillator::FetchGuard::Result.new(allowed: true))
+ fetcher.expects(:fetch).with do |kwargs|
+ assert_equal true, kwargs[:absolute_src]
+ assert_equal true, kwargs[:include_fragment]
+ assert_equal "12", kwargs[:force_scrape_every_hrs]
+ true
+ end.returns(Struct.new(:cache).new(nil))
+
+ result = Distillator::CacheFetchCommand.new(
+ params: {
+ uri: "https://example.org/events#details",
+ fetch_kind: "normal",
+ absolute_src: "true",
+ include_fragment: "true",
+ force_scrape_every_hrs: "12"
+ },
+ fetcher: fetcher,
+ guard: guard
+ ).call
+
+ assert_equal true, result.ok?
+ end
+end
diff --git a/test/services/distillator/cache_health_materializer_test.rb b/test/services/distillator/cache_health_materializer_test.rb
new file mode 100644
index 00000000..6481db19
--- /dev/null
+++ b/test/services/distillator/cache_health_materializer_test.rb
@@ -0,0 +1,335 @@
+# frozen_string_literal: true
+
+require "test_helper"
+
+# Tests for Distillator::CacheHealthMaterializer.
+#
+# These tests protect the save-time materialization layer used by the cache
+# operations UI. The materializer turns a FetchCache row's raw cache state into
+# queryable health and issue fields.
+#
+# Runtime chain under test:
+#
+# Distillator::FetchCache
+# -> before_save
+# -> Distillator::CacheHealthMaterializer.call(cache)
+# -> Distillator::CacheHealth.call(cache)
+# -> Distillator::WringerIssueSet.call(...)
+# -> config/wringer.yml
+# -> materialized FetchCache fields
+#
+# Important behaviors:
+# - health fields are derived from cache status;
+# - byte counts are materialized for blob-free index/summary pages;
+# - issue labels/severity/category are resolved from config/wringer.yml;
+# - issue keys/hints are queryable without reparsing cache HTML/body;
+# - when last-good content is preserved after a failed refresh, stale body text
+# must not become the primary issue.
+#
+class Distillator::CacheHealthMaterializerTest < ActiveSupport::TestCase
+ test "materializes health status sizes signals and hints" do
+ cache = build_cache(
+ uri: "http://example.org/json",
+ html: "cached",
+ body: '{"ok":true}',
+ scrape_date: Time.zone.now,
+ successful_refresh: Time.zone.now,
+ http_response_code: 200,
+ signals: { "network_status" => "ok", "content_type" => "json" },
+ hints: ["json_detected"],
+ final_url: "https://example.org/json",
+ redirect_chain: ["http://example.org/json", "https://example.org/json"]
+ )
+
+ Distillator::CacheHealthMaterializer.call(cache)
+
+ assert_equal "healthy", cache.health_status
+ assert_equal "ok", cache.health_severity
+ assert_equal ["successful_2xx_refresh"], cache.health_reasons
+ assert_equal "cached".bytesize, cache.html_bytes
+ assert_equal '{"ok":true}'.bytesize, cache.body_bytes
+ assert_equal true, cache.redirected
+ assert_equal "ok", cache.network_status
+ assert_equal "json", cache.content_type
+ assert_equal ["json_detected"], cache.hint_keys
+ end
+
+ test "materializes primary issue fields from signals and yaml metadata" do
+ cache = build_cache(
+ uri: "http://example.org/queue",
+ html: "cached",
+ body: "cached",
+ scrape_date: Time.zone.now,
+ successful_refresh: 1.day.ago,
+ http_response_code: 200,
+ signals: {
+ "network_status" => "ok",
+ "content_type" => "html",
+ "primary_issue_key" => "queue_it",
+ "primary_issue_error_code" => "system_queue",
+ "primary_issue_delete" => false
+ },
+ hints: ["queue_it"],
+ final_url: "http://example.org/queue"
+ )
+
+ Distillator::CacheHealthMaterializer.call(cache)
+
+ assert_equal "queue_it", cache.primary_issue_key
+ assert_equal "system_queue", cache.primary_issue_error_code
+ assert_equal "Queue-it waiting room", cache.primary_issue_label
+ assert_equal "warning", cache.primary_issue_severity
+ assert_equal "anti_bot", cache.primary_issue_category
+ assert_equal ["queue_it"], cache.issue_keys
+ assert_includes cache.issue_hints, "waiting_room"
+ assert_equal false, cache.delete_candidate
+ end
+
+ test "materializes issue fields from latest failure metadata when last good body is preserved" do
+ cache = build_cache(
+ uri: "http://example.org/timeout",
+ html: "last good",
+ body: "last good",
+ scrape_date: Time.zone.now,
+ successful_refresh: 1.hour.ago,
+ http_response_code: 500,
+ signals: {
+ "network_status" => "failed",
+ "content_type" => "html",
+ "primary_issue_key" => "timeout",
+ "primary_issue_error_code" => "timeout",
+ "primary_issue_severity" => "failed",
+ "primary_issue_category" => "network",
+ "primary_issue_label" => "Fetch timeout",
+ "primary_issue_delete" => false,
+ "last_good_preserved_failure" => true
+ },
+ hints: %w[timeout last_good_preserved_failure],
+ final_url: "http://example.org/timeout"
+ )
+
+ Distillator::CacheHealthMaterializer.call(cache)
+
+ assert_equal "timeout", cache.primary_issue_key
+ assert_includes cache.issue_keys, "timeout"
+ assert_includes cache.issue_keys, "http_5xx"
+ assert_includes cache.issue_hints, "timeout"
+ assert_equal false, cache.delete_candidate
+ end
+
+ test "last good preserved timeout is not overridden by stale error text in cached body" do
+ cache = build_cache(
+ uri: "http://example.org/stale-error-body",
+ html: "Error from old cached page",
+ body: "Error from old cached page",
+ scrape_date: Time.zone.now,
+ successful_refresh: 1.hour.ago,
+ http_response_code: nil,
+ signals: {
+ "network_status" => "failed",
+ "content_type" => "html",
+ "primary_issue_key" => "timeout",
+ "primary_issue_error_code" => "timeout",
+ "primary_issue_severity" => "failed",
+ "primary_issue_category" => "network",
+ "primary_issue_label" => "Fetch timeout",
+ "last_good_preserved_failure" => true
+ },
+ hints: %w[timeout last_good_preserved_failure],
+ final_url: "http://example.org/stale-error-body"
+ )
+
+ Distillator::CacheHealthMaterializer.call(cache)
+
+ assert_equal "timeout", cache.primary_issue_key
+ assert_equal "timeout", cache.primary_issue_error_code
+ assert_equal "network", cache.primary_issue_category
+ assert_includes cache.issue_keys, "timeout"
+ assert_not_includes cache.issue_keys, "generic_error_text"
+ end
+
+ test "last good preserved http 5xx is not overridden by stale POST call text in cached body" do
+ cache = build_cache(
+ uri: "http://example.org/stale-post-call-body",
+ html: "POST call from old cached page",
+ body: "POST call from old cached page",
+ scrape_date: Time.zone.now,
+ successful_refresh: 1.hour.ago,
+ http_response_code: 500,
+ signals: {
+ "network_status" => "ok",
+ "content_type" => "html",
+ "last_good_preserved_failure" => true
+ },
+ hints: ["last_good_preserved_failure"],
+ final_url: "http://example.org/stale-post-call-body"
+ )
+
+ Distillator::CacheHealthMaterializer.call(cache)
+
+ assert_equal "http_5xx", cache.primary_issue_key
+ assert_equal "http_server_error", cache.primary_issue_error_code
+ assert_equal "http", cache.primary_issue_category
+ assert_includes cache.issue_keys, "http_5xx"
+ assert_not_includes cache.issue_keys, "post_call_observed"
+ end
+
+ test "body text can still classify issue when last good content is not preserved" do
+ cache = build_cache(
+ uri: "http://example.org/queue-body",
+ html: "Queue-it Please wait while we redirect you",
+ body: "Queue-it Please wait while we redirect you",
+ scrape_date: Time.zone.now,
+ successful_refresh: nil,
+ http_response_code: 200,
+ signals: {
+ "network_status" => "ok",
+ "content_type" => "html"
+ },
+ hints: [],
+ final_url: "http://example.org/queue-body"
+ )
+
+ Distillator::CacheHealthMaterializer.call(cache)
+
+ assert_equal "queue_it", cache.primary_issue_key
+ assert_equal "system_queue", cache.primary_issue_error_code
+ assert_equal "anti_bot", cache.primary_issue_category
+ assert_includes cache.issue_keys, "queue_it"
+ end
+
+ test "failed redirect listing attempt is materialized as attempt failed instead of never fetched" do
+ cache = build_cache(
+ uri: "https://www.ovation.ca/event",
+ html: nil,
+ body: nil,
+ scrape_date: Time.zone.now,
+ successful_refresh: nil,
+ http_response_code: 200,
+ signals: {
+ "network_status" => "ok",
+ "content_type" => "html",
+ "renderer" => "legacy_phantomjs",
+ "renderer_unavailable" => true,
+ "renderer_fallback" => "direct_url",
+ "primary_issue_key" => "redirect_to_listing",
+ "primary_issue_severity" => "failed",
+ "content_success" => false
+ },
+ hints: %w[legacy_phantomjs phantomjs_unavailable redirect_to_listing],
+ final_url: "https://www.ovation.ca/Search/Title/",
+ redirect_chain: ["https://www.ovation.ca/event", "https://www.ovation.ca/Search/Title/"]
+ )
+
+ Distillator::CacheHealthMaterializer.call(cache)
+
+ assert_equal "attempt_failed", cache.health_status
+ assert_equal "redirect_to_listing", cache.primary_issue_key
+ end
+
+ test "policy aborted non empty html materializes as content rejected instead of empty body" do
+ cache = build_cache(
+ uri: "https://example.org/rejected",
+ html: nil,
+ body: nil,
+ scrape_date: Time.zone.now,
+ successful_refresh: nil,
+ http_response_code: 200,
+ signals: {
+ "network_status" => "ok",
+ "content_type" => "html",
+ "transport_success" => true,
+ "content_success" => false,
+ "policy_action" => "abort_update",
+ "content_rejected" => true,
+ "fetched_body_state" => "non_empty",
+ "fetched_body_bytes" => 96,
+ "stored_body_state" => "not_stored",
+ "storage_decision" => "abort_update",
+ "cache_body_empty_after_abort" => true,
+ "primary_issue_key" => "generic_error_text",
+ "primary_issue_label" => "Generic error text observed",
+ "primary_issue_severity" => "failed",
+ "primary_issue_match" => {
+ "source" => "body_text",
+ "pattern" => "Une erreur est survenue",
+ "snippet" => "Une erreur est survenue Retry later."
+ }
+ },
+ hints: ["generic_error_text"],
+ final_url: "https://example.org/rejected"
+ )
+
+ Distillator::CacheHealthMaterializer.call(cache)
+
+ assert_equal "content_rejected", cache.health_status
+ assert_equal "high", cache.health_severity
+ assert_equal "generic_error_text", cache.primary_issue_key
+ assert_equal "Generic error text observed", cache.primary_issue_label
+ refute_includes cache.health_reasons, "empty_body"
+ end
+
+ test "html cache stays healthy when stored body is empty but cached html is present" do
+ cache = build_cache(
+ uri: "https://example.org/html-cache-present",
+ html: "Cached HTML cached body",
+ body: "",
+ scrape_date: Time.zone.now,
+ successful_refresh: Time.zone.now,
+ http_response_code: 200,
+ signals: {
+ "network_status" => "ok",
+ "content_type" => "html",
+ "transport_success" => true,
+ "content_success" => true,
+ "stored_body_bytes" => 0,
+ "empty_body" => true
+ },
+ hints: ["empty_body"],
+ final_url: "https://example.org/html-cache-present"
+ )
+
+ Distillator::CacheHealthMaterializer.call(cache)
+
+ assert_equal "healthy", cache.health_status
+ assert_equal "ok", cache.health_severity
+ assert_equal cache.html.to_s.bytesize, cache.html_bytes
+ assert_equal 0, cache.body_bytes
+ refute_includes cache.health_reasons, "empty_body"
+ end
+
+ private
+
+ # Build an unsaved FetchCache with realistic defaults.
+ #
+ # The materializer mutates the object in memory, so these tests do not need to
+ # persist rows unless they are explicitly testing callbacks or database indexes.
+ def build_cache(
+ uri:,
+ html: nil,
+ body: nil,
+ scrape_date: nil,
+ successful_refresh: nil,
+ http_response_code: nil,
+ headers: {},
+ signals: {},
+ hints: [],
+ final_url: nil,
+ redirect_chain: []
+ )
+ Distillator::FetchCache.new(
+ uri_key: CGI.escape(uri),
+ normalized_url: uri,
+ html: html,
+ body: body,
+ scrape_date: scrape_date,
+ successful_refresh: successful_refresh,
+ http_response_code: http_response_code,
+ headers: headers,
+ signals: signals,
+ hints: hints,
+ final_url: final_url,
+ redirect_chain: redirect_chain
+ )
+ end
+end
diff --git a/test/services/distillator/cache_health_test.rb b/test/services/distillator/cache_health_test.rb
new file mode 100644
index 00000000..a197c464
--- /dev/null
+++ b/test/services/distillator/cache_health_test.rb
@@ -0,0 +1,131 @@
+require "test_helper"
+
+class Distillator::CacheHealthTest < ActiveSupport::TestCase
+ CacheStub = Struct.new(
+ :normalized_url,
+ :final_url,
+ :html,
+ :successful_refresh,
+ :scrape_date,
+ :http_response_code,
+ :signals,
+ :hints,
+ keyword_init: true
+ )
+
+ test "classifies blocked cache from network_status signal" do
+ result = Distillator::CacheHealth.call(build_cache(signals: { "network_status" => "blocked" }, hints: []))
+
+ assert_equal :blocked, result.status
+ assert_equal ["blocked"], result.reasons
+ end
+
+ test "classifies failed network cache" do
+ result = Distillator::CacheHealth.call(build_cache(signals: { "network_status" => "failed" }))
+
+ assert_equal :network_failed, result.status
+ assert_equal ["network_failed"], result.reasons
+ end
+
+ test "classifies never fetched cache" do
+ result = Distillator::CacheHealth.call(build_cache(successful_refresh: nil, scrape_date: nil, http_response_code: nil, html: nil))
+
+ assert_equal :never_fetched, result.status
+ assert_equal ["never_fetched"], result.reasons
+ end
+
+ test "classifies attempted but failed cache without successful refresh" do
+ result = Distillator::CacheHealth.call(
+ build_cache(
+ successful_refresh: nil,
+ scrape_date: 10.minutes.ago,
+ http_response_code: 200,
+ html: nil,
+ signals: {
+ "network_status" => "ok",
+ "content_type" => "html",
+ "primary_issue_key" => "redirect_to_listing",
+ "primary_issue_severity" => "failed",
+ "content_success" => false
+ },
+ hints: ["redirect_to_listing"]
+ )
+ )
+
+ assert_equal :attempt_failed, result.status
+ assert_equal ["redirect_to_listing"], result.reasons
+ end
+
+ test "classifies empty body cache" do
+ result = Distillator::CacheHealth.call(build_cache(hints: ["empty_body"], html: ""))
+
+ assert_equal :empty_body, result.status
+ assert_equal ["empty_body"], result.reasons
+ end
+
+ test "classifies preserved html after 404 failure" do
+ result = Distillator::CacheHealth.call(build_cache(http_response_code: 404, scrape_date: Time.current, successful_refresh: 1.day.ago))
+
+ assert_equal :preserved_after_failure, result.status
+ assert_equal ["non_2xx_preserved_html"], result.reasons
+ end
+
+ test "classifies preserved html after 500 failure" do
+ result = Distillator::CacheHealth.call(build_cache(http_response_code: 500, scrape_date: Time.current, successful_refresh: 1.day.ago))
+
+ assert_equal :preserved_after_failure, result.status
+ assert_equal ["non_2xx_preserved_html"], result.reasons
+ end
+
+ test "classifies redirect changed cache" do
+ result = Distillator::CacheHealth.call(build_cache(final_url: "https://other.example.org/final"))
+
+ assert_equal :redirect_changed, result.status
+ assert_equal ["redirect_changed"], result.reasons
+ end
+
+ test "classifies stale cache" do
+ result = Distillator::CacheHealth.call(build_cache(scrape_date: 8.days.ago))
+
+ assert_equal :stale, result.status
+ assert_equal ["stale"], result.reasons
+ end
+
+ test "classifies healthy cache" do
+ result = Distillator::CacheHealth.call(build_cache)
+
+ assert_equal :healthy, result.status
+ assert_equal ["successful_2xx_refresh"], result.reasons
+ end
+
+ test "classifies unknown cache" do
+ result = Distillator::CacheHealth.call(build_cache(http_response_code: nil, html: nil, successful_refresh: 1.day.ago, scrape_date: nil, signals: {}, hints: []))
+
+ assert_equal :unknown, result.status
+ assert_equal ["unknown"], result.reasons
+ end
+
+ private
+
+ def build_cache(
+ normalized_url: "http://example.org/page",
+ final_url: "http://example.org/page",
+ html: "cached",
+ successful_refresh: 1.day.ago,
+ scrape_date: 1.day.ago,
+ http_response_code: 200,
+ signals: { "network_status" => "ok", "content_type" => "html" },
+ hints: []
+ )
+ CacheStub.new(
+ normalized_url: normalized_url,
+ final_url: final_url,
+ html: html,
+ successful_refresh: successful_refresh,
+ scrape_date: scrape_date,
+ http_response_code: http_response_code,
+ signals: signals,
+ hints: hints
+ )
+ end
+end
diff --git a/test/services/distillator/cache_index_query_test.rb b/test/services/distillator/cache_index_query_test.rb
new file mode 100644
index 00000000..6807e876
--- /dev/null
+++ b/test/services/distillator/cache_index_query_test.rb
@@ -0,0 +1,283 @@
+require "test_helper"
+
+class Distillator::CacheIndexQueryTest < ActiveSupport::TestCase
+ setup do
+ Distillator::FetchCache.delete_all
+ end
+
+ test "uses sql-backed filtering sorting and pagination when possible" do
+ first = create_cache(uri: "http://example.org/a", http_response_code: 200, updated_at: 2.days.ago)
+ second = create_cache(uri: "http://example.org/b", http_response_code: 404, updated_at: 1.day.ago)
+
+ result = Distillator::CacheIndexQuery.call(
+ filters: { status_group: "4xx" },
+ sort: "updated_at",
+ direction: "desc",
+ page: 1,
+ per_page: 10
+ )
+
+ assert_kind_of ActiveRecord::Relation, result.records
+ assert_kind_of ActiveRecord::Relation, result.summary_scope
+ assert_equal [second.id], result.records.map(&:id)
+ assert_equal 1, result.total_count
+ assert_equal 1, result.total_pages
+ assert_not_equal first.id, result.records.first.id
+ end
+
+ test "filters by term through sql" do
+ matching = create_cache(uri: "http://example.org/needle", name: "Alpha")
+ create_cache(uri: "http://example.org/other", name: "Beta")
+
+ result = Distillator::CacheIndexQuery.call(
+ filters: { term: "needle" },
+ sort: "updated_at",
+ direction: "desc",
+ page: 1,
+ per_page: 10
+ )
+
+ assert_equal [matching.id], result.records.map(&:id)
+ end
+
+ test "filters by exact http response code and ignores blank invalid values" do
+ not_found = create_cache(uri: "http://example.org/not-found", http_response_code: 404)
+ ok = create_cache(uri: "http://example.org/success", http_response_code: 200)
+
+ exact = Distillator::CacheIndexQuery.call(
+ filters: { http_response_code: " 404 " },
+ sort: "updated_at",
+ direction: "desc",
+ page: 1,
+ per_page: 10
+ )
+ blank = Distillator::CacheIndexQuery.call(
+ filters: { http_response_code: "" },
+ sort: "updated_at",
+ direction: "desc",
+ page: 1,
+ per_page: 10
+ )
+ invalid = Distillator::CacheIndexQuery.call(
+ filters: { http_response_code: "abc" },
+ sort: "updated_at",
+ direction: "desc",
+ page: 1,
+ per_page: 10
+ )
+
+ assert_equal [not_found.id], exact.records.map(&:id)
+ assert_equal [ok.id, not_found.id].sort, blank.records.map(&:id).sort
+ assert_equal [ok.id, not_found.id].sort, invalid.records.map(&:id).sort
+ end
+
+ test "filters by has html and network status through sql" do
+ with_html = create_cache(uri: "http://example.org/with-html", html: "present", signals: { "network_status" => "failed" })
+ create_cache(uri: "http://example.org/without-html", html: nil, body: nil, signals: { "network_status" => "ok" })
+
+ html_result = Distillator::CacheIndexQuery.call(
+ filters: { has_html: "true" },
+ sort: "updated_at",
+ direction: "desc",
+ page: 1,
+ per_page: 10
+ )
+ network_result = Distillator::CacheIndexQuery.call(
+ filters: { network_status: "failed" },
+ sort: "updated_at",
+ direction: "desc",
+ page: 1,
+ per_page: 10
+ )
+
+ assert_equal [with_html.id], html_result.records.map(&:id)
+ assert_equal [with_html.id], network_result.records.map(&:id)
+ end
+
+ test "applies ruby-derived health filtering before pagination" do
+ create_cache(uri: "http://example.org/healthy", http_response_code: 200)
+ failed = create_cache(uri: "http://example.org/network", signals: { "network_status" => "failed", "content_type" => "html" })
+
+ result = Distillator::CacheIndexQuery.call(
+ filters: { health: "network_failed" },
+ sort: "updated_at",
+ direction: "desc",
+ page: 1,
+ per_page: 10
+ )
+
+ assert_equal [failed.id], result.records.map(&:id)
+ assert_kind_of ActiveRecord::Relation, result.summary_scope
+ end
+
+ test "applies ruby-derived hint filtering before pagination" do
+ matching = create_cache(uri: "http://example.org/empty", hints: ["empty_body"])
+ create_cache(uri: "http://example.org/other", hints: ["json_detected"])
+
+ result = Distillator::CacheIndexQuery.call(
+ filters: { hint: "empty_body" },
+ sort: "updated_at",
+ direction: "desc",
+ page: 1,
+ per_page: 10
+ )
+
+ assert_equal [matching.id], result.records.map(&:id)
+ end
+
+ test "supports computed byte sorting before pagination" do
+ small = create_cache(uri: "http://example.org/small", html: "x
", body: "x
")
+ large = create_cache(uri: "http://example.org/large", html: "#{'x' * 20}
", body: "#{'x' * 20}
")
+
+ result = Distillator::CacheIndexQuery.call(
+ filters: {},
+ sort: "html_bytes",
+ direction: "desc",
+ page: 1,
+ per_page: 10
+ )
+
+ assert_equal [large.id, small.id], result.records.map(&:id)
+ end
+
+ test "filters by hint through sql without loading every record" do
+ matching = create_cache(uri: "http://example.org/empty", hints: ["empty_body"])
+ create_cache(uri: "http://example.org/other", hints: ["json_detected"])
+
+ result = Distillator::CacheIndexQuery.call(
+ filters: { hint: "empty_body" },
+ sort: "updated_at",
+ direction: "desc",
+ page: 1,
+ per_page: 10
+ )
+
+ assert_equal [matching.id], result.records.map(&:id)
+ end
+
+ test "combines sql and ruby filtering before pagination" do
+ create_cache(uri: "http://example.org/one", http_response_code: 404, hints: ["json_detected"])
+ matching = create_cache(uri: "http://example.org/two", http_response_code: 404, hints: ["empty_body"])
+ create_cache(uri: "http://example.org/three", http_response_code: 200, hints: ["empty_body"])
+
+ result = Distillator::CacheIndexQuery.call(
+ filters: { status_group: "4xx", hint: "empty_body" },
+ sort: "updated_at",
+ direction: "desc",
+ page: 1,
+ per_page: 10
+ )
+
+ assert_equal [matching.id], result.records.map(&:id)
+ end
+
+ test "filters redirected false rows with explicit sql negation" do
+ direct = create_cache(uri: "http://example.org/direct", final_url: "http://example.org/direct", redirect_chain: [])
+ create_cache(
+ uri: "http://example.org/redirected",
+ final_url: "https://example.org/final",
+ redirect_chain: ["http://example.org/redirected", "https://example.org/final"]
+ )
+
+ result = Distillator::CacheIndexQuery.call(
+ filters: { redirected: "false" },
+ sort: "updated_at",
+ direction: "desc",
+ page: 1,
+ per_page: 10
+ )
+
+ assert_kind_of ActiveRecord::Relation, result.records
+ assert_equal [direct.id], result.records.map(&:id)
+ end
+
+ test "filters redirected true rows with explicit sql predicate" do
+ redirected = create_cache(
+ uri: "http://example.org/redirected",
+ final_url: "https://example.org/final",
+ redirect_chain: ["http://example.org/redirected", "https://example.org/final"]
+ )
+ create_cache(uri: "http://example.org/direct", final_url: "http://example.org/direct", redirect_chain: [])
+
+ result = Distillator::CacheIndexQuery.call(
+ filters: { redirected: "true" },
+ sort: "updated_at",
+ direction: "desc",
+ page: 1,
+ per_page: 10
+ )
+
+ assert_equal [redirected.id], result.records.map(&:id)
+ end
+
+ test "filters by status group nil and content type" do
+ no_code = create_cache(uri: "http://example.org/nil-code", http_response_code: nil, signals: { "content_type" => "json" })
+ create_cache(uri: "http://example.org/html", http_response_code: 200, signals: { "content_type" => "html" })
+
+ nil_status = Distillator::CacheIndexQuery.call(
+ filters: { status_group: "nil" },
+ sort: "updated_at",
+ direction: "desc",
+ page: 1,
+ per_page: 10
+ )
+ content_type = Distillator::CacheIndexQuery.call(
+ filters: { content_type: "json" },
+ sort: "updated_at",
+ direction: "desc",
+ page: 1,
+ per_page: 10
+ )
+
+ assert_equal [no_code.id], nil_status.records.map(&:id)
+ assert_equal [no_code.id], content_type.records.map(&:id)
+ end
+
+ test "filters by last attempt and last success time windows" do
+ never_attempted = create_cache(uri: "http://example.org/never-attempted", scrape_date: nil)
+ never_success = create_cache(uri: "http://example.org/never-success", successful_refresh: nil)
+ create_cache(uri: "http://example.org/attempted-and-successful")
+
+ last_attempt = Distillator::CacheIndexQuery.call(
+ filters: { last_attempt: "never" },
+ sort: "updated_at",
+ direction: "desc",
+ page: 1,
+ per_page: 10
+ )
+ last_success = Distillator::CacheIndexQuery.call(
+ filters: { last_success: "never" },
+ sort: "updated_at",
+ direction: "desc",
+ page: 1,
+ per_page: 10
+ )
+
+ assert_equal [never_attempted.id], last_attempt.records.map(&:id)
+ assert_equal [never_success.id], last_success.records.map(&:id)
+ end
+
+ private
+
+ def create_cache(uri:, html: "cached", body: html, name: "Cached", signals: { "network_status" => "ok", "content_type" => "html" }, hints: [], final_url: nil, redirect_chain: [], http_response_code: 200, scrape_date: Time.zone.now, successful_refresh: Time.zone.now, updated_at: Time.zone.now)
+ cache = Distillator::FetchCache.new(
+ uri_key: CGI.escape(uri),
+ normalized_url: uri,
+ html: html,
+ body: body,
+ name: name,
+ scrape_date: scrape_date,
+ successful_refresh: successful_refresh,
+ http_response_code: http_response_code,
+ headers: {},
+ signals: signals,
+ hints: hints,
+ final_url: final_url,
+ redirect_chain: redirect_chain
+ )
+ cache.created_at = updated_at
+ cache.updated_at = updated_at
+ cache.save!
+ cache
+ end
+end
diff --git a/test/services/distillator/cache_link_resolver_test.rb b/test/services/distillator/cache_link_resolver_test.rb
new file mode 100644
index 00000000..06b35394
--- /dev/null
+++ b/test/services/distillator/cache_link_resolver_test.rb
@@ -0,0 +1,182 @@
+require "test_helper"
+
+class Distillator::CacheLinkResolverTest < ActiveSupport::TestCase
+ setup do
+ @old_fetch_mode = ENV["DISTILLATOR_FETCH_MODE"]
+ @old_replay_fetch = ENV["REPLAY_FETCH"]
+ Distillator::WringerEndpoint.stubs(:current).returns(
+ Distillator::WringerEndpoint::Result.new(
+ compatibility_base_url: "http://compat.example",
+ legacy_lookup_base_url: "http://wringer.example",
+ state: :remote_configured,
+ status_label: "Current Wringer: Remote configured",
+ status_detail: "http://wringer.example"
+ )
+ )
+ end
+
+ teardown do
+ ENV["DISTILLATOR_FETCH_MODE"] = @old_fetch_mode
+ ENV["REPLAY_FETCH"] = @old_replay_fetch
+ end
+
+ test "uses safe legacy links without website context even when env requests Distillator" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "legacy"
+ assert_equal :legacy, Distillator::CacheLinkResolver.call(url: "http://example.org/page")[:mode]
+
+ ENV["DISTILLATOR_FETCH_MODE"] = "internal"
+ internal = Distillator::CacheLinkResolver.call(url: "http://example.org/page")
+ assert_equal :legacy, internal[:mode]
+ assert_equal :legacy, internal[:rollout_mode]
+ assert_equal :wringer, internal[:active_backend]
+ assert_equal :default, internal[:source]
+ assert_equal internal[:legacy_cache_url], internal[:active_cache_url]
+
+ ENV["DISTILLATOR_FETCH_MODE"] = "shadow"
+ shadow = Distillator::CacheLinkResolver.call(url: "http://example.org/page")
+ assert_equal :legacy, shadow[:mode]
+ assert_equal :legacy, shadow[:rollout_mode]
+ assert_equal :wringer, shadow[:active_backend]
+ assert_equal :default, shadow[:source]
+ assert_equal shadow[:legacy_cache_url], shadow[:active_cache_url]
+
+ ENV["REPLAY_FETCH"] = "fixture"
+ replay = Distillator::CacheLinkResolver.call(url: "http://example.org/page")
+ assert_equal :replay, replay[:mode]
+ assert_equal "Diagnostic mode only. Do not treat this as a production state.", replay[:warning]
+
+ invalid = Distillator::CacheLinkResolver.call(url: "http://[invalid")
+ assert_equal true, invalid[:disabled]
+ end
+
+ test "explicit active alias compatibility and shadow modes remain available for diagnostics" do
+ internal = Distillator::CacheLinkResolver.call(url: "http://example.org/page", mode: :internal)
+ assert_equal "Open active cache", internal[:label]
+ assert_equal :active, internal[:mode]
+ assert_equal :active, internal[:rollout_mode]
+ assert_equal :condenser, internal[:active_backend]
+ assert_equal :explicit, internal[:source]
+
+ shadow = Distillator::CacheLinkResolver.call(url: "http://example.org/page", mode: :shadow)
+ assert_equal "Open active cache", shadow[:label]
+ assert_equal :shadow, shadow[:mode]
+ assert_equal :shadow, shadow[:rollout_mode]
+ assert_equal :wringer, shadow[:active_backend]
+ assert_equal :explicit, shadow[:source]
+ assert_equal 2, shadow[:secondary_links].length
+ assert_match "/condenser/cache/compare?uri=", shadow[:secondary_links].first[:url]
+ end
+
+ test "preserves include fragment behavior in generated cache links" do
+ payload = Distillator::CacheLinkResolver.call(url: "http://example.org/page#frag", include_fragment: true, mode: :internal)
+
+ assert_includes payload[:distillator_cache_url], "term=http%3A%2F%2Fexample.org%2Fpage%23frag"
+ end
+
+ test "json_post keeps condenser as active cache path in active alias mode" do
+ payload = Distillator::CacheLinkResolver.call(
+ url: "http://example.org/api",
+ mode: :internal,
+ scrape_options: { json_post: true }
+ )
+
+ assert_equal "Open active cache", payload[:label]
+ assert_equal payload[:distillator_cache_url], payload[:active_cache_url]
+ assert_nil payload[:warning]
+ end
+
+ test "website rollout mode wins over global default when website context is present" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "legacy"
+ website = websites(:one)
+ website.update!(distillator_mode: "active")
+
+ payload = Distillator::CacheLinkResolver.call(url: "http://example.org/page", website: website)
+
+ assert_equal :active, payload[:mode]
+ assert_equal :active, payload[:rollout_mode]
+ assert_equal :condenser, payload[:active_backend]
+ assert_equal :website, payload[:source]
+ assert_equal payload[:distillator_cache_url], payload[:active_cache_url]
+ assert_equal "Inspect legacy Wringer", payload[:secondary_links].first[:label]
+ end
+
+ test "shadow website keeps wringer active and exposes comparison link" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "legacy"
+ website = websites(:one)
+ website.update!(distillator_mode: "shadow")
+
+ payload = Distillator::CacheLinkResolver.call(url: "http://example.org/page", website: website)
+
+ assert_equal :shadow, payload[:mode]
+ assert_equal :shadow, payload[:rollout_mode]
+ assert_equal :wringer, payload[:active_backend]
+ assert_equal :website, payload[:source]
+ assert_equal payload[:legacy_cache_url], payload[:active_cache_url]
+ assert_equal "Compare Condenser vs Wringer", payload[:secondary_links].first[:label]
+ assert_match "/condenser/cache/compare?uri=", payload[:secondary_links].first[:url]
+ assert_equal "Wringer serves production while Condenser is checked in the background.", payload[:warning]
+ end
+
+ test "compare link preserves single escaped uri parameter for query urls" do
+ payload = Distillator::CacheLinkResolver.call(url: "https://example.org/evenements/caf%C3%A9?lang=fr&category=arts%20vivants", mode: :shadow)
+
+ assert_includes payload[:compare_url], "uri=https%3A%2F%2Fexample.org%2Fevenements%2Fcaf%25C3%25A9%3Flang%3Dfr%26category%3Darts%2520vivants"
+ refute_includes payload[:compare_url], "uri=https%253A"
+ end
+
+ test "missing wringer endpoint keeps condenser links but does not build malformed legacy links" do
+ Distillator::WringerEndpoint.stubs(:current).returns(
+ Distillator::WringerEndpoint::Result.new(
+ compatibility_base_url: nil,
+ legacy_lookup_base_url: nil,
+ compatibility_source: nil,
+ state: :missing_config,
+ status_label: "Current Wringer: Missing staging config",
+ status_detail: "comparisons disabled"
+ )
+ )
+
+ payload = Distillator::CacheLinkResolver.call(url: "http://example.org/page", mode: :shadow)
+
+ assert_nil payload[:legacy_cache_url]
+ assert_nil payload[:active_cache_url]
+ assert_equal [{ label: "Open Condenser cache", url: payload[:distillator_cache_url] }], payload[:secondary_links]
+ assert_equal "Wringer endpoint missing; comparison unavailable.", payload[:warning]
+ end
+
+ test "legacy mode with missing wringer endpoint still exposes condenser cache only" do
+ Distillator::WringerEndpoint.stubs(:current).returns(
+ Distillator::WringerEndpoint::Result.new(
+ compatibility_base_url: nil,
+ legacy_lookup_base_url: nil,
+ compatibility_source: nil,
+ state: :missing_config,
+ status_label: "Current Wringer: Missing staging config",
+ status_detail: "comparisons disabled"
+ )
+ )
+
+ payload = Distillator::CacheLinkResolver.call(url: "http://example.org/page", mode: :legacy)
+
+ assert_nil payload[:legacy_cache_url]
+ assert_nil payload[:active_cache_url]
+ assert_equal [{ label: "Open Condenser cache", url: payload[:distillator_cache_url] }], payload[:secondary_links]
+ assert_equal "Wringer endpoint missing; comparison unavailable.", payload[:warning]
+ end
+
+ test "legacy mode keeps wringer active and exposes distillator cache as secondary link" do
+ payload = Distillator::CacheLinkResolver.call(url: "http://example.org/page", mode: :legacy)
+
+ assert_equal payload[:legacy_cache_url], payload[:active_cache_url]
+ assert_equal "Open Condenser cache", payload[:secondary_links].first[:label]
+ assert_equal payload[:distillator_cache_url], payload[:secondary_links].first[:url]
+ assert_equal "Wringer serves production.", payload[:warning]
+ end
+
+ test "active mode does not duplicate condenser cache as a secondary link" do
+ payload = Distillator::CacheLinkResolver.call(url: "http://example.org/page", mode: :internal)
+
+ assert_equal payload[:distillator_cache_url], payload[:active_cache_url]
+ refute_includes payload[:secondary_links].map { |link| link[:label] }, "Open Condenser cache"
+ end
+end
diff --git a/test/services/distillator/cache_refresh_preview_test.rb b/test/services/distillator/cache_refresh_preview_test.rb
new file mode 100644
index 00000000..8e546d14
--- /dev/null
+++ b/test/services/distillator/cache_refresh_preview_test.rb
@@ -0,0 +1,119 @@
+require "test_helper"
+
+class Distillator::CacheRefreshPreviewTest < ActiveSupport::TestCase
+ setup do
+ Distillator::FetchCache.delete_all
+ end
+
+ test "missing cache returns would_refresh true with missing_cache" do
+ Distillator::FetchGuard.stubs(:check_url).returns(Distillator::FetchGuard::Result.new(allowed: true))
+
+ preview = Distillator::CacheRefreshPreview.call(uri: "http://example.org/missing")
+
+ assert_equal true, preview[:would_refresh]
+ assert_equal :missing_cache, preview[:reason]
+ assert_equal false, preview[:cache_exists]
+ end
+
+ test "nil scrape_date returns missing_scrape_date" do
+ cache = create_cache(scrape_date: nil)
+ Distillator::FetchGuard.stubs(:check_url).returns(Distillator::FetchGuard::Result.new(allowed: true))
+
+ preview = Distillator::CacheRefreshPreview.call(uri: cache.normalized_url)
+
+ assert_equal true, preview[:would_refresh]
+ assert_equal :missing_scrape_date, preview[:reason]
+ assert_equal cache.id, preview[:cache_id]
+ end
+
+ test "force_scrape true returns force_scrape" do
+ cache = create_cache(scrape_date: 1.minute.ago)
+ Distillator::FetchGuard.stubs(:check_url).returns(Distillator::FetchGuard::Result.new(allowed: true))
+
+ preview = Distillator::CacheRefreshPreview.call(uri: cache.normalized_url, force_scrape: true)
+
+ assert_equal true, preview[:would_refresh]
+ assert_equal :force_scrape, preview[:reason]
+ end
+
+ test "stale cache returns stale_by_force_scrape_every_hrs" do
+ cache = create_cache(scrape_date: 2.days.ago)
+ Distillator::FetchGuard.stubs(:check_url).returns(Distillator::FetchGuard::Result.new(allowed: true))
+
+ preview = Distillator::CacheRefreshPreview.call(uri: cache.normalized_url, force_scrape_every_hrs: "24")
+
+ assert_equal true, preview[:would_refresh]
+ assert_equal :stale_by_force_scrape_every_hrs, preview[:reason]
+ end
+
+ test "force_scrape_every_hrs zero returns refresh required" do
+ cache = create_cache(scrape_date: 1.minute.ago)
+ Distillator::FetchGuard.stubs(:check_url).returns(Distillator::FetchGuard::Result.new(allowed: true))
+
+ preview = Distillator::CacheRefreshPreview.call(uri: cache.normalized_url, force_scrape_every_hrs: "0")
+
+ assert_equal true, preview[:would_refresh]
+ assert_equal :stale_by_force_scrape_every_hrs, preview[:reason]
+ end
+
+ test "fresh cache returns fresh_cache" do
+ cache = create_cache(scrape_date: 1.minute.ago)
+ Distillator::FetchGuard.stubs(:check_url).returns(Distillator::FetchGuard::Result.new(allowed: true))
+
+ preview = Distillator::CacheRefreshPreview.call(uri: cache.normalized_url, force_scrape_every_hrs: "200000")
+
+ assert_equal false, preview[:would_refresh]
+ assert_equal :fresh_cache, preview[:reason]
+ end
+
+ test "invalid uri returns invalid_uri" do
+ preview = Distillator::CacheRefreshPreview.call(uri: "http://[invalid")
+
+ assert_equal false, preview[:would_refresh]
+ assert_equal :invalid_uri, preview[:reason]
+ assert_nil preview[:uri_key]
+ end
+
+ test "blank uri returns invalid_uri" do
+ preview = Distillator::CacheRefreshPreview.call(uri: "")
+
+ assert_equal false, preview[:would_refresh]
+ assert_equal :invalid_uri, preview[:reason]
+ assert_nil preview[:uri_key]
+ end
+
+ test "blocked url returns blocked_url" do
+ Distillator::FetchGuard.stubs(:check_url).returns(
+ Distillator::FetchGuard::Result.new(
+ allowed: false,
+ error: "Blocked URL host after DNS resolution error for timeout.example: Timeout::Error",
+ reason: :dns_resolution_error
+ )
+ )
+
+ preview = Distillator::CacheRefreshPreview.call(uri: "http://127.0.0.1")
+
+ assert_equal false, preview[:would_refresh]
+ assert_equal :blocked_url, preview[:reason]
+ assert_equal :dns_resolution_error, preview[:guard_reason]
+ assert_includes preview[:guard_error], "Timeout::Error"
+ end
+
+ private
+
+ def create_cache(scrape_date:)
+ Distillator::FetchCache.create!(
+ uri_key: CGI.escape("http://example.org/cached"),
+ normalized_url: "http://example.org/cached",
+ html: "cached",
+ body: "cached",
+ scrape_date: scrape_date,
+ successful_refresh: Time.zone.now,
+ http_response_code: 200,
+ headers: {},
+ signals: {},
+ hints: [],
+ redirect_chain: []
+ )
+ end
+end
diff --git a/test/services/distillator/cache_summary_test.rb b/test/services/distillator/cache_summary_test.rb
new file mode 100644
index 00000000..e1e35ab2
--- /dev/null
+++ b/test/services/distillator/cache_summary_test.rb
@@ -0,0 +1,93 @@
+require "test_helper"
+
+class Distillator::CacheSummaryTest < ActiveSupport::TestCase
+ setup do
+ Distillator::FetchCache.delete_all
+ end
+
+ test "counts cards correctly from a relation-backed scope" do
+ create_cache(uri: "http://example.org/healthy", http_response_code: 200)
+ create_cache(
+ uri: "http://example.org/preserved",
+ http_response_code: 404,
+ scrape_date: Time.zone.now,
+ successful_refresh: 2.days.ago,
+ signals: {
+ "network_status" => "ok",
+ "content_type" => "html",
+ "content_success" => false,
+ "primary_issue_key" => "http_4xx",
+ "primary_issue_severity" => "failed",
+ "last_good_preserved_failure" => true
+ },
+ hints: ["last_good_preserved_failure"]
+ )
+ create_cache(uri: "http://example.org/network", signals: { "network_status" => "failed", "content_type" => "html" })
+ create_cache(uri: "http://example.org/blocked", signals: { "error_type" => "DistillatorFetchBlocked", "content_type" => "html" }, hints: ["blocked"])
+ create_cache(uri: "http://example.org/missing-html", html: nil, body: nil, successful_refresh: nil)
+ create_cache(uri: "http://example.org/redirect", final_url: "http://example.org/final", redirect_chain: ["http://example.org/redirect", "http://example.org/final"])
+ create_cache(uri: "http://example.org/json", signals: { "network_status" => "ok", "content_type" => "json" })
+ create_cache(
+ uri: "http://example.org/server",
+ http_response_code: 500,
+ scrape_date: Time.zone.now,
+ successful_refresh: 1.day.ago,
+ signals: {
+ "network_status" => "ok",
+ "content_type" => "html",
+ "content_success" => false,
+ "primary_issue_key" => "http_5xx",
+ "primary_issue_severity" => "failed",
+ "last_good_preserved_failure" => true
+ },
+ hints: ["last_good_preserved_failure"]
+ )
+ create_cache(
+ uri: "http://example.org/empty",
+ html: "",
+ body: "",
+ signals: { "network_status" => "ok", "content_type" => "html", "empty_body" => true },
+ hints: ["empty_body"],
+ http_response_code: 200
+ )
+ create_cache(uri: "http://example.org/stale", scrape_date: 8.days.ago, successful_refresh: 8.days.ago)
+
+ scope = Distillator::FetchCache.all
+ scope.expects(:to_a).never
+ cards = Distillator::CacheSummary.call(scope: scope)
+
+ counts = cards.index_by { |card| card[:key] }.transform_values { |card| card[:count] }
+ assert_equal 10, counts[:total]
+ assert_equal 1, counts[:network_failed]
+ assert_equal 1, counts[:blocked]
+ assert_equal 1, counts[:missing_html]
+ assert_equal 1, counts[:empty_body]
+ assert_equal 1, counts[:status_4xx]
+ assert_equal 1, counts[:status_5xx]
+ assert_equal 1, counts[:redirected]
+ assert_equal 1, counts[:json_detected]
+ assert_equal 3, counts[:healthy]
+ assert_equal 2, counts[:preserved_after_failure]
+ assert_equal 1, counts[:stale]
+ end
+
+ private
+
+ def create_cache(uri:, html: "cached", body: html, name: "Cached", signals: { "network_status" => "ok", "content_type" => "html" }, hints: [], final_url: nil, redirect_chain: [], http_response_code: 200, scrape_date: Time.zone.now, successful_refresh: Time.zone.now)
+ Distillator::FetchCache.create!(
+ uri_key: CGI.escape(uri),
+ normalized_url: uri,
+ html: html,
+ body: body,
+ name: name,
+ scrape_date: scrape_date,
+ successful_refresh: successful_refresh,
+ http_response_code: http_response_code,
+ headers: {},
+ signals: signals,
+ hints: hints,
+ final_url: final_url,
+ redirect_chain: redirect_chain
+ )
+ end
+end
diff --git a/test/services/distillator/cache_website_matcher_test.rb b/test/services/distillator/cache_website_matcher_test.rb
new file mode 100644
index 00000000..325dbdda
--- /dev/null
+++ b/test/services/distillator/cache_website_matcher_test.rb
@@ -0,0 +1,63 @@
+require "test_helper"
+
+class Distillator::CacheWebsiteMatcherTest < ActiveSupport::TestCase
+ test "matches exact webpage url" do
+ website = build_website_with_page("https://example.org/event")
+ cache = Distillator::FetchCache.new(uri_key: CGI.escape("https://example.org/event"), normalized_url: "https://example.org/event")
+
+ result = Distillator::CacheWebsiteMatcher.call(cache: cache, websites: [website])
+
+ assert_equal website, result.website
+ assert_equal "webpage_url", result.matched_by
+ end
+
+ test "matches trailing slash difference" do
+ website = build_website_with_page("https://example.org/event/")
+ cache = Distillator::FetchCache.new(uri_key: CGI.escape("https://example.org/event"), normalized_url: "https://example.org/event")
+
+ result = Distillator::CacheWebsiteMatcher.call(cache: cache, websites: [website])
+
+ assert_equal website, result.website
+ assert_equal "canonical_without_fragment", result.matched_by
+ end
+
+ test "matches final url redirect with warning" do
+ website = build_website_with_page("https://example.org/event")
+ cache = Distillator::FetchCache.new(
+ uri_key: CGI.escape("https://example.org/original"),
+ normalized_url: "https://example.org/original",
+ final_url: "https://example.org/event"
+ )
+
+ result = Distillator::CacheWebsiteMatcher.call(cache: cache, websites: [website])
+
+ assert_equal website, result.website
+ assert_equal "final_url", result.matched_by
+ assert_equal "final_url_redirect_match", result.warning
+ end
+
+ test "does not silently guess ambiguous matches" do
+ one = build_website_with_page("https://example.org/shared")
+ two = build_website_with_page("https://example.org/shared")
+ cache = Distillator::FetchCache.new(uri_key: CGI.escape("https://example.org/shared"), normalized_url: "https://example.org/shared")
+
+ result = Distillator::CacheWebsiteMatcher.call(cache: cache, websites: [one, two])
+
+ assert_nil result.website
+ assert_equal "ambiguous_cache_match", result.warning
+ end
+
+ private
+
+ def build_website_with_page(url)
+ website = Website.create!(
+ name: "Matcher #{SecureRandom.hex(4)}",
+ seedurl: "matcher-#{SecureRandom.hex(4)}",
+ graph_name: "https://example.org/#{SecureRandom.hex(4)}",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+ website.webpages.create!(url: url, language: "en", rdf_uri: "rdf:#{SecureRandom.hex(4)}", rdfs_class: rdfs_classes(:one))
+ website
+ end
+end
diff --git a/test/services/distillator/capability_map_test.rb b/test/services/distillator/capability_map_test.rb
new file mode 100644
index 00000000..893d5dcc
--- /dev/null
+++ b/test/services/distillator/capability_map_test.rb
@@ -0,0 +1,72 @@
+require "test_helper"
+
+class Distillator::CapabilityMapTest < ActiveSupport::TestCase
+ test "capability map exposes the expected sections and ladder" do
+ result = Distillator::CapabilityMap.call
+
+ assert_equal(
+ [
+ "Fetch & Cache",
+ "Statement Extraction",
+ "Sources / DSL",
+ "JSON-LD Export",
+ "Transition / Rollout",
+ "Diagnostics / Reports",
+ "Operations / Safety",
+ "Legacy Wringer Compatibility"
+ ],
+ result.sections.map(&:title)
+ )
+
+ assert_equal(
+ [
+ "Fetch parity",
+ "Statement parity",
+ "Export parity",
+ "Review activation",
+ "Active rollout",
+ "Rollback path"
+ ],
+ result.activation_readiness_ladder.map(&:name)
+ )
+ end
+
+ test "capability map includes key operator features with evidence and links" do
+ result = Distillator::CapabilityMap.call
+ features = result.sections.flat_map(&:features).index_by(&:name)
+
+ assert_equal "/condenser/cache/compare", features.fetch("Cache compare").operator_path
+ assert_includes features.fetch("Cache compare").badges, "Read-only"
+
+ assert_equal "/statements/compare_extracted", features.fetch("Extracted statement parity").operator_path
+ assert_includes features.fetch("Extracted statement parity").badges, "Transition-only"
+
+ assert_equal "/distillator/shadow_report", features.fetch("Export graph diff").operator_path
+ assert_includes features.fetch("Export graph diff").badges, "Production-critical"
+
+ assert_equal "/distillator/shadow_report", features.fetch("Transition report").operator_path
+ assert_includes features.fetch("Transition report").badges, "Read-only"
+
+ assert_equal "/distillator/shadow_report", features.fetch("Rollback path").operator_path
+ assert_includes features.fetch("Rollback path").badges, "Writes data"
+
+ assert_equal "/websites/wring", features.fetch("Legacy Wringer compatibility endpoint").operator_path
+ assert_includes features.fetch("Legacy Wringer compatibility endpoint").badges, "Legacy-backed"
+ end
+
+ test "every feature row exposes the required fields" do
+ Distillator::CapabilityMap.call.sections.each do |section|
+ section.features.each do |feature|
+ assert feature.name.present?
+ assert feature.purpose.present?
+ assert feature.implementation_status.present?
+ assert feature.wringer_dependency_status.present?
+ assert feature.transition_relevance.present?
+ assert feature.evidence_source.present?
+ assert feature.operator_label.present?
+ assert feature.operator_path.present?
+ assert feature.badges.present?
+ end
+ end
+ end
+end
diff --git a/test/services/distillator/cohorts/lavitrine_pipeline_test.rb b/test/services/distillator/cohorts/lavitrine_pipeline_test.rb
new file mode 100644
index 00000000..1be8ca3f
--- /dev/null
+++ b/test/services/distillator/cohorts/lavitrine_pipeline_test.rb
@@ -0,0 +1,11 @@
+require "test_helper"
+
+class Distillator::Cohorts::LavitrinePipelineTest < ActiveSupport::TestCase
+ test "exposes the versioned la vitrine feed names" do
+ assert_equal "https://raw.githubusercontent.com/artsdata-stewards/artsdata-actions/main/queries/lavitrine_pipeline.sparql",
+ Distillator::Cohorts::LavitrinePipeline.query_url
+ assert_includes Distillator::Cohorts::LavitrinePipeline.feed_names, "hector-charland-com"
+ assert_includes Distillator::Cohorts::LavitrinePipeline.feed_names, "derived-grandtheatre-qc-ca"
+ assert_includes Distillator::Cohorts::LavitrinePipeline.feed_names, "culture-mauricie"
+ end
+end
diff --git a/test/services/distillator/cohorts/matcher_test.rb b/test/services/distillator/cohorts/matcher_test.rb
new file mode 100644
index 00000000..18a6393d
--- /dev/null
+++ b/test/services/distillator/cohorts/matcher_test.rb
@@ -0,0 +1,48 @@
+require "test_helper"
+
+class Distillator::Cohorts::MatcherTest < ActiveSupport::TestCase
+ test "matches a website by seedurl" do
+ website = build_website(seedurl: "hector-charland-com", name: "Hector Charland")
+
+ assert_equal true, Distillator::Cohorts::Matcher.match?(website, "lavitrine_pipeline")
+ end
+
+ test "matches hyphenated cohort names against normalized website names" do
+ website = build_website(seedurl: "other-seed", name: "Tout Culture")
+
+ assert_equal true, Distillator::Cohorts::Matcher.match?(website, "lavitrine_pipeline")
+ end
+
+ test "matches derived names and domain like code values" do
+ website = build_website(
+ seedurl: "other-seed",
+ name: "Other name",
+ graph_name: "https://derived-grandtheatre.qc.ca/"
+ )
+
+ assert_equal true, Distillator::Cohorts::Matcher.match?(website, "lavitrine_pipeline")
+ end
+
+ test "normalizes protocols domains and trailing slashes" do
+ assert_equal "placedesarts-com", Distillator::Cohorts::Matcher.normalize("https://www.placedesarts.com/")
+ assert_equal "culture-mauricie", Distillator::Cohorts::Matcher.normalize("Culture Mauricie")
+ end
+
+ test "returns false for non cohort websites" do
+ website = build_website(seedurl: "outside-feed", name: "Outside Feed")
+
+ assert_equal false, Distillator::Cohorts::Matcher.match?(website, "lavitrine_pipeline")
+ end
+
+ private
+
+ def build_website(seedurl:, name:, graph_name: "https://example.org/feed")
+ Website.new(
+ name: name,
+ seedurl: seedurl,
+ graph_name: graph_name,
+ default_language: "en",
+ distillator_mode: "legacy"
+ )
+ end
+end
diff --git a/test/services/distillator/cohorts/registry_test.rb b/test/services/distillator/cohorts/registry_test.rb
new file mode 100644
index 00000000..43d2225c
--- /dev/null
+++ b/test/services/distillator/cohorts/registry_test.rb
@@ -0,0 +1,28 @@
+require "test_helper"
+
+class Distillator::Cohorts::RegistryTest < ActiveSupport::TestCase
+ setup do
+ Distillator::Cohorts::Registry.reload!
+ end
+
+ test "loads cohorts from yaml" do
+ cohort = Distillator::Cohorts::Registry.fetch("lavitrine_pipeline")
+
+ assert_equal "La Vitrine pipeline", cohort[:label]
+ assert_includes cohort[:match_fields], "seedurl"
+ assert_includes cohort[:match_fields], "name"
+ assert_includes cohort[:match_fields], "code"
+ assert_includes cohort[:feed_names], "hector-charland-com"
+ end
+
+ test "missing yaml fails safely with empty cohort list" do
+ Distillator::Cohorts::Registry.stubs(:path).returns(Pathname.new("/tmp/distillator-cohorts-missing.yml"))
+ Distillator::Cohorts::Registry.reload!
+
+ assert_equal({}, Distillator::Cohorts::Registry.all)
+ assert_nil Distillator::Cohorts::Registry.fetch("lavitrine_pipeline")
+ ensure
+ Distillator::Cohorts::Registry.unstub(:path)
+ Distillator::Cohorts::Registry.reload!
+ end
+end
diff --git a/test/services/distillator/export_invariance_test.rb b/test/services/distillator/export_invariance_test.rb
new file mode 100644
index 00000000..565eabd5
--- /dev/null
+++ b/test/services/distillator/export_invariance_test.rb
@@ -0,0 +1,56 @@
+require "test_helper"
+
+class Distillator::ExportInvarianceTest < ActiveSupport::TestCase
+ FIXTURE_SEEDURL = "distillator-fixture-pack".freeze
+ EXPECTED_JSONLD = Rails.root.join("test/fixtures/files/distillator_export/expected_distillator_fixture_pack.jsonld")
+ EXPECTED_NQ = Rails.root.join("test/fixtures/files/distillator_export/expected_distillator_fixture_pack.nq")
+
+ test "fixture pack export matches golden jsonld and nq graphs" do
+ actual_jsonld = ExportArtsdataService.call(seedurl: FIXTURE_SEEDURL)
+ actual_graph = jsonld_to_graph(actual_jsonld)
+ expected_graph = jsonld_to_graph(File.read(EXPECTED_JSONLD))
+ diff = Distillator::GraphDiff.call(expected_graph: expected_graph, actual_graph: actual_graph)
+
+ assert_equal 0, diff.added_count, diff_message(diff)
+ assert_equal 0, diff.removed_count, diff_message(diff)
+ assert_equal normalize_nquads(File.read(EXPECTED_NQ)), normalize_nquads(actual_graph.dump(:nquads))
+ end
+
+ test "fixture pack export matches the current production equivalent export path after normalization" do
+ actual = Distillator::ExportNormalizer.normalize(ExportArtsdataService.call(seedurl: FIXTURE_SEEDURL))
+ expected = Distillator::ExportNormalizer.normalize(ExportArtsdataService.production_equivalent(seedurl: FIXTURE_SEEDURL))
+
+ assert_equal expected, actual
+ end
+
+ test "fixture pack export includes a complete event with linked place and offer semantics" do
+ graph = jsonld_to_graph(ExportArtsdataService.call(seedurl: FIXTURE_SEEDURL))
+ event = RDF::URI("http://kg.footlight.io/resource/distillator-full-event")
+
+ assert graph.query([event, RDF.type, RDF::Vocab::SCHEMA.Event]).to_a.any?, "expected schema:Event triple"
+ assert graph.query([event, RDF::Vocab::SCHEMA.location, nil]).to_a.any?, "expected linked schema:location"
+ assert graph.query([event, RDF::Vocab::SCHEMA.offers, nil]).to_a.any?, "expected schema:offers"
+ assert graph.query([event, RDF::Vocab::SCHEMA.eventStatus, RDF::URI("http://schema.org/EventScheduled")]).to_a.any?, "expected reconciled eventStatus URI"
+ assert graph.query([event, RDF::Vocab::SCHEMA.eventAttendanceMode, RDF::URI("http://schema.org/OnlineEventAttendanceMode")]).to_a.any?, "expected reconciled attendance mode URI"
+ end
+
+ private
+
+ def jsonld_to_graph(jsonld)
+ RDF::Graph.new << JSON::LD::API.toRdf(JSON.parse(jsonld))
+ end
+
+ def normalize_nquads(nquads)
+ nquads.to_s.lines.map(&:strip).reject(&:blank?).sort.join("\n")
+ end
+
+ def diff_message(diff)
+ [
+ "added_count=#{diff.added_count} removed_count=#{diff.removed_count} same_count=#{diff.same_count}",
+ ("added:\n" + diff.added.first(10).join("\n") if diff.added.any?),
+ ("removed:\n" + diff.removed.first(10).join("\n") if diff.removed.any?),
+ ("changed_literal_values:\n" + diff.changed_literal_values.first(10).map(&:inspect).join("\n") if diff.changed_literal_values.any?),
+ ("changed_uri_objects:\n" + diff.changed_uri_objects.first(10).map(&:inspect).join("\n") if diff.changed_uri_objects.any?)
+ ].compact.join("\n\n")
+ end
+end
diff --git a/test/services/distillator/export_normalizer_test.rb b/test/services/distillator/export_normalizer_test.rb
new file mode 100644
index 00000000..52fdfe21
--- /dev/null
+++ b/test/services/distillator/export_normalizer_test.rb
@@ -0,0 +1,50 @@
+require "test_helper"
+
+class Distillator::ExportNormalizerTest < ActiveSupport::TestCase
+ test "normalizes JSON deterministically" do
+ input = {
+ "updated_at" => "2026-04-27T12:00:00Z",
+ "b" => [" two spaces ", "one"],
+ "a" => { "z" => 1, "created_at" => "2026-04-27T12:00:00Z" }
+ }
+
+ assert_equal <<~JSON.strip, Distillator::ExportNormalizer.normalize(input)
+ {
+ "a": {
+ "z": 1
+ },
+ "b": [
+ "one",
+ "two spaces"
+ ]
+ }
+ JSON
+ end
+
+ test "sorts JSON-LD graph nodes and preserves semantic date fields" do
+ input = [
+ { "@id" => "event:b", "startDate" => "2026-05-02T20:00:00Z" },
+ { "@id" => "event:a", "startDate" => "2026-05-01T20:00:00Z" }
+ ]
+
+ normalized = Distillator::ExportNormalizer.normalize(input)
+
+ assert_operator normalized.index("event:a"), :<, normalized.index("event:b")
+ assert_includes normalized, "2026-05-01T20:00:00Z"
+ assert_includes normalized, "2026-05-02T20:00:00Z"
+ end
+
+ test "normalizes RDF-ish line output" do
+ input = <<~TEXT
+ "Beta" .
+
+ "2026-04-27T12:00:00Z" .
+ "Alpha" .
+ TEXT
+
+ assert_equal <<~TEXT.strip, Distillator::ExportNormalizer.normalize(input)
+ "Alpha" .
+ "Beta" .
+ TEXT
+ end
+end
diff --git a/test/services/distillator/fetch_cache_store_test.rb b/test/services/distillator/fetch_cache_store_test.rb
new file mode 100644
index 00000000..03e09e40
--- /dev/null
+++ b/test/services/distillator/fetch_cache_store_test.rb
@@ -0,0 +1,1748 @@
+require "test_helper"
+
+class Distillator::FetchCacheStoreTest < ActiveSupport::TestCase
+ class CapturingLogger
+ attr_reader :infos
+
+ def initialize
+ @infos = []
+ end
+
+ def info(payload)
+ @infos << payload
+ end
+ end
+
+ setup do
+ Distillator::FetchCache.delete_all
+ @old_rendered_fallback = ENV["DISTILLATOR_ALLOW_RENDERED_DIRECT_FALLBACK"]
+ end
+
+ teardown do
+ ENV["DISTILLATOR_ALLOW_RENDERED_DIRECT_FALLBACK"] = @old_rendered_fallback
+ end
+
+ test "cache miss creates a distillator fetch cache and returns metadata-rich payload" do
+ Distillator::FetchService.expects(:fetch_result).with do |kwargs|
+ assert_equal "http://example.org/page", kwargs[:url]
+ assert_equal false, kwargs[:render_js]
+ assert_equal :internal, kwargs[:mode]
+ true
+ end.returns(success_result(body: "Hello Body", final_url: "https://example.org/final", fetch_path: "native"))
+
+ result = Distillator::FetchCacheStore.fetch(uri: "example.org/page", mode: :internal)
+ cache = Distillator::FetchCache.find_by(uri_key: result.uri_key)
+
+ assert cache
+ assert_equal :ok, result.status
+ assert_equal "Hello Body", result.body
+ assert_equal "Hello Body", result.html
+ assert_equal 200, result.http_response_code
+ assert_equal false, result.cache_hit
+ assert_equal true, result.cache_write
+ assert_equal "missing_cache", result.cache_reason
+ assert_equal "native", result.fetch_path
+ assert_equal "http://example.org/page", result.normalized_url
+ assert_equal ["https://example.org/final"], result.redirect_chain
+ assert_equal "https://example.org/final", cache.final_url
+ assert_equal "Hello Body", cache.html
+ assert_equal "healthy", cache.health_status
+ assert_equal "html", cache.content_type
+ assert_equal "ok", cache.network_status
+ assert_equal "native", cache.signals["fetch_backend"]
+ assert_equal "GET", cache.signals["request_method"]
+ assert_equal false, cache.signals["use_phantomjs"]
+ end
+
+ test "website rollout context is forwarded to fetch service" do
+ website = websites(:one)
+ website.update!(distillator_mode: "active")
+
+ Distillator::FetchService.expects(:fetch_result).with do |kwargs|
+ assert_nil kwargs[:mode]
+ assert_equal website, kwargs[:website]
+ assert_equal website.id, kwargs[:website_id]
+ true
+ end.returns(success_result(body: "Hello Body", fetch_path: "native"))
+
+ result = Distillator::FetchCacheStore.fetch(
+ uri: "https://example.org/context",
+ website: website,
+ website_id: website.id
+ )
+
+ assert_equal :ok, result.status
+ assert_equal "native", result.fetch_path
+ end
+
+ test "fetch cache store persists the exact wringer-compatible uri key" do
+ Distillator::FetchService.expects(:fetch_result).returns(success_result(body: "Hello ", fetch_path: "native"))
+
+ result = Distillator::FetchCacheStore.fetch(uri: "culturecreates.com/people/")
+
+ assert_equal "http%3A%2F%2Fculturecreates.com%2Fpeople%2F", result.uri_key
+ assert_equal ["http%3A%2F%2Fculturecreates.com%2Fpeople%2F"], Distillator::FetchCache.pluck(:uri_key)
+ end
+
+ test "cache fetch forwards structured log context to fetch service and logs cache events" do
+ logger = CapturingLogger.new
+ Distillator::FetchService.expects(:fetch_result).with do |kwargs|
+ assert_equal({ statement_id: 7, source_id: 8, webpage_id: 9, website_id: 10 }, kwargs[:log_context])
+ true
+ end.returns(success_result(body: "Hello Body", fetch_path: "legacy"))
+
+ Distillator::FetchCacheStore.fetch(
+ uri: "https://example.org/context",
+ mode: :internal,
+ logger: logger,
+ log_context: { statement_id: 7, source_id: 8, webpage_id: 9, website_id: 10 }
+ )
+
+ assert_equal "cache.miss", logger.infos.first[:event]
+ assert_equal 7, logger.infos.last[:statement_id]
+ assert_equal 8, logger.infos.last[:source_id]
+ assert_equal 9, logger.infos.last[:webpage_id]
+ assert_equal 10, logger.infos.first[:website_id]
+ assert_equal 10, logger.infos.last[:website_id]
+ assert_equal "legacy", logger.infos.last[:fetch_path]
+ end
+
+ test "fresh cache hit does not fetch" do
+ cache = create_cache(scrape_date: 10.minutes.ago, successful_refresh: 10.minutes.ago)
+ Distillator::FetchService.expects(:fetch_result).never
+ logger = CapturingLogger.new
+
+ result = Distillator::FetchCacheStore.fetch(
+ uri: cache.normalized_url,
+ logger: logger,
+ log_context: { statement_id: 1, source_id: 2, webpage_id: 3, website_id: 4 }
+ )
+
+ assert_equal :ok, result.status
+ assert_equal true, result.cache_hit
+ assert_equal false, result.cache_write
+ assert_equal "fresh_cache", result.cache_reason
+ assert_equal "cache", result.fetch_path
+ assert_equal cache.html, result.html
+ assert_equal cache.body, result.body
+ assert_equal "cache.hit", logger.infos.first[:event]
+ assert_equal 4, logger.infos.first[:website_id]
+ end
+
+ test "force_scrape true refreshes even when cache exists" do
+ cache = create_cache(scrape_date: 10.minutes.ago, successful_refresh: 10.minutes.ago, html: "old")
+ Distillator::FetchService.expects(:fetch_result).returns(success_result(body: "New "))
+ logger = CapturingLogger.new
+
+ result = Distillator::FetchCacheStore.fetch(uri: cache.normalized_url, force_scrape: true, logger: logger)
+
+ assert_equal false, result.cache_hit
+ assert_equal true, result.cache_write
+ assert_equal "force_scrape", result.cache_reason
+ assert_equal "New ", result.html
+ assert_equal "cache.refresh", logger.infos.last[:event]
+ end
+
+ test "force_scrape string false does not refresh an existing cache" do
+ cache = create_cache(scrape_date: 10.minutes.ago, successful_refresh: 10.minutes.ago)
+ Distillator::FetchService.expects(:fetch_result).never
+
+ result = Distillator::FetchCacheStore.fetch(uri: cache.normalized_url, force_scrape: "false")
+
+ assert_equal true, result.cache_hit
+ assert_equal false, result.cache_write
+ assert_equal "fresh_cache", result.cache_reason
+ assert_equal cache.html, result.html
+ end
+
+ test "missing scrape_date refreshes and reports missing_scrape_date" do
+ cache = create_cache(scrape_date: nil, successful_refresh: 10.minutes.ago)
+ Distillator::FetchService.expects(:fetch_result).returns(success_result(body: "missing scrape date"))
+
+ result = Distillator::FetchCacheStore.fetch(uri: cache.normalized_url)
+
+ assert_equal false, result.cache_hit
+ assert_equal true, result.cache_write
+ assert_equal "missing_scrape_date", result.cache_reason
+ assert_equal "missing scrape date", result.html
+ end
+
+ test "force_scrape_every_hrs zero refreshes" do
+ cache = create_cache(scrape_date: 5.minutes.ago, successful_refresh: 5.minutes.ago)
+ Distillator::FetchService.expects(:fetch_result).returns(success_result(body: "zero refresh"))
+
+ result = Distillator::FetchCacheStore.fetch(uri: cache.normalized_url, force_scrape_every_hrs: "0")
+ cache.reload
+
+ assert_equal "stale_by_force_scrape_every_hrs", result.cache_reason
+ assert_equal "zero refresh", result.html
+ assert_equal "zero refresh", cache.html
+ assert_equal "zero refresh", cache.body
+ assert_equal result.uri_key, cache.uri_key
+ end
+
+ test "old cache refreshes when older than threshold" do
+ cache = create_cache(scrape_date: 3.hours.ago, successful_refresh: 3.hours.ago)
+ Distillator::FetchService.expects(:fetch_result).returns(success_result(body: "stale refresh"))
+
+ result = Distillator::FetchCacheStore.fetch(uri: cache.normalized_url, force_scrape_every_hrs: "1")
+
+ assert_equal "stale_by_force_scrape_every_hrs", result.cache_reason
+ assert_equal "stale refresh", result.html
+ end
+
+ test "successful 2xx response updates successful_refresh" do
+ cache = create_cache(scrape_date: 2.days.ago, successful_refresh: 2.days.ago)
+ previous_success = cache.successful_refresh
+ Distillator::FetchService.expects(:fetch_result).returns(success_result(body: "Fresh ", http_code: 201))
+
+ result = Distillator::FetchCacheStore.fetch(uri: cache.normalized_url, force_scrape: true)
+
+ assert_equal 201, result.http_response_code
+ assert_operator result.successful_refresh, :>, previous_success
+ assert_equal "Fresh ", result.html
+ end
+
+ test "rendered fetch fallback to direct listing redirect is not a successful refresh" do
+ ENV["DISTILLATOR_ALLOW_RENDERED_DIRECT_FALLBACK"] = "true"
+ ovation_url = "https://www.ovation.ca/00001Q/fr/Event/?seriesId=series&venueId=venue"
+ listing_url = "https://www.ovation.ca/Search/Title/"
+ listing_html = "Recherche par titre Recherche par titre"
+
+ Distillator::FetchService.expects(:fetch_result).with do |kwargs|
+ assert_equal true, kwargs[:render_js]
+ true
+ end.returns(
+ success_result(
+ body: listing_html,
+ headers: { content_type: "text/html" },
+ signals: {
+ network_status: "ok",
+ content_type: "html",
+ renderer: "legacy_phantomjs",
+ renderer_unavailable: true,
+ renderer_fallback: "direct_url",
+ primary_issue_key: "redirect_to_listing",
+ primary_issue_severity: "failed",
+ primary_issue_category: "redirect",
+ primary_issue_label: "Redirect to listing",
+ blocking_issue_key: "redirect_to_listing",
+ content_success: false,
+ transport_success: true
+ },
+ hints: %w[legacy_phantomjs phantomjs_unavailable redirect_to_listing],
+ http_code: 200,
+ final_url: listing_url,
+ redirect_chain: [ovation_url, listing_url],
+ fetch_path: "native"
+ )
+ )
+
+ result = Distillator::FetchCacheStore.fetch(uri: ovation_url, render_js: true, use_phantomjs: true, mode: :internal, force_scrape: true)
+ cache = Distillator::FetchCache.find_by(uri_key: result.uri_key)
+
+ assert_equal true, result.transport_success?
+ assert_equal false, result.content_success?
+ assert_equal true, result.blocking_issue?
+ assert_equal "redirect_to_listing", result.blocking_issue_key
+ assert_equal "redirect_to_listing", cache.primary_issue_key
+ assert_equal true, cache.signals["renderer_unavailable"]
+ assert_equal "direct_url", cache.signals["renderer_fallback"]
+ assert_nil cache.successful_refresh
+ assert_equal "attempt_failed", cache.health_status
+ end
+
+ test "non 2xx response updates scrape_date and http_response_code but preserves last good html" do
+ old_scrape_date = 2.days.ago
+ old_successful_refresh = 2.hours.ago
+ cache = create_cache(
+ scrape_date: old_scrape_date,
+ successful_refresh: old_successful_refresh,
+ html: "last good",
+ body: "last good"
+ )
+ Distillator::FetchService.expects(:fetch_result).returns(
+ abort_result(
+ http_code: 500,
+ raw_body: "Server Error ",
+ headers: { content_type: "text/html" },
+ signals: { network_status: "ok" },
+ hints: [],
+ fetch_path: "native"
+ )
+ )
+
+ result = Distillator::FetchCacheStore.fetch(uri: cache.normalized_url, force_scrape: true)
+
+ assert_equal :ok, result.status
+ assert_equal 500, result.http_response_code
+ assert_equal "last good", result.html
+ assert_equal "last good", result.body
+ assert_equal old_successful_refresh.to_i, result.successful_refresh.to_i
+ assert_operator result.scrape_date, :>, old_scrape_date
+ assert_equal "Server Error", result.name
+ end
+
+ test "404 response preserves last good html body and successful_refresh while updating failure metadata" do
+ old_scrape_date = 2.days.ago
+ old_successful_refresh = 3.hours.ago
+ cache = create_cache(
+ scrape_date: old_scrape_date,
+ successful_refresh: old_successful_refresh,
+ html: "last good",
+ body: "last good",
+ signals: { "network_status" => "ok", "content_type" => "html" },
+ hints: []
+ )
+
+ Distillator::FetchService.expects(:fetch_result).returns(
+ abort_result(
+ http_code: 404,
+ raw_body: "Not Found ",
+ headers: { content_type: "text/html" },
+ signals: { network_status: "ok", content_type: "html" },
+ hints: ["not_found"],
+ fetch_path: "native"
+ )
+ )
+
+ result = Distillator::FetchCacheStore.fetch(uri: cache.normalized_url, force_scrape: true)
+ cache.reload
+
+ assert_equal :ok, result.status
+ assert_equal 404, result.http_response_code
+ assert_equal "last good", result.html
+ assert_equal "last good", result.body
+ assert_equal old_successful_refresh.to_i, result.successful_refresh.to_i
+ assert_operator result.scrape_date, :>, old_scrape_date
+ assert_includes result.hints, "not_found"
+ assert_includes result.hints, "last_good_preserved_failure"
+ assert_equal "ok", result.signals["network_status"]
+ assert_equal "Not Found", cache.name
+ end
+
+ test "timeout abort preserves last good html body and successful_refresh while updating abort metadata" do
+ old_scrape_date = 2.days.ago
+ old_successful_refresh = 4.hours.ago
+ cache = create_cache(
+ scrape_date: old_scrape_date,
+ successful_refresh: old_successful_refresh,
+ html: "last good",
+ body: "last good",
+ name: "Last Good"
+ )
+
+ Distillator::FetchService.expects(:fetch_result).returns(
+ {
+ status: :abort,
+ body: ["abort_update", { error: "execution expired", error_type: "TimeoutError" }],
+ raw_body: nil,
+ headers: {},
+ final_url: cache.normalized_url,
+ redirect_chain: [],
+ wringer: {
+ error_type: "TimeoutError",
+ signals: { network_status: "failed", timeout: true },
+ hints: ["timeout"]
+ },
+ http_code: nil,
+ duration_ms: 8.0,
+ fetch_path: "native"
+ }
+ )
+
+ result = Distillator::FetchCacheStore.fetch(uri: cache.normalized_url, force_scrape: true)
+ cache.reload
+
+ assert_equal :ok, result.status
+ assert_nil result.http_response_code
+ assert_equal "last good", result.html
+ assert_equal "last good", result.body
+ assert_equal old_successful_refresh.to_i, result.successful_refresh.to_i
+ assert_operator result.scrape_date, :>, old_scrape_date
+ assert_equal "failed", result.signals["network_status"]
+ assert_includes result.hints, "timeout"
+ assert_includes result.hints, "last_good_preserved_failure"
+ assert_equal "Last Good", cache.name
+ end
+
+ test "blocked abort preserves last good html body and successful_refresh while updating blocked metadata" do
+ old_scrape_date = 2.days.ago
+ old_successful_refresh = 5.hours.ago
+ cache = create_cache(
+ uri: "http://example.org/blocked",
+ scrape_date: old_scrape_date,
+ successful_refresh: old_successful_refresh,
+ html: "last good",
+ body: "last good",
+ name: "Last Good"
+ )
+
+ Distillator::FetchService.expects(:fetch_result).returns(
+ {
+ status: :abort,
+ body: ["abort_update", { error: "Blocked localhost host: 127.0.0.1", error_type: "DistillatorFetchBlocked" }],
+ raw_body: nil,
+ headers: {},
+ final_url: cache.normalized_url,
+ redirect_chain: [],
+ wringer: {
+ error_type: "DistillatorFetchBlocked",
+ signals: { network_status: "blocked", native_ineligible_reason: "blocked_url" },
+ hints: ["blocked_url", "blocked"]
+ },
+ http_code: nil,
+ duration_ms: 1.0,
+ fetch_path: "blocked"
+ }
+ )
+
+ result = Distillator::FetchCacheStore.fetch(uri: cache.normalized_url, force_scrape: true)
+ cache.reload
+
+ assert_equal :ok, result.status
+ assert_nil result.http_response_code
+ assert_equal "last good", result.html
+ assert_equal "last good", result.body
+ assert_equal old_successful_refresh.to_i, result.successful_refresh.to_i
+ assert_operator result.scrape_date, :>, old_scrape_date
+ assert_equal "blocked", result.signals["network_status"]
+ assert_equal "blocked_url", result.signals["native_ineligible_reason"]
+ assert_includes result.hints, "blocked_url"
+ assert_includes result.hints, "blocked"
+ assert_includes result.hints, "last_good_preserved_failure"
+ assert_equal "Last Good", cache.name
+ end
+
+ test "policy abort with http 200 body preserves last good content and marks failed content semantics" do
+ old_scrape_date = 2.days.ago
+ old_successful_refresh = 6.hours.ago
+ cache = create_cache(
+ uri: "https://www.ovation.ca/event",
+ scrape_date: old_scrape_date,
+ successful_refresh: old_successful_refresh,
+ html: "last good",
+ body: "last good",
+ name: "Last Good"
+ )
+
+ Distillator::FetchService.expects(:fetch_result).returns(
+ {
+ status: :ok,
+ body: "Recherche par titre ",
+ raw_body: "Recherche par titre ",
+ headers: { content_type: "text/html" },
+ final_url: "https://www.ovation.ca/Search/Title/",
+ redirect_chain: [cache.normalized_url, "https://www.ovation.ca/Search/Title/"],
+ wringer: {
+ policy_action: "abort_update",
+ retry: false,
+ cache: false,
+ signals: {
+ network_status: "ok",
+ content_type: "html",
+ primary_issue_key: "redirect_to_listing",
+ primary_issue_severity: "failed"
+ },
+ hints: ["redirect_to_listing"]
+ },
+ http_code: 200,
+ duration_ms: 3.0,
+ fetch_path: "native"
+ }
+ )
+
+ result = Distillator::FetchCacheStore.fetch(uri: cache.normalized_url, force_scrape: true)
+ cache.reload
+
+ assert_equal :ok, result.status
+ assert_equal true, result.transport_success?
+ assert_equal false, result.content_success?
+ assert_equal "last good", result.html
+ assert_equal "last good", result.body
+ assert_equal old_successful_refresh.to_i, result.successful_refresh.to_i
+ assert_operator result.scrape_date, :>, old_scrape_date
+ assert_includes result.hints, "last_good_preserved_failure"
+ assert_equal true, result.signals["last_good_preserved_failure"]
+ assert_equal "abort_update", result.signals["policy_action"]
+ assert_equal false, result.signals["cache"]
+ assert_equal old_successful_refresh.to_i, cache.successful_refresh.to_i
+ end
+
+ test "rendered payload includes cache metadata for legacy refreshes too" do
+ Distillator::FetchService.expects(:fetch_result).with do |kwargs|
+ assert_equal true, kwargs[:render_js]
+ assert_equal true, kwargs[:scrape_options][:json_post]
+ true
+ end.returns(
+ success_result(
+ body: '{"ok":true}',
+ headers: { content_type: "application/json" },
+ signals: { network_status: "ok", content_type: "json", json_detected: true },
+ hints: ["json_detected"],
+ http_code: 200,
+ fetch_path: "legacy"
+ )
+ )
+
+ result = Distillator::FetchCacheStore.fetch(
+ uri: "http://example.org/api",
+ render_js: true,
+ json_post: true,
+ mode: :legacy
+ )
+
+ assert_equal "legacy", result.fetch_path
+ assert_equal false, result.cache_hit
+ assert_equal true, result.cache_write
+ assert_equal '{"ok":true}', result.body
+ assert_equal "json", result.signals["content_type"]
+ assert_equal ["json_detected"], result.hints
+ end
+
+ test "json_post in internal mode persists native fetch metadata into distillator cache" do
+ Distillator::FetchService.expects(:fetch_result).with do |kwargs|
+ assert_equal :internal, kwargs[:mode]
+ assert_equal true, kwargs[:scrape_options][:json_post]
+ true
+ end.returns(
+ success_result(
+ body: '{"ok":true}',
+ headers: { content_type: "application/json" },
+ signals: { network_status: "ok", content_type: "json", json_detected: true },
+ hints: ["json_detected"],
+ http_code: 200,
+ final_url: "https://example.org/api/final",
+ redirect_chain: ["https://example.org/api", "https://example.org/api/final"],
+ fetch_path: "native"
+ )
+ )
+
+ result = Distillator::FetchCacheStore.fetch(
+ uri: "https://example.org/api",
+ json_post: true,
+ mode: :internal,
+ force_scrape: true
+ )
+ cache = Distillator::FetchCache.find_by(uri_key: result.uri_key)
+
+ assert_equal "native", result.fetch_path
+ assert_equal 200, result.http_response_code
+ assert_equal "https://example.org/api/final", result.final_url
+ assert_equal ["https://example.org/api", "https://example.org/api/final"], result.redirect_chain
+ assert_equal "json", result.signals["content_type"]
+ assert_equal "POST", result.signals["request_method"]
+ assert_equal "native", result.signals["fetch_backend"]
+ assert_includes result.hints, "json_detected"
+ assert_equal '{"ok":true}', cache.body
+ assert_equal "json", cache.signals["content_type"]
+ end
+
+ test "json_post string false does not opt into post mode" do
+ Distillator::FetchService.expects(:fetch_result).with do |kwargs|
+ assert_equal false, kwargs[:scrape_options][:json_post]
+ true
+ end.returns(success_result(body: "get mode", fetch_path: "native"))
+
+ result = Distillator::FetchCacheStore.fetch(
+ uri: "https://example.org/api",
+ json_post: "false",
+ mode: :internal,
+ force_scrape: true
+ )
+
+ assert_equal "get mode", result.html
+ end
+
+ test "json_post 404 preserves previous html body and successful_refresh" do
+ old_successful_refresh = 2.hours.ago
+ cache = create_cache(
+ uri: "https://example.org/api",
+ html: "last good",
+ body: "last good",
+ scrape_date: 2.days.ago,
+ successful_refresh: old_successful_refresh
+ )
+
+ Distillator::FetchService.expects(:fetch_result).returns(
+ abort_result(
+ http_code: 404,
+ raw_body: '{"error":"missing"}',
+ headers: { content_type: "application/json" },
+ signals: { network_status: "ok", content_type: "json", json_detected: true },
+ hints: ["json_detected"],
+ fetch_path: "native"
+ )
+ )
+
+ result = Distillator::FetchCacheStore.fetch(uri: cache.normalized_url, json_post: true, force_scrape: true)
+
+ assert_equal 404, result.http_response_code
+ assert_equal "last good", result.html
+ assert_equal "last good", result.body
+ assert_equal old_successful_refresh.to_i, result.successful_refresh.to_i
+ assert_equal "json", result.signals["content_type"]
+ end
+
+ test "json_post 500 preserves previous html body while updating status metadata" do
+ old_successful_refresh = 2.hours.ago
+ cache = create_cache(
+ uri: "https://example.org/api",
+ html: "last good json post",
+ body: "last good json post",
+ scrape_date: 2.days.ago,
+ successful_refresh: old_successful_refresh
+ )
+
+ Distillator::FetchService.expects(:fetch_result).returns(
+ abort_result(
+ http_code: 500,
+ raw_body: '{"error":"server"}',
+ headers: { content_type: "application/json" },
+ signals: { network_status: "ok", content_type: "json", json_detected: true, request_method: "POST" },
+ hints: ["json_detected"],
+ fetch_path: "native"
+ )
+ )
+
+ result = Distillator::FetchCacheStore.fetch(uri: cache.normalized_url, json_post: true, force_scrape: true)
+
+ assert_equal 500, result.http_response_code
+ assert_equal "last good json post", result.html
+ assert_equal "last good json post", result.body
+ assert_equal old_successful_refresh.to_i, result.successful_refresh.to_i
+ assert_equal "POST", result.signals["request_method"]
+ assert_equal "json", result.signals["content_type"]
+ assert_includes result.hints, "json_detected"
+ end
+
+ test "queue it issue preserves last good html while materializing issue metadata" do
+ old_successful_refresh = 2.hours.ago
+ cache = create_cache(
+ uri: "https://example.org/events",
+ html: "last good event",
+ body: "last good event",
+ scrape_date: 2.days.ago,
+ successful_refresh: old_successful_refresh
+ )
+
+ Distillator::FetchService.expects(:fetch_result).returns(
+ success_result(
+ body: "Queue-it Please wait while we redirect you",
+ headers: { content_type: "text/html" },
+ signals: {
+ network_status: "ok",
+ content_type: "html",
+ primary_issue_key: "queue_it",
+ primary_issue_error_code: "system_queue",
+ primary_issue_severity: "warning",
+ primary_issue_category: "anti_bot",
+ primary_issue_label: "Queue-it waiting room",
+ primary_issue_delete: false
+ },
+ hints: ["system_queue", "queue_it", "waiting_room"],
+ http_code: 200,
+ fetch_path: "native"
+ ).merge(
+ wringer: {
+ signals: {
+ network_status: "ok",
+ content_type: "html",
+ primary_issue_key: "queue_it",
+ primary_issue_error_code: "system_queue",
+ primary_issue_severity: "warning",
+ primary_issue_category: "anti_bot",
+ primary_issue_label: "Queue-it waiting room",
+ primary_issue_delete: false
+ },
+ hints: ["system_queue", "queue_it", "waiting_room"],
+ policy_action: "abort_update"
+ }
+ )
+ )
+
+ result = Distillator::FetchCacheStore.fetch(uri: cache.normalized_url, force_scrape: true)
+ cache.reload
+
+ assert_equal "last good event", result.html
+ assert_equal old_successful_refresh.to_i, result.successful_refresh.to_i
+ assert_equal "queue_it", cache.primary_issue_key
+ assert_equal "system_queue", cache.primary_issue_error_code
+ assert_equal "Queue-it waiting room", cache.primary_issue_label
+ assert_equal "warning", cache.primary_issue_severity
+ assert_equal "anti_bot", cache.primary_issue_category
+ assert_includes cache.issue_keys, "queue_it"
+ assert_includes cache.issue_keys, "last_good_preserved_failure"
+ assert_includes cache.issue_hints, "waiting_room"
+ assert_equal false, cache.delete_candidate
+ end
+
+ test "render_js in internal mode persists rendered phantomjs metadata into distillator cache" do
+ Distillator::FetchService.expects(:fetch_result).with do |kwargs|
+ assert_equal true, kwargs[:render_js]
+ assert_equal true, kwargs[:scrape_options][:use_phantomjs]
+ assert_equal true, kwargs[:scrape_options][:iframe]
+ true
+ end.returns(
+ success_result(
+ body: "rendered",
+ headers: { content_type: "text/html" },
+ signals: { network_status: "ok", content_type: "html", renderer: "legacy_phantomjs" },
+ hints: ["legacy_phantomjs"],
+ http_code: 200,
+ final_url: "https://example.org/rendered",
+ redirect_chain: ["https://example.org/events", "https://example.org/rendered"],
+ fetch_path: "native"
+ )
+ )
+
+ result = Distillator::FetchCacheStore.fetch(
+ uri: "https://example.org/eventsiframe",
+ render_js: true,
+ mode: :internal,
+ force_scrape: true
+ )
+ cache = Distillator::FetchCache.find_by(uri_key: result.uri_key)
+
+ assert_equal "native", result.fetch_path
+ assert_equal "legacy_phantomjs", result.signals["renderer"]
+ assert_equal "phantomjs", result.signals["fetch_backend"]
+ assert_equal true, result.signals["use_phantomjs"]
+ assert_equal true, result.signals["phantomjs_iframe_extraction"]
+ assert_includes result.hints, "legacy_phantomjs"
+ assert_equal "https://example.org/rendered", result.final_url
+ assert_equal "rendered", cache.html
+ end
+
+ test "iframe url forces phantomjs even without explicit render_js or use_phantomjs" do
+ Distillator::FetchService.expects(:fetch_result).with do |kwargs|
+ assert_equal true, kwargs[:render_js]
+ assert_equal true, kwargs[:scrape_options][:use_phantomjs]
+ assert_equal true, kwargs[:scrape_options][:render_js]
+ assert_equal true, kwargs[:scrape_options][:iframe]
+ true
+ end.returns(
+ success_result(
+ body: "iframe child",
+ headers: { content_type: "text/html" },
+ signals: {
+ network_status: "ok",
+ content_type: "html",
+ renderer: "legacy_phantomjs",
+ fetch_backend: "phantomjs",
+ request_method: "GET",
+ use_phantomjs: true,
+ phantomjs_iframe_extraction: true
+ },
+ hints: ["legacy_phantomjs"],
+ fetch_path: "native"
+ )
+ )
+
+ result = Distillator::FetchCacheStore.fetch(
+ uri: "https://example.org/eventsiframe",
+ mode: :internal,
+ force_scrape: true
+ )
+
+ assert_equal "iframe child", result.html
+ assert_equal "phantomjs", result.signals["fetch_backend"]
+ assert_equal true, result.signals["use_phantomjs"]
+ assert_equal true, result.signals["phantomjs_iframe_extraction"]
+ end
+
+ test "phantomjs missing child frame failure preserves last good html and records hint" do
+ cache = create_cache(
+ uri: "https://example.org/eventsiframe",
+ html: "last good child frame",
+ body: "last good child frame",
+ scrape_date: 2.days.ago,
+ successful_refresh: 2.hours.ago,
+ signals: { "network_status" => "ok", "content_type" => "html", "renderer" => "legacy_phantomjs" },
+ hints: ["legacy_phantomjs"]
+ )
+
+ Distillator::FetchService.expects(:fetch_result).returns(
+ {
+ status: :abort,
+ body: ["abort_update", { error_type: "PhantomjsIframeExtractionError" }],
+ raw_body: nil,
+ headers: { content_type: "application/json" },
+ final_url: "https://example.org/eventsiframe",
+ redirect_chain: [],
+ wringer: {
+ signals: {
+ network_status: "ok",
+ content_type: "json",
+ renderer: "legacy_phantomjs",
+ fetch_backend: "phantomjs",
+ request_method: "GET",
+ use_phantomjs: true,
+ phantomjs_iframe_extraction: false
+ },
+ hints: ["legacy_phantomjs", "phantomjs_iframe_missing_child_content"]
+ },
+ http_code: nil,
+ duration_ms: 8.0,
+ fetch_path: "native"
+ }
+ )
+
+ result = Distillator::FetchCacheStore.fetch(uri: cache.normalized_url, force_scrape: true)
+
+ assert_equal "last good child frame", result.html
+ assert_equal false, result.signals["phantomjs_iframe_extraction"]
+ assert_includes result.hints, "phantomjs_iframe_missing_child_content"
+ end
+
+ test "use_phantomjs string false does not opt into rendered fetch" do
+ Distillator::FetchService.expects(:fetch_result).with do |kwargs|
+ assert_equal false, kwargs[:render_js]
+ assert_equal false, kwargs[:scrape_options][:use_phantomjs]
+ true
+ end.returns(success_result(body: "direct", fetch_path: "native"))
+
+ result = Distillator::FetchCacheStore.fetch(
+ uri: "https://example.org/direct",
+ use_phantomjs: "false",
+ mode: :internal,
+ force_scrape: true
+ )
+
+ assert_equal "direct", result.html
+ assert_equal "native", result.fetch_path
+ end
+
+ test "rendered non 2xx preserves previous html while updating renderer metadata" do
+ cache = create_cache(
+ uri: "https://example.org/eventsiframe",
+ html: "last good rendered",
+ body: "last good rendered",
+ scrape_date: 2.days.ago,
+ successful_refresh: 2.hours.ago
+ )
+
+ Distillator::FetchService.expects(:fetch_result).returns(
+ abort_result(
+ http_code: 500,
+ raw_body: '{"pageResponses":[]}',
+ headers: { content_type: "application/json" },
+ signals: { network_status: "ok", content_type: "json", renderer: "legacy_phantomjs" },
+ hints: ["legacy_phantomjs"],
+ fetch_path: "native"
+ )
+ )
+
+ result = Distillator::FetchCacheStore.fetch(uri: cache.normalized_url, render_js: true, force_scrape: true)
+
+ assert_equal 500, result.http_response_code
+ assert_equal "last good rendered", result.html
+ assert_equal "legacy_phantomjs", result.signals["renderer"]
+ assert_equal "phantomjs", result.signals["fetch_backend"]
+ assert_includes result.hints, "legacy_phantomjs"
+ assert_includes result.hints, "last_good_preserved_failure"
+ end
+
+ test "phantomjs iframe extraction failure preserves last good html and records hint" do
+ cache = create_cache(
+ uri: "https://example.org/eventsiframe",
+ html: "last good iframe",
+ body: "last good iframe",
+ scrape_date: 2.days.ago,
+ successful_refresh: 2.hours.ago,
+ signals: { "network_status" => "ok", "content_type" => "html", "renderer" => "legacy_phantomjs" },
+ hints: ["legacy_phantomjs"]
+ )
+
+ Distillator::FetchService.expects(:fetch_result).returns(
+ {
+ status: :abort,
+ body: ["abort_update", { error_type: "PhantomjsIframeExtractionError" }],
+ raw_body: nil,
+ headers: { content_type: "application/json" },
+ final_url: "https://example.org/eventsiframe",
+ redirect_chain: [],
+ wringer: {
+ signals: {
+ network_status: "ok",
+ content_type: "json",
+ renderer: "legacy_phantomjs",
+ fetch_backend: "phantomjs",
+ request_method: "GET",
+ use_phantomjs: true,
+ phantomjs_iframe_extraction: false
+ },
+ hints: ["legacy_phantomjs", "phantomjs_iframe_malformed_json"]
+ },
+ http_code: nil,
+ duration_ms: 8.0,
+ fetch_path: "native"
+ }
+ )
+
+ result = Distillator::FetchCacheStore.fetch(uri: cache.normalized_url, render_js: true, force_scrape: true)
+
+ assert_equal "last good iframe", result.html
+ assert_equal false, result.signals["phantomjs_iframe_extraction"]
+ assert_includes result.hints, "phantomjs_iframe_malformed_json"
+ end
+
+ test "2xx empty body preserves last good html while recording empty body metadata" do
+ old_successful_refresh = 2.hours.ago
+ cache = create_cache(
+ uri: "https://example.org/empty",
+ html: "last good",
+ body: "last good",
+ scrape_date: 2.days.ago,
+ successful_refresh: old_successful_refresh
+ )
+
+ Distillator::FetchService.expects(:fetch_result).returns(
+ success_result(
+ body: "",
+ headers: { content_type: "text/html" },
+ signals: { network_status: "ok", content_type: "html", empty_body: true },
+ hints: ["empty_body"],
+ http_code: 200,
+ fetch_path: "native"
+ )
+ )
+
+ result = Distillator::FetchCacheStore.fetch(uri: cache.normalized_url, force_scrape: true)
+
+ assert_equal "last good", result.html
+ assert_equal old_successful_refresh.to_i, result.successful_refresh.to_i
+ assert_equal true, result.signals["empty_body"]
+ assert_includes result.hints, "empty_body"
+ end
+
+ test "policy aborted 200 html records content rejection instead of empty body" do
+ cache = create_cache(
+ uri: "https://example.org/rejected",
+ html: nil,
+ body: nil,
+ scrape_date: nil,
+ successful_refresh: nil
+ )
+
+ Distillator::FetchService.expects(:fetch_result).returns(
+ {
+ status: :ok,
+ body: "Une erreur est survenue Retry later.
",
+ raw_body: "Une erreur est survenue Retry later.
",
+ headers: { content_type: "text/html" },
+ final_url: "https://example.org/rejected",
+ redirect_chain: [],
+ wringer: {
+ policy_action: "abort_update",
+ retry: true,
+ cache: false,
+ signals: {
+ network_status: "ok",
+ content_type: "html",
+ primary_issue_key: "generic_error_text",
+ primary_issue_label: "Generic error text observed",
+ primary_issue_severity: "failed",
+ primary_issue_match: {
+ source: "body_text",
+ pattern: "Une erreur est survenue",
+ snippet: "Une erreur est survenue Retry later."
+ }
+ },
+ hints: ["generic_error_text"]
+ },
+ http_code: 200,
+ duration_ms: 3.0,
+ fetch_path: "native"
+ }
+ )
+
+ result = Distillator::FetchCacheStore.fetch(uri: cache.normalized_url, force_scrape: true)
+
+ assert_nil result.html
+ refute_equal true, result.signals["empty_body"]
+ assert_equal true, result.signals["content_rejected"]
+ assert_equal "abort_update", result.signals["storage_decision"]
+ assert_equal "not_stored", result.signals["stored_body_state"]
+ assert_equal "non_empty", result.signals["fetched_body_state"]
+ assert_equal "generic_error_text", result.signals["primary_issue_key"]
+ assert_equal "Une erreur est survenue", result.signals.dig("primary_issue_match", "pattern")
+ assert_not_includes result.hints, "empty_body"
+ assert_includes result.hints, "generic_error_text"
+ end
+
+ test "common failures preserve last good cache content" do
+ old_successful_refresh = 2.hours.ago
+
+ cases = [
+ {
+ name: "404",
+ uri: "https://example.org/not-found",
+ fetch_result: abort_result(
+ http_code: 404,
+ raw_body: "Not Found ",
+ headers: { content_type: "text/html" },
+ signals: { network_status: "ok", content_type: "html" },
+ hints: ["not_found"],
+ fetch_path: "native"
+ ),
+ expected_signal_key: "content_success",
+ expected_signal_value: false,
+ expected_hint: "not_found"
+ },
+ {
+ name: "500",
+ uri: "https://example.org/server-error",
+ fetch_result: abort_result(
+ http_code: 500,
+ raw_body: "Server Error ",
+ headers: { content_type: "text/html" },
+ signals: { network_status: "ok", content_type: "html" },
+ hints: ["http_server_error"],
+ fetch_path: "native"
+ ),
+ expected_signal_key: "content_success",
+ expected_signal_value: false,
+ expected_hint: "http_server_error"
+ },
+ {
+ name: "timeout",
+ uri: "https://example.org/timeout-table",
+ fetch_result: {
+ status: :abort,
+ body: ["abort_update", { error: "execution expired", error_type: "TimeoutError" }],
+ raw_body: nil,
+ headers: {},
+ final_url: "https://example.org/timeout-table",
+ redirect_chain: [],
+ wringer: {
+ signals: { network_status: "failed", timeout: true },
+ hints: ["timeout"]
+ },
+ http_code: nil,
+ duration_ms: 8.0,
+ fetch_path: "native"
+ },
+ expected_signal_key: "timeout",
+ expected_signal_value: true,
+ expected_hint: "timeout"
+ },
+ {
+ name: "blocked URL",
+ uri: "http://example.org/blocked-table",
+ fetch_result: {
+ status: :abort,
+ body: ["abort_update", { error: "Blocked localhost host: 127.0.0.1", error_type: "DistillatorFetchBlocked" }],
+ raw_body: nil,
+ headers: {},
+ final_url: "http://example.org/blocked-table",
+ redirect_chain: [],
+ wringer: {
+ signals: { network_status: "blocked", native_ineligible_reason: "blocked_url" },
+ hints: ["blocked_url", "blocked"]
+ },
+ http_code: nil,
+ duration_ms: 1.0,
+ fetch_path: "blocked"
+ },
+ expected_signal_key: "native_ineligible_reason",
+ expected_signal_value: "blocked_url",
+ expected_hint: "blocked_url"
+ },
+ {
+ name: "empty 2xx body",
+ uri: "https://example.org/empty-table",
+ fetch_result: success_result(
+ body: "",
+ headers: { content_type: "text/html" },
+ signals: { network_status: "ok", content_type: "html", empty_body: true },
+ hints: ["empty_body"],
+ http_code: 200,
+ fetch_path: "native"
+ ),
+ expected_signal_key: "empty_body",
+ expected_signal_value: true,
+ expected_hint: "empty_body"
+ },
+ {
+ name: "HTTP 200 content failure",
+ uri: "https://www.ovation.ca/event-table",
+ fetch_result: {
+ status: :ok,
+ body: "Recherche par titre ",
+ raw_body: "Recherche par titre ",
+ headers: { content_type: "text/html" },
+ final_url: "https://www.ovation.ca/Search/Title/",
+ redirect_chain: ["https://www.ovation.ca/event-table", "https://www.ovation.ca/Search/Title/"],
+ wringer: {
+ policy_action: "abort_update",
+ retry: false,
+ cache: false,
+ signals: {
+ network_status: "ok",
+ content_type: "html",
+ primary_issue_key: "redirect_to_listing",
+ blocking_issue_key: "redirect_to_listing",
+ primary_issue_severity: "failed"
+ },
+ hints: ["redirect_to_listing"]
+ },
+ http_code: 200,
+ duration_ms: 3.0,
+ fetch_path: "native"
+ },
+ expected_signal_key: "blocking_issue_key",
+ expected_signal_value: "redirect_to_listing",
+ expected_hint: "redirect_to_listing"
+ },
+ {
+ name: "unsupported control action",
+ uri: "https://example.org/control-table",
+ fetch_result: {
+ status: :abort,
+ body: ["unsupported_action", { error: "unsupported control action", error_type: "UnsupportedControlAction" }],
+ raw_body: nil,
+ headers: { content_type: "application/json" },
+ final_url: "https://example.org/control-table",
+ redirect_chain: [],
+ wringer: {
+ policy_action: "abort_update",
+ signals: {
+ network_status: "failed",
+ control_action: "unsupported_action",
+ blocking_issue_key: "unsupported_control_action"
+ },
+ hints: ["unsupported_control_action"]
+ },
+ http_code: nil,
+ duration_ms: 2.0,
+ fetch_path: "native"
+ },
+ expected_signal_key: "blocking_issue_key",
+ expected_signal_value: "unsupported_control_action",
+ expected_hint: "unsupported_control_action"
+ },
+ {
+ name: "malformed abort/control payload",
+ uri: "https://example.org/malformed-table",
+ fetch_result: {
+ status: :abort,
+ body: ["abort_update", "malformed"],
+ raw_body: nil,
+ headers: { content_type: "application/json" },
+ final_url: "https://example.org/malformed-table",
+ redirect_chain: [],
+ wringer: {
+ signals: {
+ network_status: "failed",
+ blocking_issue_key: "malformed_control_payload"
+ },
+ hints: ["malformed_control_payload"]
+ },
+ http_code: nil,
+ duration_ms: 2.0,
+ fetch_path: "native"
+ },
+ expected_signal_key: "blocking_issue_key",
+ expected_signal_value: "malformed_control_payload",
+ expected_hint: "malformed_control_payload"
+ }
+ ]
+
+ cases.each do |test_case|
+ cache = create_cache(
+ uri: test_case[:uri],
+ html: "last good #{test_case[:name]}",
+ body: "last good #{test_case[:name]}",
+ scrape_date: 2.days.ago,
+ successful_refresh: old_successful_refresh
+ )
+ old_scrape_date = cache.scrape_date
+
+ Distillator::FetchService.expects(:fetch_result).returns(test_case[:fetch_result])
+
+ result = Distillator::FetchCacheStore.fetch(uri: cache.normalized_url, force_scrape: true)
+ cache.reload
+
+ assert_equal "last good #{test_case[:name]}", result.html, test_case[:name]
+ assert_equal "last good #{test_case[:name]}", result.body, test_case[:name]
+ assert_equal "last good #{test_case[:name]}", cache.html, test_case[:name]
+ assert_equal "last good #{test_case[:name]}", cache.body, test_case[:name]
+ assert_equal old_successful_refresh.to_i, result.successful_refresh.to_i, test_case[:name]
+ assert_equal old_successful_refresh.to_i, cache.successful_refresh.to_i, test_case[:name]
+ assert_operator result.scrape_date, :>, old_scrape_date, test_case[:name]
+ assert_operator cache.scrape_date, :>, old_scrape_date, test_case[:name]
+ assert_equal true, result.signals["last_good_preserved_failure"], test_case[:name]
+ assert_equal true, cache.signals["last_good_preserved_failure"], test_case[:name]
+ assert_includes result.hints, "last_good_preserved_failure", test_case[:name]
+ assert_includes cache.hints, "last_good_preserved_failure", test_case[:name]
+ assert_equal test_case[:expected_signal_value], result.signals[test_case[:expected_signal_key]], test_case[:name]
+ assert_equal test_case[:expected_signal_value], cache.signals[test_case[:expected_signal_key]], test_case[:name]
+ assert_includes result.hints, test_case[:expected_hint], test_case[:name]
+ assert_includes cache.hints, test_case[:expected_hint], test_case[:name]
+ end
+ end
+
+ test "all failed fetch paths preserve last good content and record latest attempt metadata" do
+ old_successful_refresh = 2.hours.ago
+
+ cases = [
+ {
+ name: "normal get transport failure",
+ uri: "https://example.org/timeout",
+ fetch_options: { force_scrape: true },
+ fetch_result: {
+ status: :abort,
+ body: ["abort_update", { error: "execution expired", error_type: "TimeoutError" }],
+ raw_body: nil,
+ headers: {},
+ final_url: "https://example.org/timeout",
+ redirect_chain: [],
+ wringer: {
+ signals: { network_status: "failed", timeout: true },
+ hints: ["timeout"]
+ },
+ http_code: nil,
+ duration_ms: 8.0,
+ fetch_path: "native"
+ },
+ expected_signal_key: "timeout",
+ expected_signal_value: true,
+ expected_hint: "timeout"
+ },
+ {
+ name: "normal get non 2xx",
+ uri: "https://example.org/server-error",
+ fetch_options: { force_scrape: true },
+ fetch_result: abort_result(
+ http_code: 500,
+ raw_body: "Server Error ",
+ headers: { content_type: "text/html" },
+ signals: { network_status: "ok", content_type: "html" },
+ hints: ["http_server_error"],
+ fetch_path: "native"
+ ),
+ expected_signal_key: "content_type",
+ expected_signal_value: "html",
+ expected_hint: "http_server_error"
+ },
+ {
+ name: "rendered failure",
+ uri: "https://example.org/eventsiframe",
+ fetch_options: { force_scrape: true, render_js: true },
+ fetch_result: abort_result(
+ http_code: 500,
+ raw_body: "{\"pageResponses\":[]}",
+ headers: { content_type: "application/json" },
+ signals: { network_status: "ok", content_type: "json", renderer: "legacy_phantomjs" },
+ hints: ["legacy_phantomjs"],
+ fetch_path: "native"
+ ),
+ expected_signal_key: "renderer",
+ expected_signal_value: "legacy_phantomjs",
+ expected_hint: "legacy_phantomjs"
+ },
+ {
+ name: "post failure",
+ uri: "https://example.org/api",
+ fetch_options: { force_scrape: true, json_post: true },
+ fetch_result: abort_result(
+ http_code: 500,
+ raw_body: "{\"error\":\"server\"}",
+ headers: { content_type: "application/json" },
+ signals: { network_status: "ok", content_type: "json", request_method: "POST" },
+ hints: ["json_detected"],
+ fetch_path: "native"
+ ),
+ expected_signal_key: "request_method",
+ expected_signal_value: "POST",
+ expected_hint: "json_detected"
+ },
+ {
+ name: "blocked url",
+ uri: "http://example.org/blocked",
+ fetch_options: { force_scrape: true },
+ fetch_result: {
+ status: :abort,
+ body: ["abort_update", { error: "Blocked localhost host: 127.0.0.1", error_type: "DistillatorFetchBlocked" }],
+ raw_body: nil,
+ headers: {},
+ final_url: "http://example.org/blocked",
+ redirect_chain: [],
+ wringer: {
+ signals: { network_status: "blocked", native_ineligible_reason: "blocked_url" },
+ hints: ["blocked_url", "blocked"]
+ },
+ http_code: nil,
+ duration_ms: 1.0,
+ fetch_path: "blocked"
+ },
+ expected_signal_key: "native_ineligible_reason",
+ expected_signal_value: "blocked_url",
+ expected_hint: "blocked"
+ },
+ {
+ name: "content failure",
+ uri: "https://www.ovation.ca/event",
+ fetch_options: { force_scrape: true },
+ fetch_result: {
+ status: :ok,
+ body: "Recherche par titre ",
+ raw_body: "Recherche par titre ",
+ headers: { content_type: "text/html" },
+ final_url: "https://www.ovation.ca/Search/Title/",
+ redirect_chain: ["https://www.ovation.ca/event", "https://www.ovation.ca/Search/Title/"],
+ wringer: {
+ policy_action: "abort_update",
+ retry: false,
+ cache: false,
+ signals: {
+ network_status: "ok",
+ content_type: "html",
+ primary_issue_key: "redirect_to_listing",
+ blocking_issue_key: "redirect_to_listing",
+ primary_issue_severity: "failed"
+ },
+ hints: ["redirect_to_listing"]
+ },
+ http_code: 200,
+ duration_ms: 3.0,
+ fetch_path: "native"
+ },
+ expected_signal_key: "blocking_issue_key",
+ expected_signal_value: "redirect_to_listing",
+ expected_hint: "redirect_to_listing"
+ },
+ {
+ name: "unsafe redirect",
+ uri: "https://example.org/unsafe-redirect",
+ fetch_options: { force_scrape: true },
+ fetch_result: {
+ status: :abort,
+ body: ["abort_update", { error: "Blocked localhost host: 127.0.0.1", error_type: "DistillatorFetchBlocked" }],
+ raw_body: nil,
+ headers: {},
+ final_url: "http://127.0.0.1/private",
+ redirect_chain: ["https://example.org/unsafe-redirect", "http://127.0.0.1/private"],
+ wringer: {
+ signals: {
+ network_status: "blocked",
+ native_ineligible_reason: "blocked_url",
+ redirect_type: "unsafe"
+ },
+ hints: ["blocked_url", "blocked"]
+ },
+ http_code: nil,
+ duration_ms: 2.0,
+ fetch_path: "blocked"
+ },
+ expected_signal_key: "native_ineligible_reason",
+ expected_signal_value: "blocked_url",
+ expected_hint: "blocked"
+ },
+ {
+ name: "ssl failure",
+ uri: "https://example.org/ssl-failure",
+ fetch_options: { force_scrape: true },
+ fetch_result: {
+ status: :abort,
+ body: ["abort_update", { error: "certificate verify failed", error_type: "SSLFailure" }],
+ raw_body: nil,
+ headers: {},
+ final_url: "https://example.org/ssl-failure",
+ redirect_chain: [],
+ wringer: {
+ signals: {
+ network_status: "failed",
+ system_error: true,
+ blocking_issue_key: "ssl_error"
+ },
+ hints: ["ssl_error"]
+ },
+ http_code: nil,
+ duration_ms: 2.0,
+ fetch_path: "native"
+ },
+ expected_signal_key: "blocking_issue_key",
+ expected_signal_value: "ssl_error",
+ expected_hint: "ssl_error"
+ },
+ {
+ name: "unsupported control action",
+ uri: "https://example.org/control",
+ fetch_options: { force_scrape: true },
+ fetch_result: {
+ status: :abort,
+ body: ["unsupported_action", { error: "unsupported control action", error_type: "UnsupportedControlAction" }],
+ raw_body: nil,
+ headers: { content_type: "application/json" },
+ final_url: "https://example.org/control",
+ redirect_chain: [],
+ wringer: {
+ policy_action: "abort_update",
+ signals: {
+ network_status: "failed",
+ control_action: "unsupported_action",
+ blocking_issue_key: "unsupported_control_action"
+ },
+ hints: ["unsupported_control_action"]
+ },
+ http_code: nil,
+ duration_ms: 2.0,
+ fetch_path: "native"
+ },
+ expected_signal_key: "blocking_issue_key",
+ expected_signal_value: "unsupported_control_action",
+ expected_hint: "unsupported_control_action"
+ },
+ {
+ name: "redirect without valid success",
+ uri: "https://example.org/redirect",
+ fetch_options: { force_scrape: true },
+ fetch_result: {
+ status: :abort,
+ body: ["abort_update", { error: "redirect without final success", error_type: "RedirectFailure" }],
+ raw_body: "moved",
+ headers: { content_type: "text/html" },
+ final_url: "https://example.org/moved",
+ redirect_chain: ["https://example.org/redirect", "https://example.org/moved"],
+ wringer: {
+ signals: {
+ network_status: "ok",
+ content_type: "html",
+ redirect_type: "normal",
+ blocking_issue_key: "redirect_failed"
+ },
+ hints: ["redirect_failed"]
+ },
+ http_code: 302,
+ duration_ms: 2.0,
+ fetch_path: "native"
+ },
+ expected_signal_key: "blocking_issue_key",
+ expected_signal_value: "redirect_failed",
+ expected_hint: "redirect_failed"
+ },
+ {
+ name: "malformed control payload",
+ uri: "https://example.org/malformed",
+ fetch_options: { force_scrape: true },
+ fetch_result: {
+ status: :abort,
+ body: ["abort_update", "malformed"],
+ raw_body: nil,
+ headers: { content_type: "application/json" },
+ final_url: "https://example.org/malformed",
+ redirect_chain: [],
+ wringer: {
+ signals: {
+ network_status: "failed",
+ blocking_issue_key: "malformed_control_payload"
+ },
+ hints: ["malformed_control_payload"]
+ },
+ http_code: nil,
+ duration_ms: 2.0,
+ fetch_path: "native"
+ },
+ expected_signal_key: "blocking_issue_key",
+ expected_signal_value: "malformed_control_payload",
+ expected_hint: "malformed_control_payload"
+ }
+ ]
+
+ cases.each do |test_case|
+ cache = create_cache(
+ uri: test_case[:uri],
+ html: "last good #{test_case[:name]}",
+ body: "last good #{test_case[:name]}",
+ scrape_date: 2.days.ago,
+ successful_refresh: old_successful_refresh
+ )
+ old_scrape_date = cache.scrape_date
+
+ Distillator::FetchService.expects(:fetch_result).returns(test_case[:fetch_result])
+
+ result = Distillator::FetchCacheStore.fetch(uri: cache.normalized_url, **test_case[:fetch_options])
+ cache.reload
+
+ assert_equal "last good #{test_case[:name]}", result.html, test_case[:name]
+ assert_equal "last good #{test_case[:name]}", result.body, test_case[:name]
+ assert_equal "last good #{test_case[:name]}", cache.html, test_case[:name]
+ assert_equal "last good #{test_case[:name]}", cache.body, test_case[:name]
+ assert_equal old_successful_refresh.to_i, result.successful_refresh.to_i, test_case[:name]
+ assert_equal old_successful_refresh.to_i, cache.successful_refresh.to_i, test_case[:name]
+ assert_operator result.scrape_date, :>, old_scrape_date, test_case[:name]
+ assert_operator cache.scrape_date, :>, old_scrape_date, test_case[:name]
+ assert_equal true, result.signals["last_good_preserved_failure"], test_case[:name]
+ assert_equal true, cache.signals["last_good_preserved_failure"], test_case[:name]
+ assert_includes result.hints, "last_good_preserved_failure", test_case[:name]
+ assert_includes cache.hints, "last_good_preserved_failure", test_case[:name]
+ assert_equal test_case[:expected_signal_value], result.signals[test_case[:expected_signal_key]], test_case[:name]
+ assert_equal test_case[:expected_signal_value], cache.signals[test_case[:expected_signal_key]], test_case[:name]
+ assert_includes result.hints, test_case[:expected_hint], test_case[:name]
+ assert_includes cache.hints, test_case[:expected_hint], test_case[:name]
+ end
+ end
+
+ test "successful direct get escapes erb tokens before cache write" do
+ body = "<% dangerous %>"
+ Distillator::FetchService.expects(:fetch_result).returns(
+ success_result(
+ body: body,
+ final_url: "https://example.org/template",
+ fetch_path: "native"
+ )
+ )
+
+ result = Distillator::FetchCacheStore.fetch(
+ uri: "https://example.org/template",
+ force_scrape: true
+ )
+
+ assert_equal "<% dangerous %>", result.html
+ end
+
+ test "absolute_src true rewrites cached html via html rewriter" do
+ Distillator::FetchService.expects(:fetch_result).returns(
+ success_result(
+ body: 'opening night ',
+ final_url: "https://example.org/artist/",
+ fetch_path: "native"
+ )
+ )
+
+ result = Distillator::FetchCacheStore.fetch(
+ uri: "https://example.org/artist/",
+ absolute_src: true,
+ force_scrape: true
+ )
+
+ assert_includes result.html, 'src="https://example.org/image.png"'
+ assert_includes result.html, 'href="https://example.org/artist/events/opening-night"'
+ end
+
+ test "absolute_src false leaves cached body untouched" do
+ body = 'opening night '
+ Distillator::FetchService.expects(:fetch_result).returns(
+ success_result(
+ body: body,
+ final_url: "https://example.org/artist/",
+ fetch_path: "native"
+ )
+ )
+
+ result = Distillator::FetchCacheStore.fetch(
+ uri: "https://example.org/artist/",
+ absolute_src: false,
+ force_scrape: true
+ )
+
+ assert_equal body, result.body
+ assert_equal body, result.html
+ end
+
+ test "absolute_src string false leaves cached body untouched" do
+ body = 'opening night '
+ Distillator::FetchService.expects(:fetch_result).returns(
+ success_result(
+ body: body,
+ final_url: "https://example.org/artist/",
+ fetch_path: "native"
+ )
+ )
+
+ result = Distillator::FetchCacheStore.fetch(
+ uri: "https://example.org/artist/",
+ absolute_src: "false",
+ force_scrape: true
+ )
+
+ assert_equal body, result.body
+ assert_equal body, result.html
+ end
+
+ test "absolute_src true preserves malformed src while rewriting valid relative links" do
+ body = 'path '
+ Distillator::FetchService.expects(:fetch_result).returns(
+ success_result(
+ body: body,
+ final_url: "https://example.org/artist/",
+ fetch_path: "native"
+ )
+ )
+
+ result = Distillator::FetchCacheStore.fetch(
+ uri: "https://example.org/artist/",
+ absolute_src: true,
+ force_scrape: true
+ )
+
+ assert_includes result.html, 'src="not a path"'
+ assert_includes result.html, 'href="https://example.org/path"'
+ assert_includes result.html, 'width="100px"'
+ end
+
+ test "abort fetch without a successful cached body returns structured abort payload" do
+ Distillator::FetchService.expects(:fetch_result).returns(
+ {
+ status: :abort,
+ body: ["abort_update", { error: "connection reset", error_type: "NativeFetchError", source: "native_fetch", retry: true, cache: false }],
+ raw_body: nil,
+ headers: {},
+ final_url: "http://example.org/failure",
+ redirect_chain: [],
+ wringer: {
+ error_type: "NativeFetchError",
+ source: "native_fetch",
+ retry: true,
+ cache: false,
+ signals: { network_status: "failed" },
+ hints: ["timeout"]
+ },
+ http_code: nil,
+ duration_ms: 4.0,
+ fetch_path: "native"
+ }
+ )
+
+ result = Distillator::FetchCacheStore.fetch(uri: "http://example.org/failure", force_scrape: true)
+
+ assert_equal :abort, result.status
+ assert_equal "abort_update", result.body.first
+ assert_equal "NativeFetchError", result.body.last[:error_type]
+ assert_equal "failed", result.signals["network_status"]
+ assert_equal ["timeout"], result.hints
+ assert_equal "native", result.fetch_path
+ end
+
+ test "blocked native fetch without cached body returns structured blocked abort payload" do
+ Distillator::FetchService.expects(:fetch_result).returns(
+ {
+ status: :abort,
+ body: ["abort_update", { error: "Blocked localhost host: 127.0.0.1", error_type: "DistillatorFetchBlocked", source: "distillator_fetch_guard", retry: false, cache: false, step: "url" }],
+ raw_body: nil,
+ headers: {},
+ final_url: "http://127.0.0.1/events",
+ redirect_chain: [],
+ wringer: {
+ error_type: "DistillatorFetchBlocked",
+ source: "distillator_fetch_guard",
+ retry: false,
+ cache: false,
+ signals: { network_status: "blocked", native_ineligible_reason: "blocked_url" },
+ hints: ["blocked_url", "blocked"]
+ },
+ http_code: nil,
+ duration_ms: 1.0,
+ fetch_path: "blocked"
+ }
+ )
+
+ result = Distillator::FetchCacheStore.fetch(uri: "http://127.0.0.1/events", force_scrape: true)
+
+ assert_equal :abort, result.status
+ assert_equal "abort_update", result.body.first
+ assert_equal "DistillatorFetchBlocked", result.body.last[:error_type]
+ assert_equal "blocked", result.signals["network_status"]
+ assert_equal "blocked_url", result.signals["native_ineligible_reason"]
+ assert_equal ["blocked_url", "blocked"], result.hints
+ assert_equal "blocked", result.fetch_path
+ end
+
+ test "blocked url does not overwrite last good cache content or perform a network fetch" do
+ old_successful_refresh = 5.hours.ago
+ cache = create_cache(
+ uri: "http://127.0.0.1/events",
+ html: "last good blocked",
+ body: "last good blocked",
+ scrape_date: 2.days.ago,
+ successful_refresh: old_successful_refresh
+ )
+ old_scrape_date = cache.scrape_date
+
+ Distillator::NativeFetch.expects(:call).never
+ Distillator::FetchShadowComparator.expects(:call).never
+
+ result = Distillator::FetchCacheStore.fetch(
+ uri: cache.normalized_url,
+ mode: :internal,
+ force_scrape: true
+ )
+ cache.reload
+
+ assert_equal "last good blocked", result.html
+ assert_equal "last good blocked", result.body
+ assert_equal "last good blocked", cache.html
+ assert_equal "last good blocked", cache.body
+ assert_equal old_successful_refresh.to_i, result.successful_refresh.to_i
+ assert_equal old_successful_refresh.to_i, cache.successful_refresh.to_i
+ assert_operator result.scrape_date, :>, old_scrape_date
+ assert_operator cache.scrape_date, :>, old_scrape_date
+ assert_equal "blocked", result.signals["network_status"]
+ assert_equal "blocked_url", result.signals["native_ineligible_reason"]
+ assert_equal true, result.signals["last_good_preserved_failure"]
+ assert_includes result.hints, "blocked_url"
+ assert_includes result.hints, "blocked"
+ assert_includes result.hints, "last_good_preserved_failure"
+ end
+
+ private
+
+ def create_cache(uri: "http://example.org/cached", html: "cached", body: html, name: "Cached", scrape_date:, successful_refresh:, http_response_code: 200, headers: {}, signals: { "network_status" => "ok", "content_type" => "html" }, hints: [], final_url: nil, redirect_chain: [])
+ key = Distillator::WringerUrlKey.call(uri)
+
+ Distillator::FetchCache.create!(
+ uri_key: key.uri_key,
+ normalized_url: key.normalized_url,
+ html: html,
+ body: body,
+ name: name,
+ scrape_date: scrape_date,
+ successful_refresh: successful_refresh,
+ http_response_code: http_response_code,
+ headers: headers,
+ signals: signals,
+ hints: hints,
+ final_url: final_url,
+ redirect_chain: redirect_chain
+ )
+ end
+
+ def success_result(body:, headers: { content_type: "text/html" }, signals: { network_status: "ok", content_type: "html" }, hints: [], http_code: 200, final_url: nil, redirect_chain: nil, fetch_path: "native")
+ {
+ status: :ok,
+ body: body,
+ raw_body: body,
+ headers: headers,
+ final_url: final_url,
+ redirect_chain: redirect_chain || Array(final_url).compact,
+ wringer: {
+ signals: signals,
+ hints: hints
+ },
+ http_code: http_code,
+ duration_ms: 12.5,
+ fetch_path: fetch_path
+ }
+ end
+
+ def abort_result(http_code:, raw_body:, headers:, signals:, hints:, fetch_path:)
+ {
+ status: :abort,
+ body: ["abort_update", { error_type: "http_server_error" }],
+ raw_body: raw_body,
+ headers: headers,
+ final_url: "http://example.org/cached",
+ redirect_chain: [],
+ wringer: {
+ signals: signals,
+ hints: hints,
+ http_code: http_code
+ },
+ http_code: http_code,
+ duration_ms: 8.0,
+ fetch_path: fetch_path
+ }
+ end
+end
diff --git a/test/services/distillator/fetch_eligibility_test.rb b/test/services/distillator/fetch_eligibility_test.rb
new file mode 100644
index 00000000..60596570
--- /dev/null
+++ b/test/services/distillator/fetch_eligibility_test.rb
@@ -0,0 +1,98 @@
+require "test_helper"
+
+class Distillator::FetchEligibilityTest < ActiveSupport::TestCase
+ test "eligible for rendered fetch when render_js is true" do
+ result = Distillator::FetchEligibility.call(
+ url: "https://example.com/events",
+ render_js: true,
+ scrape_options: {}
+ )
+
+ assert_equal true, result.eligible?
+ assert_equal :rendered_fetch, result.reason
+ end
+
+ test "eligible for native http post when json_post is true" do
+ result = Distillator::FetchEligibility.call(
+ url: "https://example.com/events",
+ render_js: false,
+ scrape_options: { json_post: true }
+ )
+
+ assert_equal true, result.eligible?
+ assert_equal :native_http_post, result.reason
+ end
+
+ test "ineligible for non-http scheme" do
+ result = Distillator::FetchEligibility.call(
+ url: "ftp://example.com/events",
+ render_js: false,
+ scrape_options: {}
+ )
+
+ assert_equal false, result.eligible?
+ assert_equal :unsupported_scheme, result.reason
+ assert_equal true, result.abort?
+ end
+
+ test "ineligible for blocked url" do
+ result = Distillator::FetchEligibility.call(
+ url: "http://127.0.0.1/events",
+ render_js: false,
+ scrape_options: {}
+ )
+
+ assert_equal false, result.eligible?
+ assert_equal :blocked_url, result.reason
+ assert_equal true, result.abort?
+ assert_match(/blocked/i, result.details[:guard_error])
+ end
+
+ test "ineligible when force legacy is set" do
+ result = Distillator::FetchEligibility.call(
+ url: "https://example.com/events",
+ render_js: false,
+ scrape_options: { force_legacy: true }
+ )
+
+ assert_equal false, result.eligible?
+ assert_equal :forced_legacy, result.reason
+ assert_equal true, result.legacy_fallback?
+ end
+
+ test "string false force_legacy does not force legacy" do
+ result = Distillator::FetchEligibility.call(
+ url: "https://example.com/events",
+ render_js: "false",
+ scrape_options: { force_legacy: "false" }
+ )
+
+ assert_equal true, result.eligible?
+ assert_equal :native_http_get, result.reason
+ assert_equal false, result.details[:forced_legacy]
+ assert_equal false, result.details[:render_js]
+ end
+
+ test "string true render_js is reflected in details" do
+ result = Distillator::FetchEligibility.call(
+ url: "https://example.com/events",
+ render_js: "true",
+ scrape_options: {}
+ )
+
+ assert_equal true, result.eligible?
+ assert_equal :rendered_fetch, result.reason
+ assert_equal true, result.details[:render_js]
+ end
+
+ test "eligible for ordinary http get" do
+ result = Distillator::FetchEligibility.call(
+ url: "https://example.com/events",
+ render_js: false,
+ scrape_options: {}
+ )
+
+ assert_equal true, result.eligible?
+ assert_equal :native_http_get, result.reason
+ end
+end
diff --git a/test/services/distillator/fetch_guard_test.rb b/test/services/distillator/fetch_guard_test.rb
new file mode 100644
index 00000000..a02b4ed9
--- /dev/null
+++ b/test/services/distillator/fetch_guard_test.rb
@@ -0,0 +1,253 @@
+require "test_helper"
+
+class Distillator::FetchGuardTest < ActiveSupport::TestCase
+ class StaticResolver
+ def initialize(addresses)
+ @addresses = addresses
+ end
+
+ def getaddresses(_host)
+ @addresses
+ end
+ end
+
+ class RaisingResolver
+ def initialize(error)
+ @error = error
+ end
+
+ def getaddresses(_host)
+ raise @error
+ end
+ end
+
+ class MappingResolver
+ def initialize(mapping)
+ @mapping = mapping
+ end
+
+ def getaddresses(host)
+ @mapping.fetch(host, [])
+ end
+ end
+
+ test "allows https public hostname" do
+ result = Distillator::FetchGuard.check_url(
+ "https://example.org/events",
+ resolver: StaticResolver.new(["93.184.216.34"])
+ )
+
+ assert result.allowed?
+ assert_equal "https://example.org/events", result.normalized_url
+ end
+
+ test "allows http public hostname like wringer safe_url" do
+ result = Distillator::FetchGuard.check_url(
+ "http://example.org/events",
+ resolver: StaticResolver.new(["93.184.216.34"])
+ )
+
+ assert result.allowed?
+ assert_equal "http://example.org/events", result.normalized_url
+ end
+
+ test "blocks localhost" do
+ result = Distillator::FetchGuard.check_url(
+ "http://localhost/events",
+ resolver: StaticResolver.new([])
+ )
+
+ refute result.allowed?
+ assert_includes result.error, "localhost"
+ assert_equal :blocked_localhost, result.reason
+ end
+
+ test "blocks 127.0.0.1:3009 like legacy safe_url" do
+ result = refute_allowed("http://127.0.0.1:3009")
+
+ assert_includes result.error, "127.0.0.1"
+ end
+
+ test "blocks localhost hostname exactly" do
+ result = Distillator::FetchGuard.check_url(
+ "http://localhost",
+ resolver: StaticResolver.new([])
+ )
+
+ refute result.allowed?
+ assert_includes result.error, "localhost"
+ end
+
+ test "blocks 10.0.0.1 like legacy safe_url" do
+ result = refute_allowed("http://10.0.0.1")
+
+ assert_includes result.error, "10.0.0.1"
+ end
+
+ test "blocks 169.254.169.254 like legacy safe_url" do
+ result = refute_allowed("http://169.254.169.254")
+
+ assert_includes result.error, "169.254.169.254"
+ end
+
+ test "blocks file scheme like legacy safe_url" do
+ result = Distillator::FetchGuard.check_url(
+ "file:///etc/passwd",
+ resolver: StaticResolver.new([])
+ )
+
+ refute result.allowed?
+ assert_includes result.error, "scheme"
+ assert_equal :blocked_scheme, result.reason
+ end
+
+ test "allows http example.org like legacy safe_url" do
+ result = Distillator::FetchGuard.check_url(
+ "http://example.org",
+ resolver: StaticResolver.new(["93.184.216.34"])
+ )
+
+ assert result.allowed?
+ end
+
+ test "blocks 192.168.0.0/16" do
+ result = refute_allowed("http://192.168.1.5/events")
+
+ assert_includes result.error, "192.168.1.5"
+ end
+
+ test "blocks every 172.16.0.0/12 legacy private range boundary" do
+ %w[172.16.0.1 172.20.10.5 172.31.255.254].each do |ip|
+ result = refute_allowed("http://#{ip}/events")
+
+ assert_includes result.error, ip
+ end
+ end
+
+ test "blocks ipv6 loopback ::1" do
+ result = refute_allowed("http://[::1]/events")
+
+ assert_includes result.error, "::1"
+ end
+
+ test "blocks ipv6 unique local fc00::/7" do
+ %w[fc00::1 fd12:3456:789a::1].each do |ip|
+ result = refute_allowed("http://[#{ip}]/events")
+
+ assert_includes result.error, ip
+ end
+ end
+
+ test "blocks ipv6 link local fe80::/10" do
+ %w[fe80::1 febf::abcd].each do |ip|
+ result = refute_allowed("http://[#{ip}]/events")
+
+ assert_includes result.error, ip
+ end
+ end
+
+ test "blocks DNS resolution to private IP deterministically" do
+ result = Distillator::FetchGuard.check_url(
+ "https://private.example/events",
+ resolver: StaticResolver.new(["10.1.2.3"])
+ )
+
+ refute result.allowed?
+ assert_includes result.error, "10.1.2.3"
+ assert_includes result.error, "private.example"
+ assert_equal :blocked_private_ip, result.reason
+ end
+
+ test "blocks DNS ambiguity when resolver returns no addresses" do
+ result = Distillator::FetchGuard.check_url(
+ "https://missing.example/events",
+ resolver: StaticResolver.new([])
+ )
+
+ refute result.allowed?
+ assert_equal :dns_resolution_failed, result.reason
+ assert_includes result.error, "no DNS resolution result"
+ end
+
+ test "blocks DNS resolver exceptions instead of allowing through" do
+ result = Distillator::FetchGuard.check_url(
+ "https://timeout.example/events",
+ resolver: RaisingResolver.new(Timeout::Error.new("execution expired"))
+ )
+
+ refute result.allowed?
+ assert_equal :dns_resolution_error, result.reason
+ assert_includes result.error, "Timeout::Error"
+ end
+
+ test "allows DNS hostname when stubbed resolver returns public IP" do
+ result = Distillator::FetchGuard.check_url(
+ "https://public.example/events",
+ resolver: StaticResolver.new(["93.184.216.34"])
+ )
+
+ assert result.allowed?
+ end
+
+ test "blocks redirect_chain containing private IP" do
+ result = Distillator::FetchGuard.check_response(
+ {
+ final_url: "https://example.org/final",
+ redirect_chain: ["https://example.org/start", "http://192.168.1.2/redirect"]
+ },
+ resolver: StaticResolver.new(["93.184.216.34"])
+ )
+
+ refute result.allowed?
+ assert_includes result.error, "192.168.1.2"
+ assert_equal :blocked_private_ip, result.reason
+ end
+
+ test "blocks unsafe final url even when redirect chain itself looks public" do
+ resolver = MappingResolver.new("example.org" => ["93.184.216.34"])
+ result = Distillator::FetchGuard.check_response(
+ {
+ final_url: "http://169.254.169.254/latest/meta-data",
+ redirect_chain: ["https://example.org/start", "https://example.org/final"]
+ },
+ resolver: resolver
+ )
+
+ refute result.allowed?
+ assert_equal :blocked_private_ip, result.reason
+ assert_includes result.error, "169.254.169.254"
+ end
+
+ test "blocked result includes caller-usable error details" do
+ result = Distillator::FetchGuard.check_url(
+ "http://localhost/events",
+ resolver: StaticResolver.new([])
+ )
+
+ refute result.allowed?
+ assert_kind_of String, result.error
+ assert_includes result.error, "Blocked"
+ assert_equal :blocked_localhost, result.reason
+ assert_nil result.normalized_url
+ end
+
+ test "blocks unresolved host instead of treating it as public unknown" do
+ result = Distillator::FetchGuard.check_url(
+ "https://missing.example/events",
+ resolver: stub(getaddresses: [])
+ )
+
+ refute result.allowed?
+ assert_equal :dns_resolution_failed, result.reason
+ end
+
+ private
+
+ def refute_allowed(url)
+ result = Distillator::FetchGuard.check_url(url, resolver: StaticResolver.new([]))
+
+ refute result.allowed?, "#{url} should be blocked"
+ assert_includes result.error, "Blocked"
+ result
+ end
+end
diff --git a/test/services/distillator/fetch_mode_test.rb b/test/services/distillator/fetch_mode_test.rb
new file mode 100644
index 00000000..4fec6758
--- /dev/null
+++ b/test/services/distillator/fetch_mode_test.rb
@@ -0,0 +1,200 @@
+require "test_helper"
+
+class Distillator::FetchModeTest < ActiveSupport::TestCase
+ setup do
+ @old_mode = ENV["DISTILLATOR_FETCH_MODE"]
+ end
+
+ teardown do
+ ENV["DISTILLATOR_FETCH_MODE"] = @old_mode
+ end
+
+ test "current remains an env helper defaulting to active" do
+ ENV["DISTILLATOR_FETCH_MODE"] = nil
+
+ assert_equal :active, Distillator::FetchMode.current
+ assert Distillator::FetchMode.active?
+ end
+
+ test "current resolves internal input alias to active" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "internal"
+
+ assert_equal :active, Distillator::FetchMode.current
+ assert Distillator::FetchMode.active?
+ end
+
+ test "current resolves shadow mode from env for legacy helper callers" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "shadow"
+
+ assert_equal :shadow, Distillator::FetchMode.current
+ assert Distillator::FetchMode.shadow?
+ end
+
+ test "unknown mode falls back to active" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "surprise"
+
+ assert_equal :active, Distillator::FetchMode.current
+ end
+
+ test "blank mode falls back to active" do
+ ENV["DISTILLATOR_FETCH_MODE"] = " "
+
+ assert_equal :active, Distillator::FetchMode.current
+ end
+
+ test "mixed case mode is normalized" do
+ ENV["DISTILLATOR_FETCH_MODE"] = " ShAdOw "
+
+ assert_equal :shadow, Distillator::FetchMode.current
+ end
+
+ test "internal input alias resolves to active mode" do
+ assert_equal :active, Distillator::FetchMode.parse("internal")
+ end
+
+ test "execution modes expose active as the canonical public mode" do
+ assert_equal %w[legacy active shadow], Distillator::FetchMode::EXECUTION_MODES
+ assert_equal({ "internal" => "active" }, Distillator::FetchMode::ALIASES)
+ end
+
+ test "active website resolves to active mode" do
+ website = websites(:one)
+ website.update!(distillator_mode: "active")
+
+ assert_equal :active, Distillator::FetchMode.rollout_mode(website: website)
+ assert_equal :active, Distillator::FetchMode.resolve(website: website)
+ end
+
+ test "rollout resolution object uses safe legacy default without website context" do
+ ENV["DISTILLATOR_FETCH_MODE"] = nil
+
+ resolution = Distillator::FetchMode.rollout_resolution_object
+
+ assert_equal :legacy, resolution.execution_mode
+ assert_equal :legacy, resolution.rollout_mode
+ assert_equal :wringer, resolution.active_backend
+ assert_equal :default, resolution.source
+ assert_nil resolution.requested_mode
+ end
+
+ test "resolve uses safe legacy default when no website context is present" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "internal"
+
+ resolution = Distillator::FetchMode.resolution
+
+ assert_equal :legacy, resolution.mode
+ assert_equal :default, resolution.source
+ assert_equal :legacy, Distillator::FetchMode.resolve
+ assert_equal :legacy, Distillator::FetchMode.rollout_mode
+ end
+
+ test "rollout resolution ignores env internal without website context" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "internal"
+
+ resolution = Distillator::FetchMode.rollout_resolution_object
+
+ assert_equal :legacy, resolution.execution_mode
+ assert_equal :legacy, resolution.rollout_mode
+ assert_equal :wringer, resolution.active_backend
+ assert_equal :default, resolution.source
+ end
+
+ test "website rollout wins over legacy env default" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "legacy"
+ website = websites(:one)
+ website.update!(distillator_mode: "active")
+
+ resolution = Distillator::FetchMode.resolution(website: website)
+
+ assert_equal :active, resolution.mode
+ assert_equal :website, resolution.source
+ end
+
+ test "rollout resolution object maps active website to condenser backend" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "legacy"
+ website = websites(:one)
+ website.update!(distillator_mode: "active")
+
+ resolution = Distillator::FetchMode.rollout_resolution_object(website: website)
+
+ assert_equal :active, resolution.execution_mode
+ assert_equal :active, resolution.rollout_mode
+ assert_equal :internal, resolution.dispatch_mode
+ assert_equal :condenser, resolution.active_backend
+ assert_equal :website, resolution.source
+ assert_equal website.id, resolution.website_id
+ end
+
+ test "resolve reports env source only for safe legacy env without website context" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "legacy"
+
+ resolution = Distillator::FetchMode.resolution
+
+ assert_equal :legacy, resolution.mode
+ assert_equal :env, resolution.source
+ end
+
+ test "explicit internal alias remains available for diagnostic compatibility" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "legacy"
+
+ resolution = Distillator::FetchMode.resolution(explicit_mode: :internal)
+ rollout = Distillator::FetchMode.rollout_resolution(explicit_mode: :internal)
+ rollout_object = Distillator::FetchMode.rollout_resolution_object(explicit_mode: :internal)
+
+ assert_equal :active, resolution.mode
+ assert_equal :explicit, resolution.source
+ assert_equal :active, rollout.mode
+ assert_equal :explicit, rollout.source
+ assert_equal :active, rollout_object.execution_mode
+ assert_equal :active, rollout_object.rollout_mode
+ assert_equal :internal, rollout_object.dispatch_mode
+ assert_equal :condenser, rollout_object.active_backend
+ assert_equal :explicit, rollout_object.source
+ assert_equal :internal, rollout_object.requested_mode
+ end
+
+ test "parse fails safe for nil blank and invalid values" do
+ assert_equal :active, Distillator::FetchMode.parse(nil)
+ assert_equal :active, Distillator::FetchMode.parse("")
+ assert_equal :active, Distillator::FetchMode.parse("not-a-mode")
+ assert_equal :active, Distillator::FetchMode.parse("INTERNAL")
+ end
+
+ test "fetch mode resolves active website from website_id only" do
+ website = Website.create!(
+ name: "fetch-mode-website-id",
+ seedurl: "fetch-mode-website-id",
+ graph_name: "https://example.org/fetch-mode-website-id",
+ default_language: "en",
+ distillator_mode: "active"
+ )
+
+ mode = Distillator::FetchMode.resolve(
+ website: nil,
+ website_id: website.id,
+ log_context: {}
+ )
+
+ assert_equal :active, mode
+ assert_equal :website_id, Distillator::FetchMode.resolution(website_id: website.id).source
+ end
+
+ test "fetch mode resolves active website from log_context website_id only" do
+ website = Website.create!(
+ name: "fetch-mode-log-context",
+ seedurl: "fetch-mode-log-context",
+ graph_name: "https://example.org/fetch-mode-log-context",
+ default_language: "en",
+ distillator_mode: "active"
+ )
+
+ mode = Distillator::FetchMode.resolve(
+ website: nil,
+ website_id: nil,
+ log_context: { website_id: website.id }
+ )
+
+ assert_equal :active, mode
+ assert_equal :website_id, Distillator::FetchMode.resolution(log_context: { website_id: website.id }).source
+ end
+end
diff --git a/test/services/distillator/fetch_recorder_test.rb b/test/services/distillator/fetch_recorder_test.rb
new file mode 100644
index 00000000..5b4cf027
--- /dev/null
+++ b/test/services/distillator/fetch_recorder_test.rb
@@ -0,0 +1,62 @@
+require "test_helper"
+require "digest/sha1"
+
+class Distillator::FetchRecorderTest < ActiveSupport::TestCase
+ setup do
+ @url = "https://example.com/events"
+ @site = "migration_test"
+ @digest = Digest::SHA1.hexdigest(@url)
+ @dir = Rails.root.join("data", "migration_fixtures", @site, "fetch")
+ @path = @dir.join("#{@digest}.json")
+ FileUtils.rm_f(@path)
+ ENV["RECORD_FETCH"] = nil
+ ENV["FETCH_SITE"] = nil
+ end
+
+ teardown do
+ FileUtils.rm_f(@path)
+ ENV["RECORD_FETCH"] = nil
+ ENV["FETCH_SITE"] = nil
+ end
+
+ test "does nothing when disabled" do
+ Distillator::FetchRecorder.record(
+ url: @url,
+ response: { status: :ok, body: "ok", final_url: nil, headers: {}, wringer: {} }
+ )
+
+ assert_not File.exist?(@path)
+ end
+
+ test "records one json file when enabled" do
+ ENV["RECORD_FETCH"] = "true"
+ ENV["FETCH_SITE"] = @site
+ response = { status: :ok, body: "ok", final_url: nil, headers: {}, wringer: { signals: {} } }
+
+ Distillator::FetchRecorder.record(url: @url, response: response)
+
+ assert File.exist?(@path)
+ payload = JSON.parse(File.read(@path))
+ assert_equal @url, payload["url"]
+ assert payload["recorded_at"].present?
+ assert_equal "ok", payload.dig("response", "status")
+ assert_equal "ok", payload.dig("response", "body")
+ assert_equal({}, payload.dig("response", "headers"))
+ end
+
+ test "does not overwrite existing fixture file" do
+ ENV["RECORD_FETCH"] = "true"
+ ENV["FETCH_SITE"] = @site
+
+ FileUtils.mkdir_p(@dir)
+ File.write(@path, '{"sentinel":"keep"}')
+ original = File.read(@path)
+
+ Distillator::FetchRecorder.record(
+ url: @url,
+ response: { status: :ok, body: "new", final_url: "https://example.com/final", headers: {}, wringer: {} }
+ )
+
+ assert_equal original, File.read(@path)
+ end
+end
diff --git a/test/services/distillator/fetch_replay_test.rb b/test/services/distillator/fetch_replay_test.rb
new file mode 100644
index 00000000..f2a24529
--- /dev/null
+++ b/test/services/distillator/fetch_replay_test.rb
@@ -0,0 +1,309 @@
+require "test_helper"
+require "digest/sha1"
+require "json"
+
+class Distillator::FetchReplayTest < ActiveSupport::TestCase
+ setup do
+ @url = "https://example.com/events"
+ @site = "replay_test"
+ @digest = Digest::SHA1.hexdigest(@url)
+ @dir = Rails.root.join("data", "migration_fixtures", @site, "fetch")
+ @path = @dir.join("#{@digest}.json")
+ FileUtils.mkdir_p(@dir)
+ FileUtils.rm_f(@path)
+ ENV["REPLAY_FETCH"] = nil
+ ENV["FETCH_SITE"] = nil
+ end
+
+ teardown do
+ FileUtils.rm_f(@path)
+ ENV["REPLAY_FETCH"] = nil
+ ENV["FETCH_SITE"] = nil
+ end
+
+ def enable_replay!
+ ENV["REPLAY_FETCH"] = "true"
+ ENV["FETCH_SITE"] = @site
+ end
+
+ def write_fixture(url: @url, response: nil, include_url: true, include_response: true, raw: nil)
+ if raw
+ File.write(@path, raw)
+ return
+ end
+
+ payload = { "recorded_at" => "2026-04-22T00:00:00Z" }
+ payload["url"] = url if include_url
+ payload["response"] = response if include_response
+ File.write(@path, JSON.pretty_generate(payload))
+ end
+
+ def full_response(status: "ok", duration_ms: 12.3, headers: { "content_type" => "text/html" }, extra: {})
+ {
+ "status" => status,
+ "body" => "fixture",
+ "headers" => headers,
+ "final_url" => "https://example.com/final",
+ "redirect_chain" => ["https://example.com/start", "https://example.com/final"],
+ "wringer" => { "signals" => { "network_status" => "ok" }, "hints" => ["redirected"] },
+ "duration_ms" => duration_ms
+ }.merge(extra)
+ end
+
+ test "replay returns expected response" do
+ ENV["REPLAY_FETCH"] = "true"
+ ENV["FETCH_SITE"] = @site
+ expected = {
+ status: :ok,
+ body: "ok",
+ headers: { content_type: "text/html" },
+ final_url: "https://example.com/events",
+ redirect_chain: ["https://example.com/events"],
+ wringer: { signals: { network_status: "ok" }, hints: [] },
+ duration_ms: 12.3
+ }
+
+ File.write(@path, <<~JSON)
+ {
+ "url": "#{@url}",
+ "recorded_at": "2026-04-22T00:00:00Z",
+ "response": {
+ "status": "#{expected[:status]}",
+ "body": "#{expected[:body]}",
+ "headers": {"content_type": "text/html"},
+ "final_url": "#{expected[:final_url]}",
+ "redirect_chain": ["https://example.com/events"],
+ "wringer": {"signals": {"network_status": "ok"}, "hints": []},
+ "duration_ms": #{expected[:duration_ms]}
+ }
+ }
+ JSON
+
+ result = Distillator::FetchReplay.load(url: @url)
+
+ assert_equal expected, result
+ end
+
+ test "missing file raises clear error" do
+ ENV["REPLAY_FETCH"] = "true"
+ ENV["FETCH_SITE"] = @site
+
+ error = assert_raises(RuntimeError) { Distillator::FetchReplay.load(url: @url) }
+ assert_equal "Missing replay fixture for URL: #{@url}", error.message
+ end
+
+ test "status is normalized to symbol" do
+ enable_replay!
+ write_fixture(response: full_response(status: "ok"))
+
+ result = Distillator::FetchReplay.load(url: @url)
+
+ assert_equal :ok, result[:status]
+ end
+
+ test "status abort is normalized to symbol" do
+ enable_replay!
+ write_fixture(response: full_response(status: "abort"))
+
+ result = Distillator::FetchReplay.load(url: @url)
+
+ assert_equal :abort, result[:status]
+ end
+
+ test "headers are normalized to symbol snake_case keys" do
+ enable_replay!
+ write_fixture(
+ response: full_response(
+ headers: {
+ "Content-Type" => "text/html",
+ "Cache-Control" => "max-age=0"
+ }
+ )
+ )
+
+ result = Distillator::FetchReplay.load(url: @url)
+
+ assert_equal(
+ {
+ content_type: "text/html",
+ cache_control: "max-age=0"
+ },
+ result[:headers]
+ )
+ end
+
+ test "duration_ms is numeric and non-negative" do
+ enable_replay!
+ write_fixture(response: full_response(duration_ms: 9.5))
+
+ result = Distillator::FetchReplay.load(url: @url)
+
+ assert_kind_of Numeric, result[:duration_ms]
+ assert_operator result[:duration_ms], :>=, 0
+ end
+
+ test "duration_ms defaults to zero when missing" do
+ enable_replay!
+ response = full_response
+ response.delete("duration_ms")
+ write_fixture(response: response)
+
+ result = Distillator::FetchReplay.load(url: @url)
+
+ assert_kind_of Numeric, result[:duration_ms]
+ assert_equal 0, result[:duration_ms]
+ end
+
+ test "missing response key raises error" do
+ enable_replay!
+ write_fixture(include_response: false, include_url: true)
+
+ error = assert_raises(RuntimeError) { Distillator::FetchReplay.load(url: @url) }
+ assert_includes error.message, @path.to_s
+ assert_includes error.message, @url
+ end
+
+ test "missing url key raises error" do
+ enable_replay!
+ write_fixture(include_url: false, include_response: true, response: full_response)
+
+ error = assert_raises(RuntimeError) { Distillator::FetchReplay.load(url: @url) }
+ assert_includes error.message, @path.to_s
+ assert_includes error.message, @url
+ end
+
+ test "invalid JSON raises error" do
+ enable_replay!
+ write_fixture(raw: "{\"url\":\"#{@url}\",\"response\":")
+
+ error = assert_raises(StandardError) { Distillator::FetchReplay.load(url: @url) }
+ assert_includes error.message, @path.to_s
+ assert_includes error.message, @url
+ end
+
+ test "replay response does not include unexpected keys" do
+ enable_replay!
+ write_fixture(response: full_response)
+
+ result = Distillator::FetchReplay.load(url: @url)
+
+ expected_keys = [
+ :status,
+ :body,
+ :headers,
+ :final_url,
+ :redirect_chain,
+ :wringer,
+ :duration_ms
+ ]
+
+ assert_equal expected_keys.sort, result.keys.sort
+ end
+
+ test "fixture with extra field raises error" do
+ enable_replay!
+ write_fixture(response: full_response(extra: { "extra_field" => "boom" }))
+
+ error = assert_raises(RuntimeError) { Distillator::FetchReplay.load(url: @url) }
+ assert_match(/extra_field/, error.message)
+ assert_includes error.message, @path.to_s
+ assert_includes error.message, @url
+ end
+
+ test "replay matches recorded structure and defaults duration_ms to zero" do
+ ENV["REPLAY_FETCH"] = "true"
+ ENV["FETCH_SITE"] = @site
+ expected = {
+ status: :abort,
+ body: ["abort_update", { error_type: "WringerFetchError" }],
+ headers: { content_type: "text/html" },
+ final_url: "https://example.com/final",
+ redirect_chain: ["https://example.com/start", "https://example.com/final"],
+ wringer: { signals: {}, hints: [] },
+ duration_ms: 0
+ }
+
+ File.write(@path, <<~JSON)
+ {
+ "url": "#{@url}",
+ "recorded_at": "2026-04-22T00:00:00Z",
+ "response": {
+ "status": "abort",
+ "body": ["abort_update", {"error_type": "WringerFetchError"}],
+ "headers": {"content_type": "text/html"},
+ "final_url": "https://example.com/final",
+ "redirect_chain": ["https://example.com/start", "https://example.com/final"],
+ "wringer": {"signals": {}, "hints": []}
+ }
+ }
+ JSON
+
+ result = Distillator::FetchReplay.load(url: @url)
+
+ assert_equal expected, result
+ end
+
+ test "replay preserves full fetch metadata" do
+ ENV["REPLAY_FETCH"] = "true"
+ ENV["FETCH_SITE"] = @site
+
+ File.write(@path, <<~JSON)
+ {
+ "url": "#{@url}",
+ "recorded_at": "2026-04-22T00:00:00Z",
+ "response": {
+ "status": "ok",
+ "body": "fixture",
+ "headers": {"content_type": "text/html", "cache_control": "max-age=0"},
+ "final_url": "https://example.com/final",
+ "redirect_chain": ["https://example.com/start", "https://example.com/final"],
+ "wringer": {"signals": {"network_status": "ok"}, "hints": ["redirected"]},
+ "duration_ms": 55.5
+ }
+ }
+ JSON
+
+ result = Distillator::FetchReplay.load(url: @url)
+
+ assert result.key?(:final_url), "missing final_url in replay response"
+ assert result.key?(:headers), "missing headers in replay response"
+ assert result.key?(:status), "missing status in replay response"
+ assert result.key?(:body), "missing body in replay response"
+ assert result.key?(:redirect_chain), "missing redirect_chain in replay response"
+ assert result.key?(:wringer), "missing wringer in replay response"
+ assert result.key?(:duration_ms), "missing duration_ms in replay response"
+
+ assert_equal :ok, result[:status]
+ assert_equal "fixture", result[:body]
+ assert_equal({ content_type: "text/html", cache_control: "max-age=0" }, result[:headers])
+ assert_equal "https://example.com/final", result[:final_url]
+ assert_equal ["https://example.com/start", "https://example.com/final"], result[:redirect_chain]
+ assert_equal({ signals: { network_status: "ok" }, hints: ["redirected"] }, result[:wringer])
+ assert_equal 55.5, result[:duration_ms]
+ end
+
+ test "fetch service uses replay when enabled" do
+ ENV["REPLAY_FETCH"] = "true"
+ Distillator::FetchReplay.expects(:load).with(url: @url).returns(
+ status: :ok,
+ body: "fixture",
+ headers: { content_type: "text/html" },
+ final_url: "https://example.com/final",
+ redirect_chain: ["https://example.com/start", "https://example.com/final"],
+ wringer: { signals: {}, hints: [] },
+ duration_ms: 0
+ )
+ Distillator::FetchService.expects(:legacy_fetch).never
+ Distillator::FetchService.expects(:internal_fetch).never
+
+ result = Distillator::FetchService.fetch(url: @url)
+
+ assert_equal :ok, result[:status]
+ assert_equal "fixture", result[:body]
+ assert_equal({ content_type: "text/html" }, result[:headers])
+ assert_equal "https://example.com/final", result[:final_url]
+ assert_equal ["https://example.com/start", "https://example.com/final"], result[:redirect_chain]
+ assert_equal({ signals: {}, hints: [] }, result[:wringer])
+ assert_equal 0, result[:duration_ms]
+ end
+end
diff --git a/test/services/distillator/fetch_response_contract_test.rb b/test/services/distillator/fetch_response_contract_test.rb
new file mode 100644
index 00000000..f0437a02
--- /dev/null
+++ b/test/services/distillator/fetch_response_contract_test.rb
@@ -0,0 +1,266 @@
+require "test_helper"
+require "digest/sha1"
+
+class Distillator::FetchResponseContractTest < ActiveSupport::TestCase
+ CONTRACT_KEYS = [
+ :status,
+ :body,
+ :headers,
+ :final_url,
+ :redirect_chain,
+ :wringer,
+ :duration_ms
+ ].freeze
+
+ setup do
+ @old_fetch_mode = ENV["DISTILLATOR_FETCH_MODE"]
+ @old_replay_fetch = ENV["REPLAY_FETCH"]
+ @old_fetch_site = ENV["FETCH_SITE"]
+ @url = "https://example.com/events"
+ @replay_site = "fetch_response_contract_test"
+ @replay_digest = Digest::SHA1.hexdigest(@url)
+ @replay_dir = Rails.root.join("data", "migration_fixtures", @replay_site, "fetch")
+ @replay_path = @replay_dir.join("#{@replay_digest}.json")
+ FileUtils.mkdir_p(@replay_dir)
+ FileUtils.rm_f(@replay_path)
+ end
+
+ teardown do
+ ENV["DISTILLATOR_FETCH_MODE"] = @old_fetch_mode
+ ENV["REPLAY_FETCH"] = @old_replay_fetch
+ ENV["FETCH_SITE"] = @old_fetch_site
+ FileUtils.rm_f(@replay_path)
+ end
+
+ test "legacy fetch returns canonical response contract" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "legacy"
+ Distillator::FetchRecorder.stubs(:record)
+ Distillator::FetchService.expects(:legacy_fetch).returns(
+ status: :ok,
+ body: "legacy",
+ headers: { "Content-Type" => "text/html", "Cache-Control" => "max-age=0" },
+ final_url: @url,
+ redirect_chain: nil,
+ wringer: { signals: {}, hints: [] }
+ )
+
+ response = Distillator::FetchService.fetch(url: @url)
+
+ assert_contract(response)
+ assert_equal(
+ {
+ content_type: "text/html",
+ cache_control: "max-age=0"
+ },
+ response[:headers]
+ )
+ assert_equal [], response[:redirect_chain]
+ end
+
+ test "default fetch without website context returns canonical legacy response contract" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "internal"
+ Distillator::FetchRecorder.stubs(:record)
+ Distillator::FetchService.expects(:legacy_fetch).returns(
+ status: :ok,
+ body: "legacy-default",
+ headers: { "Content-Type" => "text/html" },
+ final_url: @url,
+ redirect_chain: nil,
+ wringer: { signals: {}, hints: [] }
+ )
+ Distillator::FetchService.expects(:internal_fetch).never
+
+ response = Distillator::FetchService.fetch(url: @url)
+
+ assert_contract(response)
+ assert_equal "legacy-default", response[:body]
+ assert_equal [], response[:redirect_chain]
+ end
+
+ test "internal fetch returns canonical response contract" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "legacy"
+ Distillator::FetchRecorder.stubs(:record)
+ Distillator::FetchGuard.stubs(:check_url).returns(fetch_guard_allowed)
+ Distillator::FetchGuard.stubs(:check_response).returns(fetch_guard_allowed)
+ Distillator::FetchService.expects(:internal_fetch).returns(
+ status: :ok,
+ body: "internal",
+ headers: { "Content-Type" => "text/html" },
+ final_url: "https://example.com/final",
+ redirect_chain: nil,
+ wringer: { signals: { network_status: "ok" }, hints: [] }
+ )
+ Distillator::FetchService.expects(:legacy_fetch).never
+
+ response = Distillator::FetchService.fetch(
+ url: @url,
+ mode: :internal,
+ use_wringer: ->(*_) { "wringer://resolved" },
+ safe_wringer_call: ->(&blk) { blk.call }
+ )
+
+ assert_contract(response)
+ assert_equal({ content_type: "text/html" }, response[:headers])
+ assert_equal [], response[:redirect_chain]
+ end
+
+ test "shadow fetch returns canonical response contract" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "legacy"
+ Distillator::FetchRecorder.stubs(:record)
+ Distillator::FetchGuard.stubs(:check_url).returns(fetch_guard_allowed)
+ Distillator::FetchGuard.stubs(:check_response).returns(fetch_guard_allowed)
+ Distillator::FetchService.expects(:legacy_fetch).returns(
+ status: :ok,
+ body: "legacy",
+ headers: { "Content-Type" => "text/html" },
+ final_url: @url,
+ redirect_chain: nil,
+ wringer: { signals: {}, hints: [] }
+ )
+ Distillator::FetchService.expects(:internal_fetch).returns(
+ status: :ok,
+ body: "internal",
+ headers: { content_type: "text/html" },
+ final_url: @url,
+ redirect_chain: [],
+ wringer: { signals: {}, hints: [] }
+ )
+ Distillator::FetchShadowComparator.expects(:compare)
+
+ response = Distillator::FetchService.fetch(
+ url: @url,
+ mode: :shadow,
+ use_wringer: ->(*_) { "wringer://resolved" },
+ safe_wringer_call: ->(&blk) { blk.call }
+ )
+
+ assert_contract(response)
+ assert_equal({ content_type: "text/html" }, response[:headers])
+ assert_equal [], response[:redirect_chain]
+ end
+
+ test "replay fetch returns canonical response contract" do
+ ENV["REPLAY_FETCH"] = "true"
+ ENV["FETCH_SITE"] = @replay_site
+
+ File.write(@replay_path, <<~JSON)
+ {
+ "url": "#{@url}",
+ "recorded_at": "2026-04-28T00:00:00Z",
+ "response": {
+ "status": "ok",
+ "body": "replay",
+ "headers": {"Content-Type": "text/html"},
+ "final_url": "https://example.com/final",
+ "redirect_chain": ["https://example.com/start", "https://example.com/final"],
+ "wringer": {
+ "signals": {
+ "network_status": "ok",
+ "content_type": "html",
+ "redirect_type": "normal",
+ "redirected": true,
+ "final_url": "https://example.com/final"
+ },
+ "hints": []
+ },
+ "duration_ms": 12.3
+ }
+ }
+ JSON
+
+ response = Distillator::FetchService.fetch(url: @url)
+
+ assert_contract(response)
+ assert_equal({ content_type: "text/html" }, response[:headers])
+ assert_equal ["https://example.com/start", "https://example.com/final"], response[:redirect_chain]
+ assert_equal "ok", response.dig(:wringer, :signals, :network_status)
+ assert_equal "html", response.dig(:wringer, :signals, :content_type)
+ assert_equal "normal", response.dig(:wringer, :signals, :redirect_type)
+ assert_equal true, response.dig(:wringer, :signals, :redirected)
+ assert_equal "https://example.com/final", response.dig(:wringer, :signals, :final_url)
+ end
+
+ test "blocked fetch returns canonical response contract" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "legacy"
+ Distillator::FetchRecorder.stubs(:record)
+
+ response = Distillator::FetchService.fetch(
+ url: "http://127.0.0.1/events",
+ mode: :internal,
+ use_wringer: ->(*_) { "wringer://resolved" },
+ safe_wringer_call: ->(&blk) { blk.call }
+ )
+
+ assert_contract(response)
+ assert_equal :abort, response[:status]
+ assert_equal({}, response[:headers])
+ assert_equal [], response[:redirect_chain]
+ assert_equal "blocked_url", response.dig(:wringer, :signals, :native_ineligible_reason)
+ assert_equal "blocked_private_ip", response.dig(:wringer, :signals, :guard_reason)
+ end
+
+ test "abort control result returns canonical response contract" do
+ ENV["DISTILLATOR_FETCH_MODE"] = nil
+ Distillator::FetchRecorder.stubs(:record)
+ payload = ["abort_update", { error_type: "wringer_unreachable", retry: true, cache: false }]
+ client = mock("wringer_client")
+ client.expects(:fetch).with(url: @url).returns(
+ status: :abort,
+ body: payload,
+ wringer: {
+ error_type: "WringerFetchError",
+ retry: true,
+ cache: false,
+ signals: {},
+ hints: []
+ }
+ )
+
+ response = Distillator::FetchService.fetch(url: @url, client: client)
+
+ assert_contract(response)
+ assert_equal :abort, response[:status]
+ assert_equal({}, response[:headers])
+ assert_equal [], response[:redirect_chain]
+ assert_equal "WringerFetchError", response.dig(:wringer, :error_type)
+ end
+
+ test "failed network result returns canonical response contract" do
+ ENV["DISTILLATOR_FETCH_MODE"] = nil
+ Distillator::FetchRecorder.stubs(:record)
+ failed_payload = ["abort_update", { error_type: "WringerFetchError" }]
+ client = mock("wringer_client")
+ client.expects(:fetch).with(url: @url).returns(
+ status: :abort,
+ body: failed_payload,
+ wringer: {
+ error_type: "WringerFetchError",
+ retry: true,
+ cache: false,
+ signals: { network_status: "failed" },
+ hints: ["timeout"]
+ }
+ )
+
+ response = Distillator::FetchService.fetch(url: @url, client: client)
+
+ assert_contract(response)
+ assert_equal :abort, response[:status]
+ assert_equal({}, response[:headers])
+ assert_equal [], response[:redirect_chain]
+ assert_equal "failed", response.dig(:wringer, :signals, :network_status)
+ end
+
+ private
+
+ def assert_contract(response)
+ assert_equal CONTRACT_KEYS.sort, response.keys.sort
+ assert_kind_of Hash, response[:headers]
+ assert_kind_of Array, response[:redirect_chain]
+ assert_kind_of Numeric, response[:duration_ms]
+ end
+
+ def fetch_guard_allowed
+ Distillator::FetchGuard::Result.new(allowed: true)
+ end
+end
diff --git a/test/services/distillator/fetch_rollout_smoke_test.rb b/test/services/distillator/fetch_rollout_smoke_test.rb
new file mode 100644
index 00000000..e3805bbb
--- /dev/null
+++ b/test/services/distillator/fetch_rollout_smoke_test.rb
@@ -0,0 +1,113 @@
+require "test_helper"
+
+class Distillator::FetchRolloutSmokeTest < ActiveSupport::TestCase
+ class CapturingLogger
+ attr_reader :infos, :warnings
+
+ def initialize
+ @infos = []
+ @warnings = []
+ end
+
+ def info(payload)
+ @infos << payload
+ end
+
+ def warn(payload)
+ @warnings << payload
+ end
+ end
+
+ setup do
+ @old_fetch_mode = ENV["DISTILLATOR_FETCH_MODE"]
+ @old_replay_fetch = ENV["REPLAY_FETCH"]
+ ENV["REPLAY_FETCH"] = nil
+ end
+
+ teardown do
+ ENV["DISTILLATOR_FETCH_MODE"] = @old_fetch_mode
+ ENV["REPLAY_FETCH"] = @old_replay_fetch
+ end
+
+ test "shadow mode returns legacy result and emits comparison log without live network" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "legacy"
+ logger = CapturingLogger.new
+ legacy_result = fetch_result(body: "legacy")
+ internal_result = fetch_result(body: "internal")
+
+ Distillator::FetchService.expects(:legacy_fetch).returns(legacy_result)
+ Distillator::FetchService.expects(:guarded_internal_fetch).returns(internal_result)
+
+ result = Distillator::FetchService.fetch(
+ url: "https://example.com/events",
+ mode: :shadow,
+ use_wringer: ->(*_) { "wringer://resolved" },
+ safe_wringer_call: ->(&blk) { blk.call },
+ logger: logger
+ )
+
+ assert_equal "legacy", result[:body]
+ assert_equal(
+ [:body, :duration_ms, :final_url, :headers, :redirect_chain, :status, :wringer],
+ result.keys.sort
+ )
+ events = logger.infos.map { |entry| entry[:event] }
+ assert_includes events, "distillator.fetch.eligibility"
+ assert_includes events, "distillator.fetch_shadow.compare"
+ assert_includes events, "fetch.shadow_compare"
+
+ eligibility_index = events.index("distillator.fetch.eligibility")
+ compare_index = events.index("distillator.fetch_shadow.compare")
+ assert_operator eligibility_index, :<, compare_index
+
+ compare_payload = logger.infos.find { |entry| entry[:event] == "distillator.fetch_shadow.compare" }
+ assert_equal false, compare_payload[:matched]
+ assert_includes compare_payload[:mismatches].map { |item| item[:field] }, :body_hash
+
+ outcome_payload = logger.infos.find { |entry| entry[:event] == "fetch.shadow_compare" }
+ assert_equal "explicit", outcome_payload[:mode_source]
+ end
+
+ test "guard-blocked shadow request skips compare and returns abort" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "legacy"
+ logger = CapturingLogger.new
+
+ Distillator::FetchService.expects(:legacy_fetch).never
+ Distillator::FetchService.expects(:internal_fetch).never
+ Distillator::FetchShadowComparator.expects(:compare).never
+
+ result = Distillator::FetchService.fetch(
+ url: "http://127.0.0.1/events",
+ mode: :shadow,
+ use_wringer: ->(*_) { "wringer://resolved" },
+ safe_wringer_call: ->(&blk) { blk.call },
+ logger: logger
+ )
+
+ assert_equal :abort, result[:status]
+ assert_equal "distillator.fetch_mode.internal_ineligible", logger.infos.first[:event]
+ assert_equal :blocked_url, logger.infos.first[:reason]
+ assert_equal "distillator.fetch_shadow.skipped", logger.infos.second[:event]
+ assert_equal :blocked_url, logger.infos.second[:reason]
+ end
+
+ private
+
+ def fetch_result(body:)
+ {
+ status: :ok,
+ body: body,
+ headers: { content_type: "text/html" },
+ final_url: "https://example.com/events",
+ redirect_chain: ["https://example.com/events"],
+ wringer: {
+ signals: {},
+ hints: [],
+ error_type: nil,
+ received_404: false,
+ system_error: false,
+ unreachable: false
+ }
+ }
+ end
+end
diff --git a/test/services/distillator/fetch_service_test.rb b/test/services/distillator/fetch_service_test.rb
new file mode 100644
index 00000000..22a537e2
--- /dev/null
+++ b/test/services/distillator/fetch_service_test.rb
@@ -0,0 +1,929 @@
+require "test_helper"
+
+class Distillator::FetchServiceTest < ActiveSupport::TestCase
+ class CapturingLogger
+ attr_reader :infos, :warnings
+
+ def initialize
+ @infos = []
+ @warnings = []
+ end
+
+ def info(payload)
+ @infos << payload
+ end
+
+ def warn(payload)
+ @warnings << payload
+ end
+ end
+
+ setup do
+ @old_fetch_mode = ENV["DISTILLATOR_FETCH_MODE"]
+ @old_replay_fetch = ENV["REPLAY_FETCH"]
+ end
+
+ teardown do
+ ENV["DISTILLATOR_FETCH_MODE"] = @old_fetch_mode
+ ENV["REPLAY_FETCH"] = @old_replay_fetch
+ end
+
+ test "fetch defaults to legacy fetch without website context" do
+ ENV["DISTILLATOR_FETCH_MODE"] = nil
+ Distillator::FetchService.expects(:internal_fetch).never
+ Distillator::FetchService.expects(:legacy_fetch).with do |kwargs|
+ assert_equal "https://example.com/events", kwargs[:url]
+ assert_equal false, kwargs[:render_js]
+ assert_equal({}, kwargs[:scrape_options])
+ true
+ end.returns(
+ status: :ok,
+ body: "ok",
+ headers: { content_type: "text/html" },
+ final_url: "https://example.com/events",
+ redirect_chain: ["https://example.com/events"],
+ wringer: { signals: {}, hints: [] }
+ )
+
+ result = Distillator::FetchService.fetch(url: "https://example.com/events")
+
+ assert_equal :ok, result[:status]
+ assert_equal "ok", result[:body]
+ assert_equal(
+ [:body, :duration_ms, :final_url, :headers, :redirect_chain, :status, :wringer],
+ result.keys.sort
+ )
+ end
+
+ test "explicit active alias mode uses the native path when eligible" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "legacy"
+ logger = CapturingLogger.new
+ Distillator::FetchGuard.stubs(:check_url).returns(fetch_guard_allowed)
+ Distillator::FetchGuard.stubs(:check_response).returns(fetch_guard_allowed)
+ Distillator::FetchService.expects(:internal_fetch).with do |kwargs|
+ assert_equal "https://example.com/events", kwargs[:url]
+ assert_equal false, kwargs[:render_js]
+ assert_equal({}, kwargs[:scrape_options])
+ true
+ end.returns(
+ status: :ok,
+ body: "internal",
+ headers: { content_type: "text/html" },
+ final_url: "https://example.com/events",
+ redirect_chain: [],
+ wringer: { signals: {}, hints: [] }
+ )
+ Distillator::FetchService.expects(:legacy_fetch).never
+
+ result = Distillator::FetchService.fetch(
+ url: "https://example.com/events",
+ mode: :internal,
+ use_wringer: ->(*_) { "wringer://resolved" },
+ safe_wringer_call: ->(&blk) { blk.call },
+ logger: logger
+ )
+
+ assert_equal :ok, result[:status]
+ assert_equal "internal", result[:body]
+ assert_equal(
+ {
+ event: "distillator.fetch.eligibility",
+ eligible: true,
+ url: "https://example.com/events",
+ render_js: false,
+ json_post: false,
+ reason: :native_http_get,
+ policy: :native,
+ mode_source: "explicit",
+ forced_legacy: false,
+ scheme: "https",
+ statement_id: nil,
+ source_id: nil,
+ webpage_id: nil,
+ website_id: nil
+ },
+ logger.infos.first
+ )
+ assert_equal(
+ {
+ event: "distillator.fetch.path",
+ path: "native_fetch",
+ url: "https://example.com/events",
+ mode_source: "explicit",
+ statement_id: nil,
+ source_id: nil,
+ webpage_id: nil,
+ website_id: nil
+ },
+ logger.infos.second
+ )
+ assert_equal "fetch.native", logger.infos.last[:event]
+ assert_equal "https%3A%2F%2Fexample.com%2Fevents", logger.infos.last[:uri_key]
+ assert_equal "active", logger.infos.last[:mode]
+ assert_equal "explicit", logger.infos.last[:mode_source]
+ assert_equal "native", logger.infos.last[:fetch_path]
+ assert_nil logger.infos.last[:website_id]
+ assert_equal "html", result.dig(:wringer, :signals, :content_type)
+ assert_equal "none", result.dig(:wringer, :signals, :redirect_type)
+ end
+
+ test "internal fetch enriches redirect metadata for wringer-compatible result shape" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "legacy"
+ Distillator::FetchGuard.stubs(:check_url).returns(fetch_guard_allowed)
+ Distillator::FetchGuard.stubs(:check_response).returns(fetch_guard_allowed)
+ Distillator::FetchService.expects(:internal_fetch).returns(
+ status: :ok,
+ body: "redirected",
+ headers: { content_type: "text/html" },
+ final_url: "https://example.com/final",
+ redirect_chain: ["https://example.com/start", "https://example.com/final"],
+ wringer: { signals: { network_status: "ok" }, hints: [] }
+ )
+
+ result = Distillator::FetchService.fetch(
+ url: "https://example.com/start",
+ mode: :internal,
+ use_wringer: ->(*_) { "wringer://resolved" },
+ safe_wringer_call: ->(&blk) { blk.call }
+ )
+
+ assert_equal "normal", result.dig(:wringer, :signals, :redirect_type)
+ assert_equal true, result.dig(:wringer, :signals, :redirected)
+ assert_equal "https://example.com/final", result.dig(:wringer, :signals, :final_url)
+ end
+
+ test "explicit internal mode uses rendered fetch when render_js is true" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "legacy"
+ logger = CapturingLogger.new
+ Distillator::FetchGuard.stubs(:check_url).returns(fetch_guard_allowed)
+ Distillator::FetchGuard.stubs(:check_response).returns(fetch_guard_allowed)
+ Distillator::FetchService.expects(:internal_fetch).with do |kwargs|
+ assert_equal true, kwargs[:render_js]
+ true
+ end.returns(
+ status: :ok,
+ body: "rendered",
+ headers: { content_type: "text/html" },
+ final_url: "https://example.com/rendered",
+ redirect_chain: ["https://example.com/events", "https://example.com/rendered"],
+ wringer: { signals: { network_status: "ok", renderer: "legacy_phantomjs" }, hints: ["legacy_phantomjs"] },
+ http_code: 200
+ )
+ Distillator::FetchService.expects(:legacy_fetch).never
+
+ result = Distillator::FetchService.fetch(
+ url: "https://example.com/events",
+ mode: :internal,
+ render_js: true,
+ use_wringer: ->(*_) { "wringer://resolved" },
+ safe_wringer_call: ->(&blk) { blk.call },
+ logger: logger
+ )
+
+ assert_equal "rendered", result[:body]
+ assert_equal :rendered_fetch, logger.infos.first[:reason]
+ assert_equal "legacy_phantomjs", result.dig(:wringer, :signals, :renderer)
+ assert_equal "https://example.com/rendered", result[:final_url]
+ assert_equal ["https://example.com/events", "https://example.com/rendered"], result[:redirect_chain]
+ end
+
+ test "explicit internal mode uses native fetch for json_post" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "legacy"
+ logger = CapturingLogger.new
+ Distillator::FetchGuard.stubs(:check_url).returns(fetch_guard_allowed)
+ Distillator::FetchGuard.stubs(:check_response).returns(fetch_guard_allowed)
+ Distillator::FetchService.expects(:internal_fetch).with do |kwargs|
+ assert_equal true, kwargs[:scrape_options][:json_post]
+ true
+ end.returns(
+ status: :ok,
+ body: "{\"ok\":true}",
+ headers: { content_type: "application/json" },
+ final_url: "https://example.com/events",
+ redirect_chain: ["https://example.com/events"],
+ wringer: { signals: { content_type: "json" }, hints: ["json_detected"] }
+ )
+ Distillator::FetchService.expects(:legacy_fetch).never
+
+ result = Distillator::FetchService.fetch(
+ url: "https://example.com/events",
+ mode: :internal,
+ scrape_options: { json_post: true },
+ use_wringer: ->(*_) { "wringer://resolved" },
+ safe_wringer_call: ->(&blk) { blk.call },
+ logger: logger
+ )
+
+ assert_equal "{\"ok\":true}", result[:body]
+ assert_equal :native_http_post, logger.infos.first[:reason]
+ assert_equal "json", result.dig(:wringer, :signals, :content_type)
+ assert_equal "fetch.native", logger.infos.last[:event]
+ end
+
+ test "internal fetch classifies queue it body through yaml issue metadata" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "legacy"
+ Distillator::FetchGuard.stubs(:check_url).returns(fetch_guard_allowed)
+ Distillator::FetchGuard.stubs(:check_response).returns(fetch_guard_allowed)
+ Distillator::FetchService.expects(:internal_fetch).returns(
+ status: :ok,
+ body: "Queue-it Please wait while we redirect you",
+ raw_body: "Queue-it Please wait while we redirect you",
+ headers: { content_type: "text/html" },
+ final_url: "https://example.com/events",
+ redirect_chain: [],
+ wringer: { signals: { network_status: "ok" }, hints: [] },
+ http_code: 200
+ )
+
+ result = Distillator::FetchService.fetch(
+ url: "https://example.com/events",
+ mode: :internal,
+ use_wringer: ->(*_) { "wringer://resolved" },
+ safe_wringer_call: ->(&blk) { blk.call }
+ )
+
+ assert_equal "queue_it", result.dig(:wringer, :signals, :primary_issue_key)
+ assert_equal "system_queue", result.dig(:wringer, :signals, :primary_issue_error_code)
+ assert_equal "warning", result.dig(:wringer, :signals, :primary_issue_severity)
+ assert_equal "anti_bot", result.dig(:wringer, :signals, :primary_issue_category)
+ assert_includes result.dig(:wringer, :hints), "queue_it"
+ assert_equal "abort_update", result.dig(:wringer, :policy_action)
+ end
+
+ test "rendered redirect to listing keeps transport success but marks content failure" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "legacy"
+ Distillator::FetchGuard.stubs(:check_url).returns(fetch_guard_allowed)
+ Distillator::FetchGuard.stubs(:check_response).returns(fetch_guard_allowed)
+ Distillator::FetchService.expects(:internal_fetch).returns(
+ status: :ok,
+ body: "Recherche par titre ",
+ raw_body: "Recherche par titre ",
+ headers: { content_type: "text/html" },
+ final_url: "https://www.ovation.ca/Search/Title/",
+ redirect_chain: ["https://www.ovation.ca/event", "https://www.ovation.ca/Search/Title/"],
+ wringer: {
+ signals: {
+ network_status: "ok",
+ renderer: "legacy_phantomjs",
+ primary_issue_key: "redirect_to_listing",
+ primary_issue_severity: "failed",
+ blocking_issue_key: "redirect_to_listing"
+ },
+ hints: ["legacy_phantomjs", "redirect_to_listing"],
+ policy_action: "abort_update"
+ },
+ http_code: 200
+ )
+
+ result = Distillator::FetchService.fetch(
+ url: "https://www.ovation.ca/event",
+ mode: :internal,
+ render_js: true
+ )
+
+ assert_equal true, result.dig(:wringer, :signals, :transport_success)
+ assert_equal false, result.dig(:wringer, :signals, :content_success)
+ assert_equal "redirect_to_listing", result.dig(:wringer, :signals, :blocking_issue_key)
+ assert_equal "abort_update", result.dig(:wringer, :policy_action)
+ end
+
+ test "non 2xx fetch remains transport and content failure" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "legacy"
+ Distillator::FetchGuard.stubs(:check_url).returns(fetch_guard_allowed)
+ Distillator::FetchGuard.stubs(:check_response).returns(fetch_guard_allowed)
+ Distillator::FetchService.expects(:internal_fetch).returns(
+ status: :abort,
+ body: ["abort_update", { error_type: "http_server_error" }],
+ raw_body: "Server Error ",
+ headers: { content_type: "text/html" },
+ final_url: "https://example.com/events",
+ redirect_chain: [],
+ wringer: {
+ signals: {
+ network_status: "ok",
+ primary_issue_key: "http_5xx",
+ blocking_issue_key: "http_5xx"
+ },
+ hints: ["http_server_error"]
+ },
+ http_code: 500
+ )
+
+ result = Distillator::FetchService.fetch(
+ url: "https://example.com/events",
+ mode: :internal
+ )
+
+ assert_equal false, result.dig(:wringer, :signals, :transport_success)
+ assert_equal false, result.dig(:wringer, :signals, :content_success)
+ assert_equal "http_5xx", result.dig(:wringer, :signals, :blocking_issue_key)
+ end
+
+ test "explicit internal mode aborts for unsupported scheme before any network call" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "legacy"
+ logger = CapturingLogger.new
+ Distillator::FetchService.expects(:internal_fetch).never
+ Distillator::FetchService.expects(:legacy_fetch).never
+
+ result = Distillator::FetchService.fetch(
+ url: "ftp://example.com/events",
+ mode: :internal,
+ use_wringer: ->(*_) { "wringer://resolved" },
+ safe_wringer_call: ->(&blk) { blk.call },
+ logger: logger
+ )
+
+ assert_equal :abort, result[:status]
+ assert_equal :unsupported_scheme, logger.infos.first[:reason]
+ assert_equal "unsupported_scheme", result.dig(:wringer, :signals, :native_ineligible_reason)
+ end
+
+ test "explicit internal mode falls back to legacy for forced legacy option" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "legacy"
+ logger = CapturingLogger.new
+ Distillator::FetchService.expects(:internal_fetch).never
+ Distillator::FetchService.expects(:legacy_fetch).returns(
+ status: :ok,
+ body: "legacy",
+ wringer: { signals: {}, hints: [] }
+ )
+
+ result = Distillator::FetchService.fetch(
+ url: "https://example.com/events",
+ mode: :internal,
+ scrape_options: { force_legacy: true },
+ use_wringer: ->(*_) { "wringer://resolved" },
+ safe_wringer_call: ->(&blk) { blk.call },
+ logger: logger
+ )
+
+ assert_equal "legacy", result[:body]
+ assert_equal :forced_legacy, logger.infos.first[:reason]
+ end
+
+ test "ineligible log includes machine readable fields" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "legacy"
+ logger = CapturingLogger.new
+ Distillator::FetchService.expects(:internal_fetch).never
+ Distillator::FetchService.expects(:legacy_fetch).returns(
+ status: :ok,
+ body: "legacy",
+ wringer: { signals: {}, hints: [] }
+ )
+
+ Distillator::FetchService.fetch(
+ url: "https://example.com/events",
+ mode: :internal,
+ scrape_options: { force_legacy: true, json_post: true },
+ use_wringer: ->(*_) { "wringer://resolved" },
+ safe_wringer_call: ->(&blk) { blk.call },
+ logger: logger
+ )
+
+ assert_equal "distillator.fetch_mode.internal_ineligible", logger.infos.first[:event]
+ assert_equal "https://example.com/events", logger.infos.first[:url]
+ assert_equal false, logger.infos.first[:render_js]
+ assert_equal true, logger.infos.first[:json_post]
+ assert_equal :forced_legacy, logger.infos.first[:reason]
+ assert_equal :explicit_legacy_fallback, logger.infos.first[:policy]
+ assert_equal "explicit", logger.infos.first[:mode_source]
+ assert_equal "distillator.fetch.legacy_fallback", logger.warnings.first[:event]
+ assert_equal true, logger.warnings.first[:deprecated]
+ assert_equal "explicit", logger.warnings.first[:mode_source]
+ end
+
+ test "shadow mode skips comparison when native candidate is forced legacy" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "legacy"
+ logger = CapturingLogger.new
+ Distillator::FetchService.expects(:internal_fetch).never
+ Distillator::FetchShadowComparator.expects(:compare).never
+ Distillator::FetchService.expects(:legacy_fetch).returns(
+ status: :ok,
+ body: "legacy",
+ wringer: { signals: {}, hints: [] }
+ )
+
+ result = Distillator::FetchService.fetch(
+ url: "https://example.com/events",
+ mode: :shadow,
+ scrape_options: { json_post: true, force_legacy: true },
+ use_wringer: ->(*_) { "wringer://resolved" },
+ safe_wringer_call: ->(&blk) { blk.call },
+ logger: logger
+ )
+
+ assert_equal "legacy", result[:body]
+ assert_equal :forced_legacy, logger.infos.first[:reason]
+ assert_equal "distillator.fetch_shadow.skipped", logger.infos.second[:event]
+ assert_equal :forced_legacy, logger.infos.second[:reason]
+ assert_equal "forced_legacy", result.dig(:wringer, :signals, :native_ineligible_reason)
+ end
+
+ test "shadow mode aborts blocked url before any network call and records skipped comparison" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "legacy"
+ logger = CapturingLogger.new
+ Distillator::FetchService.expects(:legacy_fetch).never
+ Distillator::FetchService.expects(:internal_fetch).never
+ Distillator::FetchShadowComparator.expects(:compare).never
+
+ result = Distillator::FetchService.fetch(
+ url: "http://127.0.0.1/events",
+ mode: :shadow,
+ use_wringer: ->(*_) { "wringer://resolved" },
+ safe_wringer_call: ->(&blk) { blk.call },
+ logger: logger
+ )
+
+ assert_equal :abort, result[:status]
+ assert_equal :blocked_url, logger.infos.first[:reason]
+ assert_equal "distillator.fetch_shadow.skipped", logger.infos.second[:event]
+ assert_equal :blocked_url, logger.infos.second[:reason]
+ end
+
+ test "shadow mode returns legacy result and compares internal result" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "legacy"
+ logger = CapturingLogger.new
+ Distillator::FetchGuard.stubs(:check_url).returns(fetch_guard_allowed)
+ Distillator::FetchGuard.stubs(:check_response).returns(fetch_guard_allowed)
+ legacy = {
+ status: :ok,
+ body: "legacy",
+ headers: {},
+ final_url: nil,
+ redirect_chain: [],
+ wringer: { signals: {}, hints: [] }
+ }
+ internal = legacy.merge(body: "internal")
+
+ Distillator::FetchService.expects(:legacy_fetch).returns(legacy)
+ Distillator::FetchService.expects(:internal_fetch).returns(internal)
+ Distillator::FetchShadowComparator.expects(:compare).with do |kwargs|
+ assert_equal legacy, kwargs[:legacy]
+ assert_equal internal, kwargs[:internal]
+ true
+ end
+
+ result = Distillator::FetchService.fetch(
+ url: "https://example.com/events",
+ mode: :shadow,
+ use_wringer: ->(*_) { "wringer://resolved" },
+ safe_wringer_call: ->(&blk) { blk.call },
+ logger: logger,
+ log_context: { statement_id: 11, source_id: 22, webpage_id: 33, website_id: 44 }
+ )
+
+ assert_equal "legacy", result[:body]
+ assert_equal "fetch.shadow_compare", logger.infos.last[:event]
+ assert_equal "explicit", logger.infos.last[:mode_source]
+ assert_equal "https%3A%2F%2Fexample.com%2Fevents", logger.infos.last[:uri_key]
+ assert_equal 11, logger.infos.last[:statement_id]
+ assert_equal 22, logger.infos.last[:source_id]
+ assert_equal 33, logger.infos.last[:webpage_id]
+ assert_equal 44, logger.infos.last[:website_id]
+ end
+
+ test "shadow mode swallows internal errors and returns legacy result" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "legacy"
+ Distillator::FetchGuard.stubs(:check_url).returns(fetch_guard_allowed)
+ legacy = {
+ status: :ok,
+ body: "legacy",
+ headers: {},
+ final_url: nil,
+ redirect_chain: [],
+ wringer: { signals: {}, hints: [] }
+ }
+
+ Distillator::FetchService.expects(:legacy_fetch).returns(legacy)
+ Distillator::FetchService.expects(:internal_fetch).raises(StandardError, "internal boom")
+
+ result = Distillator::FetchService.fetch(
+ url: "https://example.com/events",
+ mode: :shadow,
+ use_wringer: ->(*_) { "wringer://resolved" },
+ safe_wringer_call: ->(&blk) { blk.call }
+ )
+
+ assert_equal "legacy", result[:body]
+ end
+
+ test "internal mode returns abort for blocked URL" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "legacy"
+ logger = CapturingLogger.new
+ Distillator::FetchService.expects(:internal_fetch).never
+ Distillator::FetchService.expects(:legacy_fetch).never
+
+ result = Distillator::FetchService.fetch(
+ url: "http://127.0.0.1/events",
+ mode: :internal,
+ use_wringer: ->(*_) { "wringer://resolved" },
+ safe_wringer_call: ->(&blk) { blk.call },
+ logger: logger
+ )
+
+ assert_equal :abort, result[:status]
+ assert_equal "abort_update", result[:body].first
+ assert_equal "DistillatorFetchBlocked", result[:body].last[:error_type]
+ assert_equal "distillator_fetch_guard", result[:body].last[:source]
+ assert_equal false, result[:body].last[:retry]
+ assert_equal false, result[:body].last[:cache]
+ assert_equal "url", result[:body].last[:step]
+ assert_equal "blocked_url", result[:body].last[:signals][:native_ineligible_reason]
+ assert_equal "blocked_private_ip", result[:body].last[:signals][:guard_reason]
+ assert_equal({}, result[:headers])
+ assert_equal "http://127.0.0.1/events", result[:final_url]
+ assert_equal [], result[:redirect_chain]
+ assert_equal "DistillatorFetchBlocked", result.dig(:wringer, :error_type)
+ assert_equal "distillator_fetch_guard", result.dig(:wringer, :source)
+ assert_equal "blocked_url", result.dig(:wringer, :signals, :native_ineligible_reason)
+ assert_equal "blocked_private_ip", result.dig(:wringer, :signals, :guard_reason)
+ assert_equal "blocked", result.dig(:wringer, :signals, :network_status)
+ assert_includes result.dig(:wringer, :hints), "blocked_url"
+ assert_includes result.dig(:wringer, :hints), "blocked_private_ip"
+ assert_includes result.dig(:wringer, :hints), "blocked"
+ assert_equal "fetch.abort", logger.infos.last[:event]
+ end
+
+ test "shadow mode does not call legacy fetch when url is blocked" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "legacy"
+ Distillator::FetchService.expects(:legacy_fetch).never
+ Distillator::FetchService.expects(:internal_fetch).never
+ Distillator::FetchShadowComparator.expects(:compare).never
+
+ result = Distillator::FetchService.fetch(
+ url: "http://127.0.0.1/events",
+ mode: :shadow,
+ use_wringer: ->(*_) { "wringer://resolved" },
+ safe_wringer_call: ->(&blk) { blk.call }
+ )
+
+ assert_equal :abort, result[:status]
+ end
+
+ test "replay mode bypasses fetch mode selection" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "internal"
+ ENV["REPLAY_FETCH"] = "true"
+ Distillator::FetchGuard.expects(:check_url).never
+ Distillator::FetchReplay.expects(:load).with(url: "https://example.com/events").returns(
+ status: :ok,
+ body: "replay",
+ headers: {},
+ final_url: nil,
+ redirect_chain: [],
+ wringer: {},
+ duration_ms: 0
+ )
+ Distillator::FetchService.expects(:internal_fetch).never
+ Distillator::FetchService.expects(:legacy_fetch).never
+
+ result = Distillator::FetchService.fetch(url: "https://example.com/events")
+
+ assert_equal "replay", result[:body]
+ end
+
+ test "returns body and wringer metadata on success" do
+ client = mock("wringer_client")
+ client.expects(:fetch).with(url: "https://example.com/events").returns(
+ status: :ok,
+ body: "ok",
+ headers: { content_type: "text/html" },
+ final_url: "https://example.com/final",
+ redirect_chain: ["https://example.com/start", "https://example.com/final"],
+ wringer: { signals: { network_status: "ok" }, hints: [] }
+ )
+
+ Dsl::Support::WringerClient.expects(:new).with do |ctx|
+ assert_instance_of Mechanize, ctx[:agent]
+ assert_equal false, ctx[:render_js]
+ assert_equal({ force_legacy: true }, ctx[:scrape_options])
+ assert_equal :use_wringer, ctx[:use_wringer].name
+ assert_equal :safe_wringer_call, ctx[:safe_wringer_call].name
+ assert_equal Rails.logger, ctx[:logger]
+ true
+ end.returns(client)
+
+ result = Distillator::FetchService.fetch(
+ url: "https://example.com/events",
+ mode: :internal,
+ scrape_options: { force_legacy: true }
+ )
+
+ assert_equal :ok, result[:status]
+ assert_equal "ok", result[:body]
+ assert_equal({ content_type: "text/html" }, result[:headers])
+ assert_equal "https://example.com/final", result[:final_url]
+ assert_equal ["https://example.com/start", "https://example.com/final"], result[:redirect_chain]
+ assert_equal "ok", result.dig(:wringer, :signals, :network_status)
+ assert_equal "html", result.dig(:wringer, :signals, :content_type)
+ assert_equal "normal", result.dig(:wringer, :signals, :redirect_type)
+ assert_equal true, result.dig(:wringer, :signals, :redirected)
+ assert_equal "https://example.com/final", result.dig(:wringer, :signals, :final_url)
+ assert_equal ["forced_legacy"], result.dig(:wringer, :hints)
+ end
+
+ test "returns abort_update payload unchanged on failure" do
+ payload = ["abort_update", { error_type: "system_cloudflare", retry: true, cache: false }]
+ client = mock("wringer_client")
+ client.expects(:fetch).with(url: "https://example.com/events").returns(
+ status: :abort,
+ body: payload,
+ wringer: { error_type: "system_cloudflare", retry: true, cache: false, signals: {}, hints: [] }
+ )
+
+ Dsl::Support::WringerClient.stubs(:new).returns(client)
+
+ result = Distillator::FetchService.fetch(
+ url: "https://example.com/events",
+ mode: :internal,
+ scrape_options: { force_legacy: true }
+ )
+
+ assert_equal :abort, result[:status]
+ assert_equal payload, result[:body]
+ end
+
+ test "preserves wringer signals and hints" do
+ wringer = {
+ error_type: "system_queue",
+ retry: true,
+ cache: false,
+ signals: { network_status: "failed", content_type: "html" },
+ hints: ["timeout", "empty_body"]
+ }
+ client = mock("wringer_client")
+ client.expects(:fetch).with(url: "https://example.com/events").returns(
+ status: :abort,
+ body: ["abort_update", { error_type: "system_queue" }],
+ wringer: wringer
+ )
+
+ Dsl::Support::WringerClient.stubs(:new).returns(client)
+
+ result = Distillator::FetchService.fetch(
+ url: "https://example.com/events",
+ mode: :internal,
+ scrape_options: { force_legacy: true }
+ )
+
+ assert_equal "failed", result.dig(:wringer, :signals, :network_status)
+ assert_equal "html", result.dig(:wringer, :signals, :content_type)
+ assert_equal "none", result.dig(:wringer, :signals, :redirect_type)
+ assert_equal false, result.dig(:wringer, :signals, :redirected)
+ assert_equal wringer[:hints] + ["forced_legacy"], result.dig(:wringer, :hints)
+ end
+
+ test "includes duration_ms" do
+ client = mock("wringer_client")
+ client.expects(:fetch).with(url: "https://example.com/events").returns(
+ status: :ok,
+ body: "ok",
+ wringer: { signals: {}, hints: [] }
+ )
+
+ Dsl::Support::WringerClient.stubs(:new).returns(client)
+
+ result = Distillator::FetchService.fetch(
+ url: "https://example.com/events",
+ mode: :internal,
+ scrape_options: { force_legacy: true }
+ )
+
+ assert result.key?(:duration_ms)
+ assert_kind_of Numeric, result[:duration_ms]
+ assert_operator result[:duration_ms], :>=, 0
+ end
+
+ test "records fetch response passively without changing returned value" do
+ Distillator::FetchService.expects(:internal_fetch).never
+ Distillator::FetchService.expects(:legacy_fetch).returns(
+ status: :ok,
+ body: "ok",
+ wringer: { signals: {}, hints: [] }
+ )
+ Distillator::FetchRecorder.expects(:record).with do |kwargs|
+ assert_equal "https://example.com/events", kwargs[:url]
+ assert_equal :ok, kwargs[:response][:status]
+ assert_equal "ok", kwargs[:response][:body]
+ assert_equal({}, kwargs[:response][:headers])
+ assert_nil kwargs[:response][:final_url]
+ assert_equal [], kwargs[:response][:redirect_chain]
+ assert_equal "ok", kwargs[:response].dig(:wringer, :signals, :network_status)
+ assert_equal "html", kwargs[:response].dig(:wringer, :signals, :content_type)
+ assert_equal "none", kwargs[:response].dig(:wringer, :signals, :redirect_type)
+ assert_equal false, kwargs[:response].dig(:wringer, :signals, :redirected)
+ assert_equal ["forced_legacy"], kwargs[:response].dig(:wringer, :hints)
+ assert_kind_of Numeric, kwargs[:response][:duration_ms]
+ true
+ end
+
+ result = Distillator::FetchService.fetch(
+ url: "https://example.com/events",
+ mode: :internal,
+ scrape_options: { force_legacy: true }
+ )
+
+ assert_equal :ok, result[:status]
+ assert_equal "ok", result[:body]
+ assert_equal({}, result[:headers])
+ assert_nil result[:final_url]
+ assert_equal [], result[:redirect_chain]
+ assert_equal "ok", result.dig(:wringer, :signals, :network_status)
+ assert_equal "html", result.dig(:wringer, :signals, :content_type)
+ assert_equal "none", result.dig(:wringer, :signals, :redirect_type)
+ assert_equal false, result.dig(:wringer, :signals, :redirected)
+ assert_equal ["forced_legacy"], result.dig(:wringer, :hints)
+ assert_kind_of Numeric, result[:duration_ms]
+ end
+
+ test "internal_fetch exposes response metadata" do
+ response = Struct.new(:code, :body, :uri, :response).new(
+ 200,
+ "ok",
+ URI("https://example.com/final"),
+ { "Content-Type" => "text/html" }
+ )
+ history_entry = Struct.new(:uri).new(URI("https://example.com/final"))
+ agent = mock("agent")
+ agent.expects(:get).with("https://example.com/events").returns(response)
+ agent.stubs(:history).returns([history_entry])
+
+ result = Distillator::FetchService.send(
+ :internal_fetch,
+ url: "https://example.com/events",
+ render_js: false,
+ scrape_options: {},
+ agent: agent
+ )
+
+ assert_equal({ content_type: "text/html" }, result[:headers])
+ assert_equal "https://example.com/final", result[:final_url]
+ assert_equal ["https://example.com/final"], result[:redirect_chain]
+ end
+
+ test "internal mode ignores Wringer helper callbacks when using NativeFetch" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "legacy"
+ Distillator::FetchGuard.stubs(:check_url).returns(fetch_guard_allowed)
+ Distillator::FetchGuard.stubs(:check_response).returns(fetch_guard_allowed)
+ ApplicationController.expects(:helpers).never
+
+ response = Struct.new(:code, :body, :uri, :response).new(
+ 200,
+ "native",
+ URI("https://example.com/final"),
+ { "Content-Type" => "text/html" }
+ )
+ history_entry = Struct.new(:uri).new(URI("https://example.com/final"))
+ agent = mock("agent")
+ agent.expects(:get).with("https://example.com/events").returns(response)
+ agent.stubs(:history).returns([history_entry])
+
+ result = Distillator::FetchService.fetch(
+ url: "https://example.com/events",
+ mode: :internal,
+ agent: agent,
+ use_wringer: ->(*_) { flunk "use_wringer should not be called by NativeFetch" },
+ safe_wringer_call: ->(*_) { flunk "safe_wringer_call should not be called by NativeFetch" }
+ )
+
+ assert_equal :ok, result[:status]
+ assert_equal "native", result[:body]
+ assert_equal({ content_type: "text/html" }, result[:headers])
+ assert_equal "https://example.com/final", result[:final_url]
+ assert_equal ["https://example.com/final"], result[:redirect_chain]
+ end
+
+ test "internal mode remains separate from FetchCacheStore because websites wring owns cache compatibility" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "legacy"
+ Distillator::FetchGuard.stubs(:check_url).returns(fetch_guard_allowed)
+ Distillator::FetchGuard.stubs(:check_response).returns(fetch_guard_allowed)
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ response = Struct.new(:code, :body, :uri, :response).new(
+ 200,
+ "internal",
+ URI("https://example.com/events"),
+ { "Content-Type" => "text/html" }
+ )
+ agent = mock("agent")
+ agent.expects(:get).with("https://example.com/events").returns(response)
+ agent.stubs(:history).returns([])
+
+ result = Distillator::FetchService.fetch(
+ url: "https://example.com/events",
+ mode: :internal,
+ agent: agent,
+ use_wringer: ->(*_) { flunk "use_wringer should not be called by NativeFetch" },
+ safe_wringer_call: ->(*_) { flunk "safe_wringer_call should not be called by NativeFetch" }
+ )
+
+ assert_equal :ok, result[:status]
+ assert_equal "internal", result[:body]
+ assert_equal(
+ [:body, :duration_ms, :final_url, :headers, :redirect_chain, :status, :wringer],
+ result.keys.sort
+ )
+ end
+
+ test "active website uses the native condenser path even when global mode is legacy" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "legacy"
+ logger = CapturingLogger.new
+ website = websites(:one)
+ website.update!(distillator_mode: "active")
+
+ Distillator::FetchGuard.stubs(:check_url).returns(fetch_guard_allowed)
+ Distillator::FetchGuard.stubs(:check_response).returns(fetch_guard_allowed)
+ Distillator::FetchService.expects(:internal_fetch).returns(
+ status: :ok,
+ body: "internal",
+ headers: { content_type: "text/html" },
+ final_url: "https://example.com/events",
+ redirect_chain: [],
+ wringer: { signals: {}, hints: [] }
+ )
+ Distillator::FetchService.expects(:legacy_fetch).never
+
+ result = Distillator::FetchService.fetch(
+ url: "https://example.com/events",
+ website: website,
+ log_context: { website_id: website.id },
+ logger: logger
+ )
+
+ assert_equal "internal", result[:body]
+ assert_equal "active", logger.infos.last[:mode]
+ assert_equal "website", logger.infos.last[:mode_source]
+ assert_equal website.id, logger.infos.first[:website_id]
+ assert_equal website.id, logger.infos.second[:website_id]
+ assert_equal website.id, logger.infos.last[:website_id]
+ end
+
+ test "shadow website returns legacy result and records Distillator comparison" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "legacy"
+ logger = CapturingLogger.new
+ website = websites(:one)
+ website.update!(distillator_mode: "shadow")
+
+ Distillator::FetchGuard.stubs(:check_url).returns(fetch_guard_allowed)
+ Distillator::FetchGuard.stubs(:check_response).returns(fetch_guard_allowed)
+ Distillator::FetchService.expects(:legacy_fetch).returns(
+ status: :ok,
+ body: "legacy",
+ wringer: { signals: {}, hints: [] }
+ )
+ Distillator::FetchService.expects(:internal_fetch).returns(
+ status: :ok,
+ body: "internal",
+ headers: { content_type: "text/html" },
+ final_url: "https://example.com/events",
+ redirect_chain: [],
+ wringer: { signals: {}, hints: [] }
+ )
+ Distillator::FetchShadowComparator.expects(:compare).once
+
+ result = Distillator::FetchService.fetch(
+ url: "https://example.com/events",
+ website: website,
+ log_context: { statement_id: 11, source_id: 22, webpage_id: 33, website_id: website.id },
+ logger: logger
+ )
+
+ assert_equal "legacy", result[:body]
+ assert_equal "shadow", logger.infos.last[:mode]
+ assert_equal "fetch.shadow_compare", logger.infos.last[:event]
+ assert_equal "website", logger.infos.last[:mode_source]
+ assert_equal 11, logger.infos.last[:statement_id]
+ assert_equal 22, logger.infos.last[:source_id]
+ assert_equal 33, logger.infos.last[:webpage_id]
+ assert_equal website.id, logger.infos.last[:website_id]
+ end
+
+ test "legacy website preserves current behavior when global mode is internal" do
+ ENV["DISTILLATOR_FETCH_MODE"] = "internal"
+ logger = CapturingLogger.new
+ website = websites(:one)
+ website.update!(distillator_mode: "legacy")
+
+ Distillator::FetchService.expects(:internal_fetch).never
+ Distillator::FetchService.expects(:legacy_fetch).returns(
+ status: :ok,
+ body: "legacy",
+ wringer: { signals: {}, hints: [] }
+ )
+
+ result = Distillator::FetchService.fetch(
+ url: "https://example.com/events",
+ website: website,
+ logger: logger
+ )
+
+ assert_equal "legacy", result[:body]
+ assert_equal "legacy", logger.infos.last[:mode]
+ assert_equal "website", logger.infos.last[:mode_source]
+ end
+
+ private
+
+ def fetch_guard_allowed
+ Distillator::FetchGuard::Result.new(allowed: true)
+ end
+end
diff --git a/test/services/distillator/fetch_shadow_comparator_test.rb b/test/services/distillator/fetch_shadow_comparator_test.rb
new file mode 100644
index 00000000..b8cc42f8
--- /dev/null
+++ b/test/services/distillator/fetch_shadow_comparator_test.rb
@@ -0,0 +1,86 @@
+require "test_helper"
+
+class Distillator::FetchShadowComparatorTest < ActiveSupport::TestCase
+ class CapturingLogger
+ attr_reader :infos, :warnings
+
+ def initialize
+ @infos = []
+ @warnings = []
+ end
+
+ def info(payload)
+ @infos << payload
+ end
+
+ def warn(payload)
+ @warnings << payload
+ end
+ end
+
+ test "logs structured mismatch without full body" do
+ logger = CapturingLogger.new
+ legacy = fetch_response(body: "legacy", final_url: "https://example.com/legacy")
+ internal = fetch_response(body: "internal", final_url: "https://example.com/internal")
+
+ mismatches = Distillator::FetchShadowComparator.compare(
+ url: "https://example.com/events",
+ legacy: legacy,
+ internal: internal,
+ logger: logger
+ )
+
+ assert_equal false, logger.infos.first[:matched]
+ assert_includes mismatches.map { |item| item[:field] }, :body_hash
+ assert_includes mismatches.map { |item| item[:field] }, :final_url
+ refute_includes logger.infos.first.inspect, "legacy"
+ refute_includes logger.infos.first.inspect, "internal"
+ end
+
+ test "returns empty mismatches for equivalent comparison fields" do
+ logger = CapturingLogger.new
+ response = fetch_response
+
+ mismatches = Distillator::FetchShadowComparator.compare(
+ url: "https://example.com/events",
+ legacy: response,
+ internal: response,
+ logger: logger
+ )
+
+ assert_equal [], mismatches
+ assert_equal true, logger.infos.first[:matched]
+ end
+
+ test "never raises comparator errors to caller" do
+ logger = CapturingLogger.new
+ broken = Object.new
+ broken.define_singleton_method(:[]) { |_key| raise "boom" }
+
+ assert_nil Distillator::FetchShadowComparator.compare(
+ url: "https://example.com/events",
+ legacy: broken,
+ internal: fetch_response,
+ logger: logger
+ )
+ assert_equal "distillator.fetch_shadow.compare_error", logger.warnings.first[:event]
+ end
+
+ private
+
+ def fetch_response(body: "ok", final_url: "https://example.com/final")
+ {
+ status: :ok,
+ body: body,
+ headers: { content_type: "text/html" },
+ final_url: final_url,
+ redirect_chain: ["https://example.com/start", final_url],
+ wringer: {
+ error_type: nil,
+ received_404: false,
+ system_error: false,
+ unreachable: false
+ }
+ }
+ end
+end
diff --git a/test/services/distillator/fixture_helpers_test.rb b/test/services/distillator/fixture_helpers_test.rb
new file mode 100644
index 00000000..82932595
--- /dev/null
+++ b/test/services/distillator/fixture_helpers_test.rb
@@ -0,0 +1,13 @@
+require "test_helper"
+require_relative "../../support/distillator_fixture_helpers"
+
+class Distillator::FixtureHelpersTest < ActiveSupport::TestCase
+ include DistillatorFixtureHelpers
+
+ test "fixture helper exposes representative migration categories" do
+ assert_equal 10, distillator_fixture_categories.length
+ assert_equal true, File.exist?(distillator_fixture_path(:simple_static_html_title))
+ assert_equal true, File.exist?(distillator_fixture_path(:json_post_legacy_only))
+ assert_equal true, File.exist?(distillator_fixture_path(:render_js_legacy_only))
+ end
+end
diff --git a/test/services/distillator/graph_diff_test.rb b/test/services/distillator/graph_diff_test.rb
new file mode 100644
index 00000000..77b1fe0f
--- /dev/null
+++ b/test/services/distillator/graph_diff_test.rb
@@ -0,0 +1,37 @@
+require "test_helper"
+
+class Distillator::GraphDiffTest < ActiveSupport::TestCase
+ test "returns deterministic added and removed triples" do
+ expected = RDF::Graph.new
+ expected << [RDF::URI("http://example.org/event"), RDF::Vocab::SCHEMA.name, RDF::Literal("Old Title")]
+ expected << [RDF::URI("http://example.org/event"), RDF::Vocab::SCHEMA.location, RDF::URI("http://example.org/place")]
+
+ actual = RDF::Graph.new
+ actual << [RDF::URI("http://example.org/event"), RDF::Vocab::SCHEMA.name, RDF::Literal("New Title")]
+ actual << [RDF::URI("http://example.org/event"), RDF::Vocab::SCHEMA.location, RDF::URI("http://example.org/virtual-place")]
+
+ diff = Distillator::GraphDiff.call(expected_graph: expected, actual_graph: actual)
+
+ assert_equal 2, diff.added_count
+ assert_equal 2, diff.removed_count
+ assert_equal 0, diff.same_count
+ assert_equal(
+ [{
+ subject: "",
+ predicate: "",
+ expected: ['"Old Title"'],
+ actual: ['"New Title"']
+ }],
+ diff.changed_literal_values
+ )
+ assert_equal(
+ [{
+ subject: "",
+ predicate: "",
+ expected: [""],
+ actual: [""]
+ }],
+ diff.changed_uri_objects
+ )
+ end
+end
diff --git a/test/services/distillator/html_absolutizer_test.rb b/test/services/distillator/html_absolutizer_test.rb
new file mode 100644
index 00000000..9b9ec39b
--- /dev/null
+++ b/test/services/distillator/html_absolutizer_test.rb
@@ -0,0 +1,38 @@
+require "test_helper"
+
+class Distillator::HtmlAbsolutizerTest < ActiveSupport::TestCase
+ test "call rewrites root relative src" do
+ html = ' '
+
+ rewritten = Distillator::HtmlAbsolutizer.call(html: html, base_url: "https://example.com/artist/")
+
+ assert_includes rewritten, 'src="https://example.com/image.png"'
+ end
+
+ test "call leaves absolute urls unchanged" do
+ html = ' '
+
+ rewritten = Distillator::HtmlAbsolutizer.call(html: html, base_url: "https://example.com/artist/")
+
+ assert_includes rewritten, 'src="http://another.com/image.png"'
+ end
+
+ test "call rewrites parent relative paths and preserves sibling attributes" do
+ html = ' '
+
+ rewritten = Distillator::HtmlAbsolutizer.call(html: html, base_url: "https://example.com/artist/")
+
+ assert_includes rewritten, 'src="https://example.com/image.png"'
+ assert_includes rewritten, 'width="100px"'
+ end
+
+ test "call preserves malformed paths without raising" do
+ html = 'path '
+
+ rewritten = Distillator::HtmlAbsolutizer.call(html: html, base_url: "https://example.com/artist/")
+
+ assert_includes rewritten, 'src="not a path"'
+ assert_includes rewritten, 'href="https://example.com/path"'
+ assert_includes rewritten, 'width="100px"'
+ end
+end
diff --git a/test/services/distillator/html_rewriter_test.rb b/test/services/distillator/html_rewriter_test.rb
new file mode 100644
index 00000000..c35a923b
--- /dev/null
+++ b/test/services/distillator/html_rewriter_test.rb
@@ -0,0 +1,105 @@
+require "test_helper"
+
+class Distillator::HtmlRewriterTest < ActiveSupport::TestCase
+ test "absolute_src rewrites relative href and src attributes" do
+ html = <<~HTML.squish
+ events
+
+
+
+ HTML
+
+ rewritten = Distillator::HtmlRewriter.absolute_src(html, "http://example.com/artist/")
+
+ assert_includes rewritten, 'href="http://example.com/events"'
+ assert_includes rewritten, 'src="http://example.com/image.png"'
+ assert_includes rewritten, 'src="http://example.com/artist/assets/app.js"'
+ assert_includes rewritten, 'href="http://example.com/artist/styles/site.css"'
+ end
+
+ test "absolute_src rewrites root relative src and leaves absolute urls unchanged" do
+ html = <<~HTML.squish
+
+ opening night
+
+ tickets
+ HTML
+
+ rewritten = Distillator::HtmlRewriter.absolute_src(html, "https://example.com/artist/")
+
+ assert_includes rewritten, 'src="https://example.com/image.png"'
+ assert_includes rewritten, 'href="https://example.com/artist/events/opening-night"'
+ assert_includes rewritten, 'src="https://cdn.example.org/poster.jpg"'
+ assert_includes rewritten, 'href="https://tickets.example.org/show"'
+ end
+
+ test "absolute_src preserves invalid src values without absolutizing them" do
+ html = ' '
+
+ rewritten = Distillator::HtmlRewriter.absolute_src(html, "http://example.com/artist/")
+
+ assert_includes rewritten, 'src="not a path"'
+ assert_includes rewritten, 'width="100px"'
+ assert_not_includes rewritten, 'http://example.com'
+ end
+
+ test "absolute_src normalizes protocol relative URLs using page scheme" do
+ html = ' '
+
+ rewritten = Distillator::HtmlRewriter.absolute_src(html, "https://example.com/artist/")
+
+ assert_includes rewritten, 'src="https://cdn.example.org/image.png"'
+ assert_includes rewritten, 'src="https://cdn.example.org/app.js"'
+ end
+
+ test "absolute_src preserves anchor mailto tel and data values" do
+ html = <<~HTML.squish
+ bio
+ mail
+ call
+
+ HTML
+
+ rewritten = Distillator::HtmlRewriter.absolute_src(html, "https://example.com/artist/")
+
+ assert_includes rewritten, 'href="#bio"'
+ assert_includes rewritten, 'href="mailto:boxoffice@example.org"'
+ assert_includes rewritten, 'href="tel:+14165551234"'
+ assert_includes rewritten, 'src="data:image/png;base64,AAAA"'
+ end
+
+ test "absolute_src respects base href when present" do
+ html = <<~HTML.squish
+
+
+ opening night
+ HTML
+
+ rewritten = Distillator::HtmlRewriter.absolute_src(html, "https://example.com/artist/")
+
+ assert_includes rewritten, 'src="https://cdn.example.org/site/images/poster.jpg"'
+ assert_includes rewritten, 'href="https://cdn.example.org/events/opening-night"'
+ assert_includes rewritten, ' '
+ end
+
+ test "absolute_src does not raise on malformed HTML" do
+ html = 'Open'
+
+ rewritten = Distillator::HtmlRewriter.absolute_src(html, "https://example.com/artist/")
+
+ assert_includes rewritten, 'src="https://example.com/poster.jpg"'
+ assert_includes rewritten, 'href="https://example.com/events"'
+ end
+
+ test "absolute_src preserves malformed paths without raising" do
+ html = <<~HTML.squish
+
+
+ HTML
+
+ rewritten = Distillator::HtmlRewriter.absolute_src(html, "https://example.com/artist/")
+
+ assert_includes rewritten, 'src="http://[broken"'
+ assert_includes rewritten, 'href="%%%"'
+ end
+end
diff --git a/test/services/distillator/json_post_fetcher_test.rb b/test/services/distillator/json_post_fetcher_test.rb
new file mode 100644
index 00000000..326b5ec4
--- /dev/null
+++ b/test/services/distillator/json_post_fetcher_test.rb
@@ -0,0 +1,21 @@
+require "test_helper"
+
+class Distillator::JsonPostFetcherTest < ActiveSupport::TestCase
+ test "json_post uses post with empty body by default" do
+ agent = mock("agent")
+ agent.expects(:post).with("https://example.com/api", "", { "Content-Type" => "application/json" }).returns(:ok)
+
+ assert_equal :ok, Distillator::JsonPostFetcher.call(agent: agent, url: "https://example.com/api", scrape_options: {})
+ end
+
+ test "json_post preserves explicit content type header" do
+ agent = mock("agent")
+ agent.expects(:post).with("https://example.com/api", "", { "Content-Type" => "application/custom+json" }).returns(:ok)
+
+ assert_equal :ok, Distillator::JsonPostFetcher.call(
+ agent: agent,
+ url: "https://example.com/api",
+ scrape_options: { headers: { "Content-Type" => "application/custom+json" } }
+ )
+ end
+end
diff --git a/test/services/distillator/migration_comparator_test.rb b/test/services/distillator/migration_comparator_test.rb
new file mode 100644
index 00000000..b03ee8cd
--- /dev/null
+++ b/test/services/distillator/migration_comparator_test.rb
@@ -0,0 +1,192 @@
+require "test_helper"
+
+class Distillator::MigrationComparatorTest < ActiveSupport::TestCase
+ class ExportServiceStub
+ def initialize(actual:, expected:)
+ @actual = actual
+ @expected = expected
+ end
+
+ def call(seedurl:)
+ @actual.fetch(seedurl)
+ end
+
+ def production_equivalent(seedurl:)
+ @expected.fetch(seedurl)
+ end
+ end
+
+ test "compares all selected export-relevant statements and ignores trace metadata" do
+ website, webpage = build_fixture("all-selected")
+ create_source(website: website, property: property_fixture(21, "Non important but selected"), algorithm: "xpath=//meta[@name='non-important']/@content")
+ create_source(
+ website: website,
+ property: property_fixture(22, "Trace payload"),
+ algorithm: 'manual=seed;ruby=[{"value"=>"Same Value","trace"=>{"step"=>"legacy-only"},"timing"=>{"ms"=>12}}]'
+ )
+
+ result = Distillator::MigrationComparator.call(
+ website: website,
+ webpage: webpage,
+ legacy_html: html_with(non_important: "Legacy value"),
+ condenser_html: html_with(non_important: "Condenser value"),
+ export_service: ExportServiceStub.new(
+ actual: { website.seedurl => "{\"@id\":\"event:1\"}" },
+ expected: { website.seedurl => "{\"@id\":\"event:1\"}" }
+ )
+ )
+
+ assert_equal Distillator::MigrationComparator::DIFFERENT_VALUE, result.statement_verdict
+ assert_nil result.export_verdict
+ diff_entry = result.statement_artifacts.find { |entry| entry[:property_id] == 21 }
+ trace_entry = result.statement_artifacts.find { |entry| entry[:property_id] == 22 }
+ assert_equal Distillator::MigrationComparator::DIFFERENT_VALUE, diff_entry[:verdict]
+ assert_equal "Legacy value", diff_entry[:legacy]
+ assert_equal "Condenser value", diff_entry[:condenser]
+ assert_equal Distillator::MigrationComparator::MATCH, trace_entry[:verdict]
+ assert_equal({ "value" => "Same Value" }, trace_entry[:legacy])
+ assert_equal({ "value" => "Same Value" }, trace_entry[:condenser])
+ end
+
+ test "when statements match it compares normalized export and skips fetch diagnostics" do
+ website, webpage = build_fixture("export-gate")
+ create_source(website: website, property: property_fixture(31, "Title"), algorithm: "xpath=//title/text()")
+
+ result = Distillator::MigrationComparator.call(
+ website: website,
+ webpage: webpage,
+ legacy_html: "Shared Title ",
+ condenser_html: "Shared Title ",
+ legacy_fetch: { status: :ok, body: "legacy", final_url: "https://example.org/legacy", redirect_chain: [], headers: { content_type: "text/html" } },
+ condenser_fetch: { status: :ok, body: "condenser", final_url: "https://example.org/condenser", redirect_chain: [], headers: { content_type: "text/html" } },
+ export_service: ExportServiceStub.new(
+ actual: { website.seedurl => '{"updated_at":"2026-05-25T12:00:00Z","@id":"event:1"}' },
+ expected: { website.seedurl => '{"@id":"event:1"}' }
+ )
+ )
+
+ assert_equal Distillator::MigrationComparator::MATCH, result.statement_verdict
+ assert_equal Distillator::MigrationComparator::MATCH, result.export_verdict
+ assert_nil result.fetch_diagnostic_verdict
+ assert_nil result.fetch_artifacts
+ end
+
+ test "when statements differ it skips export and records raw fetch diagnostics only" do
+ website, webpage = build_fixture("fetch-diagnostic")
+ create_source(website: website, property: property_fixture(41, "Title"), algorithm: "xpath=//title/text()")
+
+ export_service = Object.new
+ export_service.define_singleton_method(:call) { |seedurl:| raise "export should not run for #{seedurl}" }
+ export_service.define_singleton_method(:production_equivalent) { |seedurl:| raise "export should not run for #{seedurl}" }
+
+ result = Distillator::MigrationComparator.call(
+ website: website,
+ webpage: webpage,
+ legacy_html: "Legacy Title ",
+ condenser_html: "Condenser Title ",
+ legacy_fetch: {
+ status: :ok,
+ http_code: 200,
+ body: "legacy",
+ final_url: "https://example.org/final",
+ redirect_chain: ["https://example.org/start", "https://example.org/final"],
+ headers: { content_type: "text/html" },
+ wringer: { error_type: "ignored" }
+ },
+ condenser_fetch: {
+ status: :ok,
+ http_code: 200,
+ body: "condenser",
+ final_url: "https://example.org/final",
+ redirect_chain: ["https://example.org/start", "https://example.org/final"],
+ headers: { content_type: "text/html" },
+ wringer: { error_type: "ignored" }
+ },
+ export_service: export_service
+ )
+
+ assert_equal Distillator::MigrationComparator::DIFFERENT_VALUE, result.statement_verdict
+ assert_nil result.export_verdict
+ assert_equal Distillator::MigrationComparator::DIFFERENT_VALUE, result.fetch_diagnostic_verdict
+ assert_equal %i[body_digest body_length content_type final_url http_code redirect_chain status].sort,
+ result.fetch_artifacts[:legacy].keys.sort
+ assert_equal "ok", result.fetch_artifacts[:legacy][:status]
+ assert_equal 200, result.fetch_artifacts[:legacy][:http_code]
+ assert_equal "https://example.org/final", result.fetch_artifacts[:legacy][:final_url]
+ assert_equal ["https://example.org/start", "https://example.org/final"], result.fetch_artifacts[:legacy][:redirect_chain]
+ assert_equal "text/html", result.fetch_artifacts[:legacy][:content_type]
+ end
+
+ test "classifies read only parse failures as parse errors" do
+ website, webpage = build_fixture("parse-error")
+ create_source(website: website, property: property_fixture(51, "Fetched title"), algorithm: "url=$url + '?detail'; xpath=//title/text()")
+
+ result = Distillator::MigrationComparator.call(
+ website: website,
+ webpage: webpage,
+ legacy_html: "Legacy ",
+ condenser_html: "Condenser ",
+ export_service: ExportServiceStub.new(
+ actual: { website.seedurl => '{"@id":"event:1"}' },
+ expected: { website.seedurl => '{"@id":"event:1"}' }
+ )
+ )
+
+ assert_equal Distillator::MigrationComparator::PARSE_ERROR, result.statement_verdict
+ assert_equal Distillator::MigrationComparator::PARSE_ERROR, result.statement_artifacts.first[:verdict]
+ assert_nil result.export_verdict
+ end
+
+ private
+
+ def build_fixture(suffix)
+ website = Website.create!(
+ name: "Migration comparator #{suffix}",
+ seedurl: "migration-comparator-#{suffix}",
+ graph_name: "https://example.org/migration-comparator-#{suffix}",
+ default_language: "en"
+ )
+ webpage = Webpage.create!(
+ url: "https://example.org/#{suffix}/event",
+ language: "en",
+ rdf_uri: "rdf:#{suffix}:event",
+ rdfs_class: rdfs_classes(:one),
+ website: website
+ )
+
+ [website, webpage]
+ end
+
+ def create_source(website:, property:, algorithm:)
+ Source.create!(
+ algorithm_value: algorithm,
+ selected: true,
+ selected_by: "test",
+ language: "en",
+ render_js: false,
+ property: property,
+ website: website
+ )
+ end
+
+ def property_fixture(id, label)
+ Property.create!(
+ id: id,
+ label: label,
+ value_datatype: "MyString",
+ uri: "https://example.org/properties/#{id}",
+ rdfs_class: rdfs_classes(:one)
+ )
+ end
+
+ def html_with(non_important:)
+ <<~HTML
+
+
+
+
+
+
+ HTML
+ end
+end
diff --git a/test/services/distillator/migration_status_test.rb b/test/services/distillator/migration_status_test.rb
new file mode 100644
index 00000000..74889d29
--- /dev/null
+++ b/test/services/distillator/migration_status_test.rb
@@ -0,0 +1,14 @@
+require "test_helper"
+
+class Distillator::MigrationStatusTest < ActiveSupport::TestCase
+ test "reports compact readiness checks and known legacy bypasses" do
+ status = Distillator::MigrationStatus.call
+
+ assert_equal true, status.dig(:checks, "Fetch cache model present")
+ assert_equal true, status.dig(:checks, "Export invariance fixture")
+ assert_equal true, status.dig(:checks, "Render-JS fallback coverage")
+ assert_equal true, status.dig(:checks, "JSON POST fallback coverage")
+ assert_equal true, status.dig(:checks, "Manual link export coverage")
+ assert_equal 0, status.dig(:legacy_bypasses, :count)
+ end
+end
diff --git a/test/services/distillator/native_fetch_test.rb b/test/services/distillator/native_fetch_test.rb
new file mode 100644
index 00000000..24d7f7b5
--- /dev/null
+++ b/test/services/distillator/native_fetch_test.rb
@@ -0,0 +1,566 @@
+require "test_helper"
+
+class Distillator::NativeFetchTest < ActiveSupport::TestCase
+ class CapturingLogger
+ attr_reader :infos
+
+ def initialize
+ @infos = []
+ end
+
+ def info(payload)
+ @infos << payload
+ end
+ end
+
+ ConfigurableAgent = Struct.new(:response, :history, :user_agent_alias) do
+ def get(_url)
+ response
+ end
+ end
+
+ test "call does not use Wringer helpers and fetches the original URL" do
+ ApplicationController.expects(:helpers).never
+
+ response = Struct.new(:code, :body, :uri, :response).new(
+ 200,
+ "native",
+ URI("https://example.com/final"),
+ { "Content-Type" => "text/html" }
+ )
+ history_entry = Struct.new(:uri).new(URI("https://example.com/final"))
+ agent = ConfigurableAgent.new(response, [history_entry], nil)
+
+ result = Distillator::NativeFetch.call(
+ url: "https://example.com/events",
+ render_js: false,
+ scrape_options: {},
+ agent: agent,
+ logger: Rails.logger
+ )
+
+ assert_equal :ok, result[:status]
+ assert_equal "native", result[:body]
+ assert_equal({ content_type: "text/html" }, result[:headers])
+ assert_equal "https://example.com/final", result[:final_url]
+ assert_equal ["https://example.com/final"], result[:redirect_chain]
+ assert_equal "Mac Safari", agent.user_agent_alias
+ assert_equal "native", result.dig(:wringer, :signals, :fetch_backend)
+ assert_equal "GET", result.dig(:wringer, :signals, :request_method)
+ assert_equal false, result.dig(:wringer, :signals, :use_phantomjs)
+ assert_kind_of Hash, result[:wringer]
+ end
+
+ test "call logs native fetch request and normalized response" do
+ logger = CapturingLogger.new
+ response = Struct.new(:code, :body, :uri, :response).new(
+ 200,
+ "native",
+ URI("https://example.com/final"),
+ { "Content-Type" => "text/html" }
+ )
+ history_entry = Struct.new(:uri).new(URI("https://example.com/final"))
+ agent = mock("agent")
+ agent.expects(:get).with("https://example.com/events").returns(response)
+ agent.stubs(:history).returns([history_entry])
+
+ Distillator::NativeFetch.call(
+ url: "https://example.com/events",
+ render_js: false,
+ scrape_options: {},
+ agent: agent,
+ logger: logger
+ )
+
+ assert_equal(
+ {
+ event: "distillator.native_fetch.request",
+ url: "https://example.com/events",
+ render_js: false,
+ json_post: false
+ },
+ logger.infos.first
+ )
+ assert_equal "distillator.native_fetch.response", logger.infos.second[:event]
+ assert_equal :ok, logger.infos.second[:status]
+ assert_equal 200, logger.infos.second[:http_code]
+ assert_equal "https://example.com/final", logger.infos.second[:final_url]
+ assert_equal ["https://example.com/final"], logger.infos.second[:redirect_chain]
+ assert_equal({ content_type: "text/html" }, logger.infos.second[:headers])
+ end
+
+ test "call retries once with ssl verify none when supported" do
+ response = Struct.new(:code, :body, :uri, :response).new(
+ 200,
+ "ssl fallback",
+ URI("https://example.com/ssl"),
+ { "Content-Type" => "text/html" }
+ )
+ history_entry = Struct.new(:uri).new(URI("https://example.com/ssl"))
+ http = Struct.new(:verify_mode).new(nil)
+ mechanize_wrapper = Struct.new(:http).new(http)
+ agent = mock("agent")
+ agent.stubs(:public_methods).returns([])
+ agent.stubs(:agent).returns(mechanize_wrapper)
+ calls = 0
+ agent.stubs(:get).with("https://example.com/ssl") do
+ calls += 1
+ raise OpenSSL::SSL::SSLError, "certificate verify failed" if calls == 1
+
+ response
+ end
+ agent.stubs(:history).returns([history_entry])
+
+ result = Distillator::NativeFetch.call(
+ url: "https://example.com/ssl",
+ render_js: false,
+ scrape_options: {},
+ agent: agent,
+ logger: Rails.logger
+ )
+
+ assert_equal :ok, result[:status]
+ assert_equal OpenSSL::SSL::VERIFY_NONE, http.verify_mode
+ assert_equal true, result.dig(:wringer, :signals, :ssl_verify_none_fallback)
+ assert_includes result.dig(:wringer, :hints), "ssl_verify_none_fallback"
+ end
+
+ test "call uses post with default json content type when json_post is true" do
+ response = Struct.new(:code, :body, :uri, :response).new(
+ 200,
+ '{"ok":true}',
+ URI("https://example.com/api"),
+ { "Content-Type" => "application/json" }
+ )
+ history_entry = Struct.new(:uri).new(URI("https://example.com/api"))
+ agent = mock("agent")
+ agent.expects(:post).with("https://example.com/api", "", { "Content-Type" => "application/json" }).returns(response)
+ agent.stubs(:history).returns([history_entry])
+
+ result = Distillator::NativeFetch.call(
+ url: "https://example.com/api",
+ render_js: false,
+ scrape_options: { json_post: true },
+ agent: agent,
+ logger: Rails.logger
+ )
+
+ assert_equal :ok, result[:status]
+ assert_equal '{"ok":true}', result[:body]
+ assert_equal "json", result.dig(:wringer, :signals, :content_type)
+ assert_equal "POST", result.dig(:wringer, :signals, :request_method)
+ assert_equal "native", result.dig(:wringer, :signals, :fetch_backend)
+ assert_includes result.dig(:wringer, :hints), "json_detected"
+ end
+
+ test "call does not use post when json_post is string false" do
+ response = Struct.new(:code, :body, :uri, :response).new(
+ 200,
+ "get",
+ URI("https://example.com/api"),
+ { "Content-Type" => "text/html" }
+ )
+ history_entry = Struct.new(:uri).new(URI("https://example.com/api"))
+ agent = mock("agent")
+ agent.expects(:post).never
+ agent.expects(:get).with("https://example.com/api").returns(response)
+ agent.stubs(:history).returns([history_entry])
+
+ result = Distillator::NativeFetch.call(
+ url: "https://example.com/api",
+ render_js: false,
+ scrape_options: { json_post: "false" },
+ agent: agent,
+ logger: Rails.logger
+ )
+
+ assert_equal :ok, result[:status]
+ assert_equal "get", result[:body]
+ end
+
+ test "call preserves explicit content type header for json_post" do
+ response = Struct.new(:code, :body, :uri, :response).new(
+ 200,
+ '{"ok":true}',
+ URI("https://example.com/api"),
+ { "Content-Type" => "application/json" }
+ )
+ history_entry = Struct.new(:uri).new(URI("https://example.com/api"))
+ agent = mock("agent")
+ agent.expects(:post).with("https://example.com/api", "", { "Content-Type" => "application/custom+json" }).returns(response)
+ agent.stubs(:history).returns([history_entry])
+
+ Distillator::NativeFetch.call(
+ url: "https://example.com/api",
+ render_js: false,
+ scrape_options: { json_post: true, headers: { "Content-Type" => "application/custom+json" } },
+ agent: agent,
+ logger: Rails.logger
+ )
+ end
+
+ test "call preserves redirect chain and final_url across redirects" do
+ response = Struct.new(:code, :body, :uri, :response).new(
+ 200,
+ "redirected",
+ URI("https://example.com/final"),
+ { "Content-Type" => "text/html" }
+ )
+ history = [
+ Struct.new(:uri).new(URI("https://example.com/start")),
+ Struct.new(:uri).new(URI("https://example.com/final"))
+ ]
+ agent = mock("agent")
+ agent.expects(:get).with("https://example.com/start").returns(response)
+ agent.stubs(:history).returns(history)
+
+ result = Distillator::NativeFetch.call(
+ url: "https://example.com/start",
+ render_js: false,
+ scrape_options: {},
+ agent: agent,
+ logger: Rails.logger
+ )
+
+ assert_equal "https://example.com/final", result[:final_url]
+ assert_equal ["https://example.com/start", "https://example.com/final"], result[:redirect_chain]
+ assert_equal "normal", result.dig(:wringer, :signals, :redirect_type)
+ assert_equal true, result.dig(:wringer, :signals, :redirected)
+ assert_equal "https://example.com/final", result.dig(:wringer, :signals, :final_url)
+ end
+
+ test "call records generic error match metadata for policy rejected html" do
+ response = Struct.new(:code, :body, :uri, :response).new(
+ 200,
+ "Une erreur est survenue Retry later.
",
+ URI("https://example.com/final"),
+ { "Content-Type" => "text/html" }
+ )
+ history_entry = Struct.new(:uri).new(URI("https://example.com/final"))
+ agent = mock("agent")
+ agent.expects(:get).with("https://example.com/failure").returns(response)
+ agent.stubs(:history).returns([history_entry])
+
+ result = Distillator::NativeFetch.call(
+ url: "https://example.com/failure",
+ render_js: false,
+ scrape_options: {},
+ agent: agent,
+ logger: Rails.logger
+ )
+
+ assert_equal "generic_error_text", result.dig(:wringer, :signals, :primary_issue_key)
+ assert_equal "body_text", result.dig(:wringer, :signals, :primary_issue_match, "source")
+ assert_equal "Une erreur est survenue", result.dig(:wringer, :signals, :primary_issue_match, "pattern")
+ end
+
+ test "call marks json responses with json_detected metadata" do
+ response = Struct.new(:code, :body, :uri, :response).new(
+ 200,
+ '{"ok":true}',
+ URI("https://example.com/api"),
+ { "Content-Type" => "application/json" }
+ )
+ history_entry = Struct.new(:uri).new(URI("https://example.com/api"))
+ agent = mock("agent")
+ agent.expects(:get).with("https://example.com/api").returns(response)
+ agent.stubs(:history).returns([history_entry])
+
+ result = Distillator::NativeFetch.call(
+ url: "https://example.com/api",
+ render_js: false,
+ scrape_options: {},
+ agent: agent,
+ logger: Rails.logger
+ )
+
+ assert_equal "json", result.dig(:wringer, :signals, :content_type)
+ assert_equal true, result.dig(:wringer, :signals, :json_detected)
+ assert_includes result.dig(:wringer, :hints), "json_detected"
+ end
+
+ test "call marks empty body responses with empty_body hint" do
+ response = Struct.new(:code, :body, :uri, :response).new(
+ 200,
+ "",
+ URI("https://example.com/empty"),
+ { "Content-Type" => "text/html" }
+ )
+ history_entry = Struct.new(:uri).new(URI("https://example.com/empty"))
+ agent = mock("agent")
+ agent.expects(:get).with("https://example.com/empty").returns(response)
+ agent.stubs(:history).returns([history_entry])
+
+ result = Distillator::NativeFetch.call(
+ url: "https://example.com/empty",
+ render_js: false,
+ scrape_options: {},
+ agent: agent,
+ logger: Rails.logger
+ )
+
+ assert_includes result.dig(:wringer, :hints), "empty_body"
+ end
+
+ test "call keeps tourisme des chenaux detail body as content success despite listing css" do
+ response = Struct.new(:code, :body, :uri, :response).new(
+ 200,
+ <<~HTML,
+
+
+
+
Listing styles are present
+
Match d'improvisation | LPIA | Sainte-Anne-de-la-Pérade
+
29 janvier 2026
+
19:00
+
Description et details du lieu.
+
+
+
+ HTML
+ URI("https://tourismedeschenaux.ca/evenements"),
+ { "Content-Type" => "text/html" }
+ )
+ history_entry = Struct.new(:uri).new(URI("https://tourismedeschenaux.ca/evenements"))
+ agent = mock("agent")
+ agent.expects(:get).with("https://tourismedeschenaux.ca/evenements").returns(response)
+ agent.stubs(:history).returns([history_entry])
+
+ result = Distillator::NativeFetch.call(
+ url: "https://tourismedeschenaux.ca/evenements",
+ render_js: false,
+ scrape_options: {},
+ agent: agent,
+ logger: Rails.logger
+ )
+
+ assert_equal true, result.dig(:wringer, :signals, :transport_success)
+ assert_equal true, result.dig(:wringer, :signals, :content_success)
+ assert_nil result.dig(:wringer, :signals, :primary_issue_key)
+ assert_nil result.dig(:wringer, :policy_action)
+ end
+
+ test "call returns abort contract for 404 response" do
+ missing_page = Struct.new(:code, :body, :uri, :response).new(
+ 404,
+ "Not Found",
+ URI("https://example.com/missing"),
+ { "Content-Type" => "text/html" }
+ )
+ error = Mechanize::ResponseCodeError.new(missing_page, "404")
+ agent = mock("agent")
+ agent.expects(:get).with("https://example.com/missing").raises(error)
+ agent.stubs(:history).returns([])
+
+ result = Distillator::NativeFetch.call(
+ url: "https://example.com/missing",
+ render_js: false,
+ scrape_options: {},
+ agent: agent,
+ logger: Rails.logger
+ )
+
+ assert_equal :abort, result[:status]
+ assert_equal "abort_update", result[:body].first
+ assert_equal "http_404", result.dig(:wringer, :error_type)
+ assert_equal "https://example.com/missing", result[:final_url]
+ assert_equal({ content_type: "text/html" }, result[:headers])
+ end
+
+ test "call returns abort contract for json_post 404 response" do
+ missing_page = Struct.new(:code, :body, :uri, :response).new(
+ 404,
+ '{"error":"missing"}',
+ URI("https://example.com/api"),
+ { "Content-Type" => "application/json" }
+ )
+ error = Mechanize::ResponseCodeError.new(missing_page, "404")
+ agent = mock("agent")
+ agent.expects(:post).with("https://example.com/api", "", { "Content-Type" => "application/json" }).raises(error)
+ agent.stubs(:history).returns([])
+
+ result = Distillator::NativeFetch.call(
+ url: "https://example.com/api",
+ render_js: false,
+ scrape_options: { json_post: true },
+ agent: agent,
+ logger: Rails.logger
+ )
+
+ assert_equal :abort, result[:status]
+ assert_equal '{"error":"missing"}', result[:raw_body]
+ assert_equal "http_404", result.dig(:wringer, :error_type)
+ end
+
+ test "call normalizes abort_update control payloads to abort responses" do
+ payload = ["abort_update", { error_type: "system_cloudflare", retry: true, cache: false, source: "wringer" }]
+ agent = mock("agent")
+ agent.expects(:get).with("https://example.com/control").returns(payload)
+ agent.stubs(:history).returns([])
+
+ result = Distillator::NativeFetch.call(
+ url: "https://example.com/control",
+ render_js: false,
+ scrape_options: {},
+ agent: agent,
+ logger: Rails.logger
+ )
+
+ assert_equal :abort, result[:status]
+ assert_equal payload, result[:body]
+ assert_nil result[:http_code]
+ assert_nil result[:raw_body]
+ assert_equal "system_cloudflare", result.dig(:wringer, :error_type)
+ assert_equal true, result.dig(:wringer, :retry)
+ assert_equal false, result.dig(:wringer, :cache)
+ end
+
+ test "call normalizes skip control action into abort contract" do
+ agent = mock("agent")
+ agent.expects(:get).with("https://example.com/skip").returns(["skip", { reason: "duplicate" }])
+ agent.stubs(:history).returns([])
+
+ result = Distillator::NativeFetch.call(
+ url: "https://example.com/skip",
+ render_js: false,
+ scrape_options: {},
+ agent: agent,
+ logger: Rails.logger
+ )
+
+ assert_equal :abort, result[:status]
+ assert_equal "abort_update", result[:body].first
+ assert_equal "WringerSkip", result.dig(:wringer, :error_type)
+ end
+
+ test "call normalizes malformed control payloads into abort contract" do
+ agent = mock("agent")
+ agent.expects(:get).with("https://example.com/malformed").returns(["abort_update", "broken-payload"])
+ agent.stubs(:history).returns([])
+
+ result = Distillator::NativeFetch.call(
+ url: "https://example.com/malformed",
+ render_js: false,
+ scrape_options: {},
+ agent: agent,
+ logger: Rails.logger
+ )
+
+ assert_equal :abort, result[:status]
+ assert_equal "abort_update", result[:body].first
+ assert_equal "WringerMalformedControlPayload", result.dig(:wringer, :error_type)
+ end
+
+ test "call normalizes unsupported control actions into abort contract" do
+ agent = mock("agent")
+ agent.expects(:get).with("https://example.com/unsupported").returns(["pause", { reason: "unknown" }])
+ agent.stubs(:history).returns([])
+
+ result = Distillator::NativeFetch.call(
+ url: "https://example.com/unsupported",
+ render_js: false,
+ scrape_options: {},
+ agent: agent,
+ logger: Rails.logger
+ )
+
+ assert_equal :abort, result[:status]
+ assert_equal "abort_update", result[:body].first
+ assert_equal "WringerUnsupportedAction", result.dig(:wringer, :error_type)
+ end
+
+ test "call returns abort contract for 500 response" do
+ error_page = Struct.new(:code, :body, :uri, :response).new(
+ 500,
+ "Internal Server Error",
+ URI("https://example.com/error"),
+ { "Content-Type" => "text/html" }
+ )
+ error = Mechanize::ResponseCodeError.new(error_page, "500")
+ agent = mock("agent")
+ agent.expects(:get).with("https://example.com/error").raises(error)
+ agent.stubs(:history).returns([])
+
+ result = Distillator::NativeFetch.call(
+ url: "https://example.com/error",
+ render_js: false,
+ scrape_options: {},
+ agent: agent,
+ logger: Rails.logger
+ )
+
+ assert_equal :abort, result[:status]
+ assert_equal "http_server_error", result.dig(:wringer, :error_type)
+ assert_equal true, result.dig(:wringer, :system_error)
+ end
+
+ test "call returns abort contract for timeout" do
+ agent = mock("agent")
+ agent.expects(:get).with("https://example.com/slow").raises(Net::ReadTimeout, "timeout")
+
+ result = Distillator::NativeFetch.call(
+ url: "https://example.com/slow",
+ render_js: false,
+ scrape_options: {},
+ agent: agent,
+ logger: Rails.logger
+ )
+
+ assert_equal :abort, result[:status]
+ assert_equal "NativeFetchError", result.dig(:wringer, :error_type)
+ assert_equal "failed", result.dig(:wringer, :signals, :network_status)
+ assert_includes result.dig(:wringer, :hints), "timeout"
+ end
+
+ test "call returns abort contract for json_post timeout" do
+ agent = mock("agent")
+ agent.expects(:post).with("https://example.com/slow", "", { "Content-Type" => "application/json" }).raises(Net::ReadTimeout, "timeout")
+
+ result = Distillator::NativeFetch.call(
+ url: "https://example.com/slow",
+ render_js: false,
+ scrape_options: { json_post: true },
+ agent: agent,
+ logger: Rails.logger
+ )
+
+ assert_equal :abort, result[:status]
+ assert_equal "failed", result.dig(:wringer, :signals, :network_status)
+ assert_includes result.dig(:wringer, :hints), "timeout"
+ end
+
+ test "call returns abort contract for ssl error" do
+ agent = mock("agent")
+ agent.stubs(:agent).returns(nil)
+ agent.expects(:get).with("https://example.com/ssl").raises(OpenSSL::SSL::SSLError, "certificate verify failed")
+
+ result = Distillator::NativeFetch.call(
+ url: "https://example.com/ssl",
+ render_js: false,
+ scrape_options: {},
+ agent: agent,
+ logger: Rails.logger
+ )
+
+ assert_equal :abort, result[:status]
+ assert_equal "NativeFetchError", result.dig(:wringer, :error_type)
+ assert_equal "failed", result.dig(:wringer, :signals, :network_status)
+ end
+
+ test "call returns abort contract for socket error" do
+ agent = mock("agent")
+ agent.expects(:get).with("https://example.com/down").raises(SocketError, "getaddrinfo")
+
+ result = Distillator::NativeFetch.call(
+ url: "https://example.com/down",
+ render_js: false,
+ scrape_options: {},
+ agent: agent,
+ logger: Rails.logger
+ )
+
+ assert_equal :abort, result[:status]
+ assert_equal "NativeFetchError", result.dig(:wringer, :error_type)
+ assert_equal "failed", result.dig(:wringer, :signals, :network_status)
+ end
+end
diff --git a/test/services/distillator/operator_next_action_test.rb b/test/services/distillator/operator_next_action_test.rb
new file mode 100644
index 00000000..f7964920
--- /dev/null
+++ b/test/services/distillator/operator_next_action_test.rb
@@ -0,0 +1,66 @@
+require "test_helper"
+
+class Distillator::OperatorNextActionTest < ActiveSupport::TestCase
+ test "blocked site says review blocker" do
+ website = stub_website("shadow")
+ status = stub_status(status: :blocked, blockers: ["Cannot activate yet: fetch check failed."])
+
+ assert_equal "Review blocker.", Distillator::OperatorNextAction.call(website: website, transition_status: status)
+ end
+
+ test "legacy site says move to shadow" do
+ website = stub_website("legacy")
+ status = stub_status(status: :not_checked)
+
+ assert_equal "Move to Shadow.", Distillator::OperatorNextAction.call(website: website, transition_status: status)
+ end
+
+ test "shadow site with stale evidence says run transition batch check" do
+ website = stub_website("shadow")
+ status = stub_status(status: :review, fetch: :stale)
+
+ assert_equal "Run transition batch check.", Distillator::OperatorNextAction.call(website: website, transition_status: status)
+ end
+
+ test "ready shadow site says promote to active" do
+ website = stub_website("shadow")
+ status = stub_status(status: :ready)
+
+ assert_equal "Promote to Active.", Distillator::OperatorNextAction.call(website: website, transition_status: status)
+ end
+
+ test "review-eligible shadow site says activate after review" do
+ website = stub_website("shadow")
+ status = stub_status(status: :review, review_activation_eligible: true)
+
+ assert_equal "Activate after review.", Distillator::OperatorNextAction.call(website: website, transition_status: status)
+ end
+
+ test "ready active site prefers active cache when available" do
+ website = stub_website("active")
+ status = stub_status(status: :ready)
+
+ assert_equal "Open active cache.", Distillator::OperatorNextAction.call(
+ website: website,
+ transition_status: status,
+ cache_link_payload: { active_cache_url: "/condenser/cache?term=test" }
+ )
+ end
+
+ test "ready active site falls back to monitor without cache link" do
+ website = stub_website("active")
+ status = stub_status(status: :ready)
+
+ assert_equal "Monitor.", Distillator::OperatorNextAction.call(website: website, transition_status: status)
+ end
+
+ private
+
+ def stub_website(mode)
+ Struct.new(:distillator_mode).new(mode)
+ end
+
+ def stub_status(status:, blockers: [], fetch: :passed, statements: :passed, export: :passed, review_activation_eligible: false)
+ Struct.new(:status, :blockers, :fetch, :statements, :export, :review_activation_eligible).new(status, blockers, fetch, statements, export, review_activation_eligible)
+ end
+end
diff --git a/test/services/distillator/phantomjs_fetcher_test.rb b/test/services/distillator/phantomjs_fetcher_test.rb
new file mode 100644
index 00000000..2d7669af
--- /dev/null
+++ b/test/services/distillator/phantomjs_fetcher_test.rb
@@ -0,0 +1,30 @@
+require "test_helper"
+
+class Distillator::PhantomjsFetcherTest < ActiveSupport::TestCase
+ setup do
+ @old_phantomjs_api_key = ENV["PHANTOMJS_API_KEY"]
+ @old_direct_fallback = ENV["DISTILLATOR_ALLOW_RENDERED_DIRECT_FALLBACK"]
+ end
+
+ teardown do
+ ENV["PHANTOMJS_API_KEY"] = @old_phantomjs_api_key
+ ENV["DISTILLATOR_ALLOW_RENDERED_DIRECT_FALLBACK"] = @old_direct_fallback
+ end
+
+ test "missing phantomjs api key returns renderer unavailable abort by default" do
+ ENV["PHANTOMJS_API_KEY"] = nil
+ ENV["DISTILLATOR_ALLOW_RENDERED_DIRECT_FALLBACK"] = nil
+ Distillator::NativeFetch.expects(:call).never
+
+ result = Distillator::PhantomjsFetcher.call(
+ url: "https://www.ovation.ca/event",
+ uri_key: CGI.escape("https://www.ovation.ca/event"),
+ iframe: false
+ )
+
+ assert_equal :abort, result[:status]
+ assert_equal true, result.dig(:wringer, :signals, :renderer_unavailable)
+ assert_equal "none", result.dig(:wringer, :signals, :renderer_fallback)
+ assert_includes result.dig(:wringer, :hints), "phantomjs_unavailable"
+ end
+end
diff --git a/test/services/distillator/production_preflight_test.rb b/test/services/distillator/production_preflight_test.rb
new file mode 100644
index 00000000..589f28e8
--- /dev/null
+++ b/test/services/distillator/production_preflight_test.rb
@@ -0,0 +1,68 @@
+require "test_helper"
+
+class Distillator::ProductionPreflightTest < ActiveSupport::TestCase
+ setup do
+ @old_runtime = ENV["DISTILLATOR_RUNTIME"]
+ @old_rails_env = ENV["RAILS_ENV"]
+ ApplicationController.helpers.stubs(:get_wringer_url_per_environment).returns("http://wringer.example")
+ Website.update_all(distillator_mode: "shadow")
+ end
+
+ teardown do
+ ENV["DISTILLATOR_RUNTIME"] = @old_runtime
+ ENV["RAILS_ENV"] = @old_rails_env
+ end
+
+ test "staging preflight fails when legacy websites remain" do
+ ENV["DISTILLATOR_RUNTIME"] = "staging"
+ Website.create!(
+ name: "Legacy staging website",
+ seedurl: "legacy-staging-website",
+ graph_name: "https://legacy-staging-website.example/graph",
+ default_language: "en",
+ distillator_mode: "legacy"
+ )
+
+ result = Distillator::ProductionPreflight.call
+ entry = result.entries.find { |item| item.label == "Staging rollout modes" }
+
+ assert_not_nil entry
+ assert_equal false, entry.ok
+ assert_includes entry.value, "Staging requires every website to be Shadow or Active."
+ assert_includes entry.value, "1 invalid websites"
+ end
+
+ test "staging preflight passes when every website is shadow or active" do
+ ENV["DISTILLATOR_RUNTIME"] = "staging"
+ Website.create!(
+ name: "Shadow staging website",
+ seedurl: "shadow-staging-website",
+ graph_name: "https://shadow-staging-website.example/graph",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+ Website.create!(
+ name: "Active staging website",
+ seedurl: "active-staging-website",
+ graph_name: "https://active-staging-website.example/graph",
+ default_language: "en",
+ distillator_mode: "active"
+ )
+
+ result = Distillator::ProductionPreflight.call
+ entry = result.entries.find { |item| item.label == "Staging rollout modes" }
+
+ assert_not_nil entry
+ assert_equal true, entry.ok
+ assert_includes entry.value, "Staging requires every website to be Shadow or Active."
+ assert_includes entry.value, "OK"
+ end
+
+ test "production preflight does not add staging rollout entry" do
+ ENV["DISTILLATOR_RUNTIME"] = "production"
+
+ result = Distillator::ProductionPreflight.call
+
+ assert_nil result.entries.find { |item| item.label == "Staging rollout modes" }
+ end
+end
diff --git a/test/services/distillator/promotion_readiness_test.rb b/test/services/distillator/promotion_readiness_test.rb
new file mode 100644
index 00000000..9dab9e3a
--- /dev/null
+++ b/test/services/distillator/promotion_readiness_test.rb
@@ -0,0 +1,106 @@
+require "test_helper"
+
+class Distillator::PromotionReadinessTest < ActiveSupport::TestCase
+ test "la vitrine sites block promotion when export evidence is missing" do
+ website = build_website(name: "Tout Culture", seedurl: "outside-seed")
+ cache = build_cache(signals: { "representative_urls_checked" => true, "statement_count_delta_acceptable" => true })
+
+ result = Distillator::PromotionReadiness.call(website: website, cache: cache)
+
+ assert_includes result.blockers, "Cannot activate yet: export check is missing."
+ assert_equal [], result.warnings
+ end
+
+ test "non cohort sites warn when export evidence is missing" do
+ website = build_website(name: "Outside Feed", seedurl: "outside-feed")
+ cache = build_cache(signals: { "representative_urls_checked" => true, "statement_count_delta_acceptable" => true })
+
+ result = Distillator::PromotionReadiness.call(website: website, cache: cache)
+
+ assert_equal [], result.blockers
+ assert_includes result.warnings, "Needs review: export check is missing."
+ end
+
+ test "la vitrine sites pass when representative urls statement delta and export checks are present" do
+ website = build_website(name: "Tout Culture", seedurl: "outside-seed")
+ cache = build_cache(signals: {})
+ website.transition_evidences.create!(
+ url: "https://example.org/event",
+ check_kind: "fetch_parity",
+ status: "checked",
+ details: { representative_urls_checked: true },
+ checked_at: 1.hour.ago
+ )
+ website.transition_evidences.create!(
+ url: "https://example.org/event",
+ check_kind: "statement_delta",
+ status: "checked",
+ statement_count_delta_acceptable: true,
+ checked_at: 1.hour.ago
+ )
+ website.transition_evidences.create!(
+ url: "https://example.org/event",
+ check_kind: "export_diff",
+ status: "checked",
+ export_diff_checked: true,
+ checked_at: 1.hour.ago
+ )
+
+ result = Distillator::PromotionReadiness.call(website: website, cache: cache)
+
+ assert_equal [], result.blockers
+ assert_equal [], result.warnings
+ end
+
+ test "la vitrine sites block promotion when durable evidence is stale" do
+ website = build_website(name: "Tout Culture", seedurl: "outside-seed")
+ cache = build_cache(signals: {})
+ website.transition_evidences.create!(
+ url: "https://example.org/event",
+ check_kind: "fetch_parity",
+ status: "checked",
+ details: { representative_urls_checked: true },
+ checked_at: 2.days.ago
+ )
+ website.transition_evidences.create!(
+ url: "https://example.org/event",
+ check_kind: "statement_delta",
+ status: "checked",
+ statement_count_delta_acceptable: true,
+ checked_at: 2.days.ago
+ )
+ website.transition_evidences.create!(
+ url: "https://example.org/event",
+ check_kind: "export_diff",
+ status: "accepted",
+ export_diff_checked: true,
+ export_diff_accepted: true,
+ checked_at: 8.days.ago
+ )
+
+ result = Distillator::PromotionReadiness.call(website: website, cache: cache)
+
+ assert_includes result.blockers, "Cannot activate yet: export check is stale."
+ assert_includes result.warnings, "Needs review: export check is stale."
+ end
+
+ private
+
+ def build_website(name:, seedurl:)
+ Website.create!(
+ name: name,
+ seedurl: seedurl,
+ graph_name: "https://example.org/#{seedurl}",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+ end
+
+ def build_cache(signals:)
+ Distillator::FetchCache.new(
+ uri_key: CGI.escape("https://example.org/event"),
+ normalized_url: "https://example.org/event",
+ signals: signals
+ )
+ end
+end
diff --git a/test/services/distillator/rendered_fetch_test.rb b/test/services/distillator/rendered_fetch_test.rb
new file mode 100644
index 00000000..7fa631a5
--- /dev/null
+++ b/test/services/distillator/rendered_fetch_test.rb
@@ -0,0 +1,33 @@
+require "test_helper"
+
+class Distillator::RenderedFetchTest < ActiveSupport::TestCase
+ setup do
+ @old_renderer = ENV["DISTILLATOR_RENDERER"]
+ end
+
+ teardown do
+ ENV["DISTILLATOR_RENDERER"] = @old_renderer
+ end
+
+ test "uses legacy phantomjs renderer by default" do
+ ENV["DISTILLATOR_RENDERER"] = nil
+ Distillator::PhantomjsFetcher.expects(:call).with(has_entries(url: "https://example.com/page", iframe: false)).returns(status: :ok)
+
+ result = Distillator::RenderedFetch.call(url: "https://example.com/page", uri_key: CGI.escape("https://example.com/page"))
+
+ assert_equal :ok, result[:status]
+ end
+
+ test "uses disabled renderer when configured" do
+ ENV["DISTILLATOR_RENDERER"] = "disabled"
+ Distillator::Renderers::DisabledRenderer.expects(:call).with(has_entries(url: "https://example.com/page", iframe: true)).returns(status: :abort)
+
+ result = Distillator::RenderedFetch.call(
+ url: "https://example.com/page",
+ uri_key: CGI.escape("https://example.com/page"),
+ iframe: true
+ )
+
+ assert_equal :abort, result[:status]
+ end
+end
diff --git a/test/services/distillator/renderers/legacy_phantomjs_renderer_test.rb b/test/services/distillator/renderers/legacy_phantomjs_renderer_test.rb
new file mode 100644
index 00000000..955777dc
--- /dev/null
+++ b/test/services/distillator/renderers/legacy_phantomjs_renderer_test.rb
@@ -0,0 +1,67 @@
+require "test_helper"
+
+class Distillator::Renderers::LegacyPhantomjsRendererTest < ActiveSupport::TestCase
+ setup do
+ @old_api_key = ENV["PHANTOMJS_API_KEY"]
+ end
+
+ teardown do
+ ENV["PHANTOMJS_API_KEY"] = @old_api_key
+ end
+
+ test "builds wringer-compatible phantomjs request url" do
+ ENV["PHANTOMJS_API_KEY"] = "secret"
+
+ url = Distillator::Renderers::LegacyPhantomjsRenderer.request_url(url: "https://example.com/page", iframe: true)
+
+ assert_equal(
+ "https://phantomjscloud.com/api/browser/v2/secret/?request={url:%22https://example.com/page%22,renderType:%22html%22,outputAsJson:true,requestSettings:{ignoreImages:true, waitInterval:2500 }}",
+ url
+ )
+ end
+
+ test "extracts first child iframe html from phantomjs json payload" do
+ ENV["PHANTOMJS_API_KEY"] = "secret"
+ Distillator::NativeFetch.expects(:call).returns(
+ status: :ok,
+ body: '{"pageResponses":[{"frameData":{"childFrames":[{"content":"iframe child"}]}}]}',
+ headers: { content_type: "application/json" },
+ final_url: "https://phantomjscloud.com/api/browser/v2/secret",
+ redirect_chain: [],
+ wringer: { signals: {}, hints: [] },
+ http_code: 200,
+ raw_body: '{"pageResponses":[{"frameData":{"childFrames":[{"content":"iframe child"}]}}]}'
+ )
+
+ result = Distillator::Renderers::LegacyPhantomjsRenderer.call(
+ url: "https://example.com/iframe",
+ uri_key: CGI.escape("https://example.com/iframe"),
+ iframe: true
+ )
+
+ assert_equal "iframe child", result[:body]
+ assert_equal "iframe child", result[:raw_body]
+ assert_equal "https://example.com/iframe", result[:final_url]
+ assert_equal "legacy_phantomjs", result.dig(:wringer, :signals, :renderer)
+ end
+
+ test "missing phantomjs api key returns renderer unavailable abort by default" do
+ ENV["PHANTOMJS_API_KEY"] = nil
+ Distillator::NativeFetch.expects(:call).never
+
+ result = Distillator::Renderers::LegacyPhantomjsRenderer.call(
+ url: "https://example.com/page",
+ uri_key: CGI.escape("https://example.com/page"),
+ iframe: false
+ )
+
+ assert_equal :abort, result[:status]
+ assert_equal "abort_update", result[:body].first
+ assert_equal "DistillatorRendererUnavailable", result[:body].last[:error_type]
+ assert_equal false, result.dig(:wringer, :cache)
+ assert_equal true, result.dig(:wringer, :retry)
+ assert_equal "legacy_phantomjs", result.dig(:wringer, :signals, :renderer)
+ assert_equal true, result.dig(:wringer, :signals, :renderer_unavailable)
+ assert_equal "none", result.dig(:wringer, :signals, :renderer_fallback)
+ end
+end
diff --git a/test/services/distillator/rollout_copy_test.rb b/test/services/distillator/rollout_copy_test.rb
new file mode 100644
index 00000000..b9bf071b
--- /dev/null
+++ b/test/services/distillator/rollout_copy_test.rb
@@ -0,0 +1,69 @@
+require "test_helper"
+
+class Distillator::RolloutCopyTest < ActiveSupport::TestCase
+ test "provides centralized canonical rollout labels descriptions and cache action labels" do
+ assert_equal "Legacy", Distillator::RolloutCopy.label(:legacy)
+ assert_equal "Shadow", Distillator::RolloutCopy.label(:shadow)
+ assert_equal "Active", Distillator::RolloutCopy.label(:active)
+ assert_equal "Production mode", Distillator::RolloutCopy.rollout_panel_title
+
+ assert_equal "Wringer serves production.", Distillator::RolloutCopy.description(:legacy)
+ assert_equal "Wringer serves production while Condenser is checked in the background.", Distillator::RolloutCopy.description(:shadow)
+ assert_equal "Condenser serves production while Wringer stays available for diagnostics.", Distillator::RolloutCopy.description(:active)
+
+ assert_equal "Open active cache", Distillator::RolloutCopy.active_cache_label
+ assert_equal "Open Condenser cache", Distillator::RolloutCopy.condenser_cache_label
+ assert_equal "Inspect legacy Wringer", Distillator::RolloutCopy.legacy_inspection_label
+ assert_equal "Compare Condenser vs Wringer", Distillator::RolloutCopy.compare_label
+ end
+
+ test "separates production state copy from diagnostic state copy" do
+ assert_equal %i[legacy shadow active], Distillator::RolloutCopy.production_states.keys
+ assert_equal %i[replay unknown], Distillator::RolloutCopy.diagnostic_states.keys
+ end
+
+ test "normalizes active and internal to the same operator-facing rollout state" do
+ assert_equal :active, Distillator::RolloutCopy.normalize(:active)
+ assert_equal :active, Distillator::RolloutCopy.normalize(:internal)
+ assert_equal :unknown, Distillator::RolloutCopy.normalize(:bogus)
+ end
+
+ test "provides canonical website form option copy" do
+ assert_equal [
+ ["Legacy", "legacy"],
+ ["Shadow", "shadow"],
+ ["Active", "active"]
+ ], Distillator::RolloutCopy.website_form_options
+ end
+
+ test "website index filter options exclude replay" do
+ values = Distillator::RolloutCopy.website_index_filter_options.map(&:last)
+
+ refute_includes values, "replay"
+ assert_includes values, "legacy"
+ assert_includes values, "shadow"
+ assert_includes values, "active"
+ end
+
+ test "operator rollout copy avoids internal and phased rollout wording" do
+ operator_copy = [
+ Distillator::RolloutCopy.rollout_panel_title,
+ Distillator::RolloutCopy.label(:legacy),
+ Distillator::RolloutCopy.label(:shadow),
+ Distillator::RolloutCopy.label(:active),
+ Distillator::RolloutCopy.description(:legacy),
+ Distillator::RolloutCopy.description(:shadow),
+ Distillator::RolloutCopy.description(:active),
+ Distillator::RolloutCopy.next_step(:legacy),
+ Distillator::RolloutCopy.next_step(:shadow),
+ Distillator::RolloutCopy.next_step(:active)
+ ].join(" ").downcase
+
+ refute_includes operator_copy, "internal"
+ refute_includes operator_copy, "replay"
+ refute_includes operator_copy, "new cache"
+ refute_includes operator_copy, "phase"
+ refute_includes operator_copy, "preview"
+ refute_includes operator_copy, "distillator rollout"
+ end
+end
diff --git a/test/services/distillator/rollout_event_recorder_test.rb b/test/services/distillator/rollout_event_recorder_test.rb
new file mode 100644
index 00000000..42e54736
--- /dev/null
+++ b/test/services/distillator/rollout_event_recorder_test.rb
@@ -0,0 +1,61 @@
+require "test_helper"
+
+class Distillator::RolloutEventRecorderTest < ActiveSupport::TestCase
+ test "creates an event when mode changes" do
+ website = websites(:one)
+
+ assert_difference("Distillator::RolloutEvent.count", 1) do
+ Distillator::RolloutEventRecorder.call(
+ website: website,
+ from_mode: "shadow",
+ to_mode: "active",
+ actor: "127.0.0.1",
+ readiness_snapshot: { blockers: ["missing_export_diff"], warnings: [] }
+ )
+ end
+
+ assert_equal "rollout.transition", Distillator::RolloutEvent.order(:created_at).last.readiness_snapshot["event"]
+ end
+
+ test "skips no-op mode changes" do
+ website = websites(:one)
+
+ assert_no_difference("Distillator::RolloutEvent.count") do
+ Distillator::RolloutEventRecorder.call(
+ website: website,
+ from_mode: "shadow",
+ to_mode: "shadow"
+ )
+ end
+ end
+
+ test "marks active to legacy as a rollback event" do
+ website = websites(:one)
+
+ Distillator::RolloutEventRecorder.call(
+ website: website,
+ from_mode: "active",
+ to_mode: "legacy"
+ )
+
+ assert_equal "rollout.rollback", Distillator::RolloutEvent.order(:created_at).last.readiness_snapshot["event"]
+ end
+
+ test "keeps explicit override event when provided" do
+ website = websites(:one)
+
+ Distillator::RolloutEventRecorder.call(
+ website: website,
+ from_mode: "shadow",
+ to_mode: "active",
+ reason: "Manual inspection",
+ readiness_snapshot: { override: true, blockers: [], warnings: ["Needs review: export check is missing."] },
+ event: "rollout.override"
+ )
+
+ event = Distillator::RolloutEvent.order(:created_at).last
+ assert_equal "rollout.override", event.readiness_snapshot["event"]
+ assert_equal true, event.readiness_snapshot["override"]
+ assert_equal "Manual inspection", event.reason
+ end
+end
diff --git a/test/services/distillator/rollout_transition_test.rb b/test/services/distillator/rollout_transition_test.rb
new file mode 100644
index 00000000..594d42e2
--- /dev/null
+++ b/test/services/distillator/rollout_transition_test.rb
@@ -0,0 +1,408 @@
+require "test_helper"
+
+class Distillator::RolloutTransitionTest < ActiveSupport::TestCase
+ setup do
+ @old_override_flag = ENV["DISTILLATOR_ALLOW_ACTIVE_OVERRIDE"]
+ @old_heroku_app_name = ENV["HEROKU_APP_NAME"]
+ @old_app_name = ENV["APP_NAME"]
+ @old_heroku_parent_app_name = ENV["HEROKU_PARENT_APP_NAME"]
+ @old_heroku_slug_commit = ENV["HEROKU_SLUG_COMMIT"]
+ end
+
+ teardown do
+ ENV["DISTILLATOR_ALLOW_ACTIVE_OVERRIDE"] = @old_override_flag
+ ENV["HEROKU_APP_NAME"] = @old_heroku_app_name
+ ENV["APP_NAME"] = @old_app_name
+ ENV["HEROKU_PARENT_APP_NAME"] = @old_heroku_parent_app_name
+ ENV["HEROKU_SLUG_COMMIT"] = @old_heroku_slug_commit
+ end
+
+ test "blocks direct legacy to active by default" do
+ website = build_website("legacy")
+
+ result = Distillator::RolloutTransition.call(website: website, to_mode: "active", actor: "test")
+
+ assert_equal false, result.success?
+ assert_equal "legacy", website.reload.distillator_mode
+ assert_includes result.errors, "Direct legacy to active promotion is blocked"
+ end
+
+ test "blocks shadow to active when status is review" do
+ website = build_website("shadow")
+ url = "https://example.org/review"
+ website.webpages.create!(url: url, language: "en", rdf_uri: "rdf:review", rdfs_class: rdfs_classes(:one))
+ Distillator::FetchCache.create!(
+ uri_key: CGI.escape(url),
+ normalized_url: url,
+ html: "ok",
+ body: "ok",
+ scrape_date: 1.hour.ago,
+ successful_refresh: 1.hour.ago,
+ headers: {},
+ signals: { "transport_success" => true, "content_success" => true },
+ final_url: url
+ )
+
+ result = Distillator::RolloutTransition.call(website: website, to_mode: "active", actor: "test")
+
+ assert_equal false, result.success?
+ assert_includes result.errors, "Cannot activate yet: statements check is missing."
+ end
+
+ test "allows shadow to active when status is ready" do
+ website = build_website("shadow", seedurl: "hector-charland-com")
+ url = "https://example.org/ready"
+ website.webpages.create!(url: url, language: "en", rdf_uri: "rdf:ready", rdfs_class: rdfs_classes(:one))
+ Distillator::FetchCache.create!(
+ uri_key: CGI.escape(url),
+ normalized_url: url,
+ html: "ok",
+ body: "ok",
+ scrape_date: 1.hour.ago,
+ successful_refresh: 1.hour.ago,
+ headers: {},
+ signals: { "transport_success" => true, "content_success" => true, "statement_count_delta_acceptable" => true, "export_diff_checked" => true },
+ final_url: url
+ )
+ website.transition_evidences.create!(
+ url: url,
+ check_kind: "statement_delta",
+ status: "checked",
+ statement_count_delta_acceptable: true,
+ checked_at: 1.hour.ago
+ )
+ website.transition_evidences.create!(
+ url: url,
+ check_kind: "export_diff",
+ status: "checked",
+ export_diff_checked: true,
+ checked_at: 1.hour.ago
+ )
+
+ result = Distillator::RolloutTransition.call(website: website, to_mode: "active", actor: "test")
+
+ assert_equal true, result.success?
+ assert_equal "active", website.reload.distillator_mode
+ end
+
+ test "allows shadow to active after review when only review-needed parity differences remain" do
+ website = build_website("shadow", seedurl: "review-activate")
+ url = "https://example.org/review-activate"
+ website.webpages.create!(url: url, language: "en", rdf_uri: "rdf:review-activate", rdfs_class: rdfs_classes(:one))
+ Distillator::FetchCache.create!(
+ uri_key: CGI.escape(url),
+ normalized_url: url,
+ html: "ok",
+ body: "ok",
+ scrape_date: 1.hour.ago,
+ successful_refresh: 1.hour.ago,
+ headers: {},
+ signals: { "transport_success" => true, "content_success" => true },
+ final_url: url
+ )
+ website.transition_evidences.create!(
+ url: url,
+ check_kind: "fetch_parity",
+ status: "checked",
+ checked_at: 1.hour.ago,
+ details: { reason: "review_needed_difference", comparison_policy: "operator", compare_summary: { review_needed_diffs: ["html_sha256"] } }
+ )
+ website.transition_evidences.create!(url: url, check_kind: "statement_delta", status: "checked", statement_count_delta_acceptable: true, checked_at: 1.hour.ago)
+ website.transition_evidences.create!(url: url, check_kind: "export_diff", status: "checked", export_diff_checked: true, checked_at: 1.hour.ago)
+
+ result = Distillator::RolloutTransition.call(website: website, to_mode: "active", actor: "test", reason: "Manual review complete", review: true)
+
+ assert_equal true, result.success?
+ assert_equal "active", website.reload.distillator_mode
+ event = website.rollout_events.order(:created_at).last
+ assert_equal "rollout.review_activate", event.readiness_snapshot["event"]
+ assert_equal true, event.readiness_snapshot["review_activation"]
+ assert_equal ["html_sha256"], event.readiness_snapshot["review_needed_fields"]
+ assert_equal "review", event.readiness_snapshot["safety"]
+ assert_equal "low", event.readiness_snapshot["confidence"]
+ assert_equal "operator", event.readiness_snapshot["comparison_policy"]
+ end
+
+ test "rejects activate after review when legacy body was omitted" do
+ website = build_website("shadow", seedurl: "review-body-omitted")
+ url = "https://example.org/review-body-omitted"
+ website.webpages.create!(url: url, language: "en", rdf_uri: "rdf:review-body-omitted", rdfs_class: rdfs_classes(:one))
+ Distillator::FetchCache.create!(
+ uri_key: CGI.escape(url),
+ normalized_url: url,
+ html: "ok",
+ body: "ok",
+ scrape_date: 1.hour.ago,
+ successful_refresh: 1.hour.ago,
+ headers: {},
+ signals: { "transport_success" => true, "content_success" => true },
+ final_url: url
+ )
+ website.transition_evidences.create!(
+ url: url,
+ check_kind: "fetch_parity",
+ status: "checked",
+ checked_at: 1.hour.ago,
+ details: { reason: "legacy_lookup_body_omitted", comparison_policy: "operator" }
+ )
+ website.transition_evidences.create!(url: url, check_kind: "statement_delta", status: "checked", statement_count_delta_acceptable: true, checked_at: 1.hour.ago)
+ website.transition_evidences.create!(url: url, check_kind: "export_diff", status: "checked", export_diff_checked: true, checked_at: 1.hour.ago)
+
+ result = Distillator::RolloutTransition.call(
+ website: website,
+ to_mode: "active",
+ actor: "test",
+ reason: "Manual review complete",
+ review: true
+ )
+
+ assert_equal false, result.success?
+ assert_equal "shadow", website.reload.distillator_mode
+ assert_includes result.errors, "Activate after review is available only for review-needed parity differences."
+ end
+
+ test "allows active rollback to legacy even when readiness would fail" do
+ website = build_website("active", seedurl: "hector-charland-com")
+
+ result = Distillator::RolloutTransition.call(website: website, to_mode: "legacy", actor: "test")
+
+ assert_equal true, result.success?
+ assert_equal "legacy", website.reload.distillator_mode
+ end
+
+ test "allows invalid internal mode to move to shadow for staging repair" do
+ website = build_website("legacy", seedurl: "repair-internal-transition")
+ website.update_column(:distillator_mode, "internal")
+
+ result = Distillator::RolloutTransition.call(website: website, to_mode: "shadow", actor: "test")
+
+ assert_equal true, result.success?
+ assert_equal "shadow", website.reload.distillator_mode
+ end
+
+ test "activate anyway succeeds when explicit override flag is enabled" do
+ ENV["DISTILLATOR_ALLOW_ACTIVE_OVERRIDE"] = "true"
+ Rails.stubs(:env).returns(ActiveSupport::StringInquirer.new("production"))
+ website = build_website("legacy", seedurl: "override-flag")
+
+ result = Distillator::RolloutTransition.call(
+ website: website,
+ to_mode: "active",
+ actor: "test",
+ reason: "Manual check complete",
+ override: true
+ )
+
+ assert_equal true, result.success?
+ assert_equal "active", website.reload.distillator_mode
+ ensure
+ Rails.unstub(:env)
+ end
+
+ test "activate anyway succeeds on known staging heroku app" do
+ ENV["HEROKU_APP_NAME"] = "footlight-condenser-test-c24c162bb7c8"
+ Rails.stubs(:env).returns(ActiveSupport::StringInquirer.new("production"))
+ website = build_website("shadow", seedurl: "override-staging")
+
+ result = Distillator::RolloutTransition.call(
+ website: website,
+ to_mode: "active",
+ actor: "test",
+ reason: "Staging acceptance",
+ override: true
+ )
+
+ assert_equal true, result.success?
+ assert_equal "active", website.reload.distillator_mode
+ ensure
+ Rails.unstub(:env)
+ end
+
+ test "activate anyway succeeds when allow listed app name is present in APP_NAME" do
+ ENV["APP_NAME"] = "footlight-condenser-test-c24c162bb7c8"
+ Rails.stubs(:env).returns(ActiveSupport::StringInquirer.new("production"))
+ website = build_website("shadow", seedurl: "override-app-name")
+
+ result = Distillator::RolloutTransition.call(
+ website: website,
+ to_mode: "active",
+ actor: "test",
+ reason: "Allow listed app name",
+ override: true
+ )
+
+ assert_equal true, result.success?
+ assert_equal "active", website.reload.distillator_mode
+ ensure
+ Rails.unstub(:env)
+ end
+
+ test "activate anyway succeeds when allow listed parent app name is present" do
+ ENV["HEROKU_PARENT_APP_NAME"] = "footlight-condenser-test-c24c162bb7c8"
+ Rails.stubs(:env).returns(ActiveSupport::StringInquirer.new("production"))
+ website = build_website("shadow", seedurl: "override-parent-app")
+
+ result = Distillator::RolloutTransition.call(
+ website: website,
+ to_mode: "active",
+ actor: "test",
+ reason: "Allow listed parent app",
+ override: true
+ )
+
+ assert_equal true, result.success?
+ assert_equal "active", website.reload.distillator_mode
+ ensure
+ Rails.unstub(:env)
+ end
+
+ test "activate anyway fails outside allowed runtime when env flag is absent" do
+ ENV["DISTILLATOR_ALLOW_ACTIVE_OVERRIDE"] = nil
+ ENV["HEROKU_APP_NAME"] = nil
+ ENV["APP_NAME"] = nil
+ ENV["HEROKU_PARENT_APP_NAME"] = nil
+ Rails.stubs(:env).returns(ActiveSupport::StringInquirer.new("production"))
+ website = build_website("shadow", seedurl: "override-blocked")
+
+ result = Distillator::RolloutTransition.call(
+ website: website,
+ to_mode: "active",
+ actor: "test",
+ reason: "Not allowed here",
+ override: true
+ )
+
+ assert_equal false, result.success?
+ assert_equal "shadow", website.reload.distillator_mode
+ assert_includes result.errors, "Activate anyway is not allowed in this runtime"
+ ensure
+ Rails.unstub(:env)
+ end
+
+ test "HEROKU_SLUG_COMMIT containing test does not allow override" do
+ ENV["DISTILLATOR_ALLOW_ACTIVE_OVERRIDE"] = nil
+ ENV["HEROKU_APP_NAME"] = nil
+ ENV["APP_NAME"] = nil
+ ENV["HEROKU_PARENT_APP_NAME"] = nil
+ ENV["HEROKU_SLUG_COMMIT"] = "build-test-commit"
+ Rails.stubs(:env).returns(ActiveSupport::StringInquirer.new("production"))
+ website = build_website("shadow", seedurl: "override-slug-commit")
+
+ result = Distillator::RolloutTransition.call(
+ website: website,
+ to_mode: "active",
+ actor: "test",
+ reason: "Should stay blocked",
+ override: true
+ )
+
+ assert_equal false, result.success?
+ assert_equal "shadow", website.reload.distillator_mode
+ assert_includes result.errors, "Activate anyway is not allowed in this runtime"
+ ensure
+ Rails.unstub(:env)
+ end
+
+ test "random app name containing test does not allow override unless allow listed" do
+ ENV["DISTILLATOR_ALLOW_ACTIVE_OVERRIDE"] = nil
+ ENV["APP_NAME"] = "my-test-production-app"
+ ENV["HEROKU_APP_NAME"] = nil
+ ENV["HEROKU_PARENT_APP_NAME"] = nil
+ Rails.stubs(:env).returns(ActiveSupport::StringInquirer.new("production"))
+ website = build_website("shadow", seedurl: "override-random-app-name")
+
+ result = Distillator::RolloutTransition.call(
+ website: website,
+ to_mode: "active",
+ actor: "test",
+ reason: "Should stay blocked",
+ override: true
+ )
+
+ assert_equal false, result.success?
+ assert_equal "shadow", website.reload.distillator_mode
+ assert_includes result.errors, "Activate anyway is not allowed in this runtime"
+ ensure
+ Rails.unstub(:env)
+ end
+
+ test "activate anyway records override reason blockers and warnings" do
+ website = build_website("shadow", seedurl: "override-record")
+ url = "https://example.org/override-record"
+ website.webpages.create!(url: url, language: "en", rdf_uri: "rdf:override-record", rdfs_class: rdfs_classes(:one))
+ Distillator::FetchCache.create!(
+ uri_key: CGI.escape(url),
+ normalized_url: url,
+ html: "ok",
+ body: "ok",
+ scrape_date: 1.hour.ago,
+ successful_refresh: 1.hour.ago,
+ headers: {},
+ signals: { "transport_success" => true, "content_success" => true },
+ final_url: url
+ )
+
+ result = Distillator::RolloutTransition.call(
+ website: website,
+ to_mode: "active",
+ actor: "test",
+ reason: " Manual inspection complete ",
+ override: true
+ )
+
+ assert_equal true, result.success?
+ event = website.rollout_events.order(:created_at).last
+ assert_equal "Manual inspection complete", event.reason
+ assert_equal true, event.readiness_snapshot["override"]
+ assert_equal "rollout.override", event.readiness_snapshot["event"]
+ assert_includes event.readiness_snapshot["warnings"], "Needs review: statements check is missing."
+ assert_equal [], event.readiness_snapshot["blockers"]
+ end
+
+ test "activate anyway records current blockers when readiness is blocked" do
+ website = Website.create!(
+ name: "Tout Culture",
+ seedurl: "outside-seed",
+ graph_name: "https://example.org/outside-seed",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+ url = "https://example.org/override-blockers"
+ website.webpages.create!(url: url, language: "en", rdf_uri: "rdf:override-blockers", rdfs_class: rdfs_classes(:one))
+ Distillator::FetchCache.create!(
+ uri_key: CGI.escape(url),
+ normalized_url: url,
+ html: "ok",
+ body: "ok",
+ scrape_date: 1.hour.ago,
+ successful_refresh: 1.hour.ago,
+ headers: {},
+ signals: { "transport_success" => true, "content_success" => true, "statement_count_delta_acceptable" => true },
+ final_url: url
+ )
+
+ result = Distillator::RolloutTransition.call(
+ website: website,
+ to_mode: "active",
+ actor: "test",
+ reason: "Staging override",
+ override: true
+ )
+
+ assert_equal true, result.success?
+ event = website.rollout_events.order(:created_at).last
+ assert_includes event.readiness_snapshot["blockers"], "Cannot activate yet: export check is missing."
+ end
+
+ private
+
+ def build_website(mode, seedurl: "rollout-transition")
+ Website.create!(
+ name: "Rollout transition #{mode}",
+ seedurl: seedurl,
+ graph_name: "https://example.org/#{seedurl}",
+ default_language: "en",
+ distillator_mode: mode
+ )
+ end
+end
diff --git a/test/services/distillator/shadow_report_query_test.rb b/test/services/distillator/shadow_report_query_test.rb
new file mode 100644
index 00000000..384068b5
--- /dev/null
+++ b/test/services/distillator/shadow_report_query_test.rb
@@ -0,0 +1,309 @@
+require "test_helper"
+
+class Distillator::ShadowReportQueryTest < ActiveSupport::TestCase
+ test "defaults to a bounded shadow-only report" do
+ 30.times do |index|
+ create_shadow_site(name: format("Shadow %02d", index), seedurl: "shadow-#{index}", recommendation: :ready)
+ end
+ create_website(name: "Legacy site", seedurl: "legacy-site", mode: "legacy")
+ create_website(name: "Active site", seedurl: "active-site", mode: "active")
+
+ result = Distillator::ShadowReportQuery.call(
+ filters: {},
+ sort: Distillator::ShadowReportQuery::DEFAULT_SORT,
+ direction: Distillator::ShadowReportQuery::DEFAULT_DIRECTION,
+ page: 1,
+ per_page: Distillator::ShadowReportQuery::DEFAULT_LIMIT
+ )
+
+ names = result.records.map { |row| row.website.name }
+
+ assert_equal Distillator::ShadowReportQuery::DEFAULT_LIMIT, result.records.length
+ assert_equal 30, result.total_count
+ assert_includes names, "Shadow 00"
+ refute_includes names, "Legacy site"
+ refute_includes names, "Active site"
+ end
+
+ test "limit cannot exceed the maximum" do
+ 101.times do |index|
+ create_shadow_site(name: format("Max %03d", index), seedurl: "max-#{index}", recommendation: :ready)
+ end
+
+ result = Distillator::ShadowReportQuery.call(
+ filters: {},
+ sort: Distillator::ShadowReportQuery::DEFAULT_SORT,
+ direction: Distillator::ShadowReportQuery::DEFAULT_DIRECTION,
+ page: 1,
+ per_page: 999
+ )
+
+ assert_equal Distillator::ShadowReportQuery::MAX_LIMIT, result.per_page
+ assert_equal Distillator::ShadowReportQuery::MAX_LIMIT, result.records.length
+ assert_equal 101, result.total_count
+ end
+
+ test "default report paginates many shadow sites without showing them all on page one" do
+ 40.times do |index|
+ create_shadow_site(name: format("Paged %02d", index), seedurl: "visible-#{index}", recommendation: :ready)
+ end
+
+ page_one = Distillator::ShadowReportQuery.call(
+ filters: {},
+ sort: Distillator::ShadowReportQuery::DEFAULT_SORT,
+ direction: Distillator::ShadowReportQuery::DEFAULT_DIRECTION,
+ page: 1,
+ per_page: Distillator::ShadowReportQuery::DEFAULT_LIMIT
+ )
+ page_two = Distillator::ShadowReportQuery.call(
+ filters: {},
+ sort: Distillator::ShadowReportQuery::DEFAULT_SORT,
+ direction: Distillator::ShadowReportQuery::DEFAULT_DIRECTION,
+ page: 2,
+ per_page: Distillator::ShadowReportQuery::DEFAULT_LIMIT
+ )
+
+ assert_equal 25, page_one.records.length
+ assert_equal 15, page_two.records.length
+ refute_equal page_one.records.map { |row| row.website.name }, page_two.records.map { |row| row.website.name }
+ end
+
+ test "filters by recommendation, health severity, issue key, and search term" do
+ create_shadow_site(name: "Ready site", seedurl: "ready-site", recommendation: :ready, issue_key: nil, health_severity: "ok")
+ create_shadow_site(name: "Blocked site", seedurl: "blocked-site", recommendation: :blocked, issue_key: "timeout", health_severity: "high")
+ create_shadow_site(name: "Review queue", seedurl: "review-site", recommendation: :review, issue_key: "queue_it", health_severity: "low")
+
+ filtered = Distillator::ShadowReportQuery.call(
+ filters: { recommendation: "review", health_severity: "ok", primary_issue_key: "queue_it", term: "queue" },
+ sort: Distillator::ShadowReportQuery::DEFAULT_SORT,
+ direction: Distillator::ShadowReportQuery::DEFAULT_DIRECTION,
+ page: 1,
+ per_page: 25
+ )
+
+ assert_equal ["Review queue"], filtered.records.map { |row| row.website.name }
+ end
+
+ test "filters by promotable recommendation readiness" do
+ ready = create_shadow_site(name: "Promotable queue", seedurl: "promotable-queue", recommendation: :ready)
+ blocked = create_shadow_site(name: "Blocked queue", seedurl: "blocked-promotable-queue", recommendation: :blocked, issue_key: "timeout", health_severity: "high")
+
+ add_promotion_evidence(ready, checked_at: 1.hour.ago, statement_delta: true, export_diff: true)
+
+ promotable = Distillator::ShadowReportQuery.call(
+ filters: { promotable: "yes", term: "queue" },
+ sort: Distillator::ShadowReportQuery::DEFAULT_SORT,
+ direction: Distillator::ShadowReportQuery::DEFAULT_DIRECTION,
+ page: 1,
+ per_page: 25
+ )
+ not_promotable = Distillator::ShadowReportQuery.call(
+ filters: { promotable: "no", term: "queue" },
+ sort: Distillator::ShadowReportQuery::DEFAULT_SORT,
+ direction: Distillator::ShadowReportQuery::DEFAULT_DIRECTION,
+ page: 1,
+ per_page: 25
+ )
+
+ assert_equal ["Promotable queue"], promotable.records.map { |row| row.website.name }
+ assert_equal ["Blocked queue"], not_promotable.records.map { |row| row.website.name }
+ end
+
+ test "filters by safety and confidence independently" do
+ safe = create_shadow_site(name: "Safe confidence high", seedurl: "safe-confidence-high", recommendation: :ready)
+ add_promotion_evidence(safe, checked_at: 1.hour.ago, statement_delta: true, export_diff: true)
+
+ review = create_shadow_site(name: "Review confidence low", seedurl: "review-confidence-low", recommendation: :review, issue_key: "queue_it", health_severity: "low")
+ review_url = "https://review-confidence-low.example/event"
+ review.transition_evidences.create!(
+ id: next_id,
+ url: review_url,
+ check_kind: "fetch_parity",
+ status: "checked",
+ checked_at: 1.hour.ago,
+ details: { reason: "review_needed_difference", comparison_policy: "operator", compare_summary: { review_needed_diffs: ["html_sha256"] } }
+ )
+ add_promotion_evidence(review, checked_at: 1.hour.ago, statement_delta: true, export_diff: true)
+
+ filtered = Distillator::ShadowReportQuery.call(
+ filters: { safety: "review", confidence: "low", term: "confidence" },
+ sort: Distillator::ShadowReportQuery::DEFAULT_SORT,
+ direction: Distillator::ShadowReportQuery::DEFAULT_DIRECTION,
+ page: 1,
+ per_page: 25
+ )
+
+ assert_equal ["Review confidence low"], filtered.records.map { |row| row.website.name }
+ end
+
+ test "sorts by website, recommendation, latest attempt, latest successful refresh, and issue key" do
+ create_shadow_site(name: "Sort fixture Zulu ready", seedurl: "sort-fixture-zulu-ready", recommendation: :ready, scrape_date: 3.hours.ago, successful_refresh: 3.hours.ago)
+ create_shadow_site(name: "Sort fixture Alpha blocked", seedurl: "sort-fixture-alpha-blocked", recommendation: :blocked, issue_key: "timeout", scrape_date: 1.hour.ago, successful_refresh: 5.hours.ago)
+ create_shadow_site(name: "Sort fixture Beta review", seedurl: "sort-fixture-beta-review", recommendation: :review, issue_key: "queue_it", scrape_date: 2.hours.ago, successful_refresh: 2.hours.ago)
+
+ by_recommendation = Distillator::ShadowReportQuery.call(filters: { term: "Sort fixture" }, sort: "recommendation", direction: "asc", page: 1, per_page: 25)
+ assert_equal ["Sort fixture Alpha blocked", "Sort fixture Beta review", "Sort fixture Zulu ready"], by_recommendation.records.map { |row| row.website.name }
+
+ by_latest_attempt = Distillator::ShadowReportQuery.call(filters: { term: "Sort fixture" }, sort: "latest_attempt", direction: "desc", page: 1, per_page: 25)
+ assert_equal ["Sort fixture Alpha blocked", "Sort fixture Beta review", "Sort fixture Zulu ready"], by_latest_attempt.records.map { |row| row.website.name }
+
+ by_latest_success = Distillator::ShadowReportQuery.call(filters: { term: "Sort fixture" }, sort: "latest_successful_refresh", direction: "desc", page: 1, per_page: 25)
+ assert_equal ["Sort fixture Beta review", "Sort fixture Zulu ready", "Sort fixture Alpha blocked"], by_latest_success.records.map { |row| row.website.name }
+
+ by_issue_key = Distillator::ShadowReportQuery.call(filters: { term: "Sort fixture" }, sort: "issue_key", direction: "asc", page: 1, per_page: 25)
+ assert_equal ["Sort fixture Zulu ready", "Sort fixture Beta review", "Sort fixture Alpha blocked"], by_issue_key.records.map { |row| row.website.name }
+ end
+
+ test "paginates rows" do
+ 3.times do |index|
+ create_shadow_site(name: "Paged #{index}", seedurl: "paged-#{index}", recommendation: :ready)
+ end
+
+ page_one = Distillator::ShadowReportQuery.call(filters: { term: "Paged" }, sort: "website", direction: "asc", page: 1, per_page: 2)
+ page_two = Distillator::ShadowReportQuery.call(filters: { term: "Paged" }, sort: "website", direction: "asc", page: 2, per_page: 2)
+
+ assert_equal 2, page_one.records.length
+ assert_equal 1, page_two.records.length
+ assert_equal 3, page_one.total_count
+ assert_equal 2, page_one.total_pages
+ assert_includes page_one.records.map { |row| row.website.name } + page_two.records.map { |row| row.website.name }, "Paged 0"
+ assert_includes page_one.records.map { |row| row.website.name } + page_two.records.map { |row| row.website.name }, "Paged 1"
+ assert_includes page_one.records.map { |row| row.website.name } + page_two.records.map { |row| row.website.name }, "Paged 2"
+ end
+
+ test "latest cache rows are loaded without html or body blobs" do
+ create_shadow_site(
+ name: "Blob guard",
+ seedurl: "blob-guard",
+ recommendation: :ready,
+ html: "blob",
+ body: "blob body"
+ )
+
+ result = Distillator::ShadowReportQuery.call(filters: {}, sort: "website", direction: "asc", page: 1, per_page: 25)
+ cache = result.records.first.cache
+
+ assert cache.present?
+ assert_raises(ActiveModel::MissingAttributeError) { cache.html }
+ assert_raises(ActiveModel::MissingAttributeError) { cache.body }
+ end
+
+ test "cache with trailing slash difference still maps to the website" do
+ website = create_website(name: "Slash site", seedurl: "slash-site", mode: "shadow")
+ website.webpages.create!(
+ id: next_id,
+ url: "https://slash-site.example/event/",
+ language: "en",
+ rdf_uri: "adr:slash-site",
+ rdfs_class: rdfs_classes(:one)
+ )
+ Distillator::FetchCache.create!(
+ id: next_id,
+ uri_key: CGI.escape("https://slash-site.example/event"),
+ normalized_url: "https://slash-site.example/event",
+ html: "cached",
+ body: "cached",
+ scrape_date: 1.hour.ago,
+ successful_refresh: 1.hour.ago,
+ headers: {},
+ signals: { "transport_success" => true, "content_success" => true, "export_diff_checked" => true },
+ final_url: "https://slash-site.example/event"
+ )
+
+ result = Distillator::ShadowReportQuery.call(filters: {}, sort: "website", direction: "asc", page: 1, per_page: 25)
+
+ assert_includes result.records.map { |row| row.website.name }, "Slash site"
+ end
+
+ private
+
+ def create_website(name:, seedurl:, mode:)
+ Website.create!(
+ id: next_id,
+ name: name,
+ seedurl: seedurl,
+ graph_name: "https://#{seedurl}.example/graph",
+ default_language: "en",
+ distillator_mode: mode
+ )
+ end
+
+ def create_shadow_site(name:, seedurl:, recommendation:, issue_key: nil, health_severity: "ok", scrape_date: 1.hour.ago, successful_refresh: 1.hour.ago, html: "cached", body: "cached")
+ website = create_website(name: name, seedurl: seedurl, mode: "shadow")
+ url = "https://#{seedurl}.example/event"
+ website.webpages.create!(
+ id: next_id,
+ url: url,
+ language: "en",
+ rdf_uri: "adr:#{seedurl}",
+ rdfs_class: rdfs_classes(:one)
+ )
+
+ attrs = {
+ uri_key: CGI.escape(url),
+ normalized_url: url,
+ name: name,
+ html: html,
+ body: body,
+ http_response_code: 200,
+ scrape_date: scrape_date,
+ successful_refresh: successful_refresh,
+ headers: {},
+ signals: {
+ "transport_success" => true,
+ "content_success" => true,
+ "primary_issue_key" => issue_key
+ }.compact,
+ hints: Array(issue_key).compact,
+ final_url: url,
+ redirect_chain: [],
+ health_status: "healthy",
+ health_severity: health_severity,
+ primary_issue_key: issue_key,
+ primary_issue_label: issue_key&.humanize,
+ primary_issue_severity: issue_key.present? ? (recommendation == :blocked ? "failed" : "warning") : nil
+ }
+
+ attrs[:signals] = attrs[:signals].merge("export_diff_checked" => true, "statement_count_delta_acceptable" => true) if recommendation == :ready
+
+ case recommendation
+ when :blocked
+ attrs[:health_status] = "attempt_failed"
+ attrs[:signals] = { "transport_success" => false, "content_success" => false, "primary_issue_key" => issue_key, "primary_issue_severity" => "failed" }.compact
+ when :review
+ attrs[:health_status] = "redirect_changed"
+ attrs[:signals] = { "transport_success" => true, "primary_issue_key" => issue_key, "primary_issue_severity" => "warning" }.compact
+ attrs[:redirected] = true
+ attrs[:final_url] = "#{url}/redirected"
+ end
+
+ Distillator::FetchCache.create!(attrs.merge(id: next_id))
+ website
+ end
+
+ def next_id
+ @next_id ||= 1_000_000_000 + ((Process.pid % 10_000) * 100_000)
+ @next_id += 1
+ end
+
+ def add_promotion_evidence(website, checked_at:, statement_delta:, export_diff:)
+ url = "https://#{website.seedurl}.example/event"
+
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url,
+ check_kind: "statement_delta",
+ status: "checked",
+ statement_count_delta_acceptable: statement_delta,
+ checked_at: checked_at
+ )
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url,
+ check_kind: "export_diff",
+ status: "checked",
+ export_diff_checked: export_diff,
+ checked_at: checked_at
+ )
+ end
+end
diff --git a/test/services/distillator/shadow_report_test.rb b/test/services/distillator/shadow_report_test.rb
new file mode 100644
index 00000000..3db21391
--- /dev/null
+++ b/test/services/distillator/shadow_report_test.rb
@@ -0,0 +1,211 @@
+require "test_helper"
+
+class Distillator::ShadowReportTest < ActiveSupport::TestCase
+ test "report defaults to bounded shadow websites and summarizes the visible rows" do
+ baseline = Distillator::ShadowReport.call(filters: {}, sort: "website", direction: "asc", page: 1, per_page: 100)
+
+ create_shadow_site(name: "A Ready shadow", seedurl: "ready-shadow", status: :ready)
+ create_shadow_site(name: "A Blocked shadow", seedurl: "blocked-shadow", status: :blocked, issue_key: "timeout")
+ create_shadow_site(name: "A Hector Charland", seedurl: "hector-charland-com", status: :review, issue_key: "queue_it", lavitrine: true)
+ create_website(name: "Legacy site", seedurl: "legacy-site", mode: "legacy")
+ create_website(name: "Active site", seedurl: "active-site", mode: "active")
+
+ report = Distillator::ShadowReport.call(filters: {}, sort: "website", direction: "asc", page: 1, per_page: 25)
+
+ assert_includes report.rows.map { |row| row.website.name }, "A Blocked shadow"
+ assert_includes report.rows.map { |row| row.website.name }, "A Hector Charland"
+ assert_includes report.rows.map { |row| row.website.name }, "A Ready shadow"
+ refute_includes report.rows.map { |row| row.website.name }, "Legacy site"
+ refute_includes report.rows.map { |row| row.website.name }, "Active site"
+ assert_equal baseline.summary_counts[:total] + 3, report.summary_counts[:total]
+ assert_equal baseline.summary_counts[:ready] + 1, report.summary_counts[:ready]
+ assert_equal baseline.summary_counts[:review] + 1, report.summary_counts[:review]
+ assert_equal baseline.summary_counts[:blocked] + 1, report.summary_counts[:blocked]
+ assert_equal baseline.summary_counts[:not_checked], report.summary_counts[:not_checked]
+ assert_equal baseline.summary_counts[:lavitrine_total] + 1, report.summary_counts[:lavitrine_total]
+ assert_equal baseline.summary_counts[:lavitrine_review] + 1, report.summary_counts[:lavitrine_review]
+ assert_equal baseline.dashboard_counts[:legacy_sites] + 1, report.dashboard_counts[:legacy_sites]
+ assert_equal baseline.dashboard_counts[:shadow_sites] + 3, report.dashboard_counts[:shadow_sites]
+ assert_equal baseline.dashboard_counts[:active_sites] + 1, report.dashboard_counts[:active_sites]
+ assert_equal baseline.blocker_counts[:failed_fetch] + 1, report.blocker_counts[:failed_fetch]
+ assert_equal baseline.blocker_counts[:missing_statement_evidence] + 1, report.blocker_counts[:missing_statement_evidence]
+ assert_equal baseline.blocker_counts[:missing_export_evidence] + 1, report.blocker_counts[:missing_export_evidence]
+ assert_equal baseline.blocker_counts[:redirect_cache_health_review] + 1, report.blocker_counts[:redirect_cache_health_review]
+ assert report.rows.all? { |row| row.testing_backend == :condenser }
+ end
+
+ test "report supports cohort slicing" do
+ create_shadow_site(name: "Hector Charland", seedurl: "hector-charland-com", status: :review, issue_key: "queue_it", lavitrine: true)
+ create_shadow_site(name: "Other review", seedurl: "other-review", status: :review, issue_key: "queue_it")
+
+ lavitrine = Distillator::ShadowReport.call(
+ filters: { cohort: "lavitrine_pipeline" },
+ sort: "website",
+ direction: "asc",
+ page: 1,
+ per_page: 25
+ )
+ other = Distillator::ShadowReport.call(
+ filters: { cohort: "other" },
+ sort: "website",
+ direction: "asc",
+ page: 1,
+ per_page: 25
+ )
+
+ assert_includes lavitrine.rows.map { |row| row.website.name }, "Hector Charland"
+ refute_includes lavitrine.rows.map { |row| row.website.name }, "Other review"
+ assert_includes other.rows.map { |row| row.website.name }, "Other review"
+ refute_includes other.rows.map { |row| row.website.name }, "Hector Charland"
+ end
+
+ test "summary counts follow the filtered visible rows" do
+ create_shadow_site(name: "Ready shadow", seedurl: "ready-shadow", status: :ready)
+ create_shadow_site(name: "Blocked shadow", seedurl: "blocked-shadow", status: :blocked, issue_key: "timeout")
+ create_website(name: "Legacy site", seedurl: "legacy-site", mode: "legacy")
+
+ report = Distillator::ShadowReport.call(
+ filters: { status: "ready" },
+ sort: "website",
+ direction: "asc",
+ page: 1,
+ per_page: 25
+ )
+
+ assert_equal 1, report.summary_counts[:total]
+ assert_equal 1, report.global_summary_counts[:total]
+ assert_equal 0, report.global_summary_counts[:blocked]
+ end
+
+ test "report supports rollout mode slicing" do
+ create_shadow_site(name: "Shadow site", seedurl: "shadow-site", status: :ready)
+ create_website(name: "Legacy site", seedurl: "legacy-site", mode: "legacy")
+ create_website(name: "Active site", seedurl: "active-site", mode: "active")
+
+ active_report = Distillator::ShadowReport.call(
+ filters: { mode: "active" },
+ sort: "website",
+ direction: "asc",
+ page: 1,
+ per_page: 25
+ )
+
+ assert_equal ["Active site"], active_report.rows.map { |row| row.website.name }
+ end
+
+ test "report supports promotable slicing" do
+ create_shadow_site(name: "Promotable site", seedurl: "promotable-site", status: :ready)
+ create_shadow_site(name: "Blocked site", seedurl: "blocked-site", status: :blocked, issue_key: "timeout")
+
+ yes_report = Distillator::ShadowReport.call(
+ filters: { promotable: "yes", term: "site" },
+ sort: "website",
+ direction: "asc",
+ page: 1,
+ per_page: 25
+ )
+ no_report = Distillator::ShadowReport.call(
+ filters: { promotable: "no", term: "site" },
+ sort: "website",
+ direction: "asc",
+ page: 1,
+ per_page: 25
+ )
+
+ assert_equal ["Promotable site"], yes_report.rows.map { |row| row.website.name }
+ assert_includes no_report.rows.map { |row| row.website.name }, "Blocked site"
+ refute_includes no_report.rows.map { |row| row.website.name }, "Promotable site"
+ end
+
+ private
+
+ def create_website(name:, seedurl:, mode:)
+ Website.create!(
+ id: next_id,
+ name: name,
+ seedurl: seedurl,
+ graph_name: "https://#{seedurl}.example/graph",
+ default_language: "en",
+ distillator_mode: mode
+ )
+ end
+
+ def create_shadow_site(name:, seedurl:, status:, issue_key: nil, lavitrine: false)
+ website = create_website(name: name, seedurl: seedurl, mode: "shadow")
+ url_seed = seedurl
+ url = "https://#{url_seed}.example/event"
+ website.webpages.create!(
+ id: next_id,
+ url: url,
+ language: "en",
+ rdf_uri: "adr:#{url_seed}",
+ rdfs_class: rdfs_classes(:one)
+ )
+
+ attrs = {
+ uri_key: CGI.escape(url),
+ normalized_url: url,
+ name: name,
+ html: "cached",
+ body: "cached",
+ http_response_code: 200,
+ scrape_date: 1.hour.ago,
+ successful_refresh: 1.hour.ago,
+ headers: {},
+ signals: { "transport_success" => true, "content_success" => true, "primary_issue_key" => issue_key }.compact,
+ hints: Array(issue_key).compact,
+ final_url: url,
+ redirect_chain: [],
+ health_status: "healthy",
+ health_severity: "ok",
+ primary_issue_key: issue_key,
+ primary_issue_label: issue_key&.humanize
+ }
+
+ attrs[:signals] = attrs[:signals].merge("export_diff_checked" => true, "statement_count_delta_acceptable" => true) if status == :ready
+
+ if status == :blocked
+ attrs[:health_status] = "attempt_failed"
+ attrs[:health_severity] = "high"
+ attrs[:signals] = { "transport_success" => false, "content_success" => false, "primary_issue_key" => issue_key, "primary_issue_severity" => "failed" }.compact
+ attrs[:primary_issue_severity] = "failed"
+ elsif status == :review
+ attrs[:health_status] = "redirect_changed"
+ attrs[:health_severity] = "low"
+ attrs[:signals] = { "transport_success" => true, "primary_issue_key" => issue_key, "primary_issue_severity" => "warning" }.compact
+ attrs[:primary_issue_severity] = "warning"
+ attrs[:redirected] = true
+ attrs[:final_url] = "#{url}/redirected"
+ end
+
+ if lavitrine
+ attrs[:signals] = attrs[:signals].merge("statement_count_delta_acceptable" => true, "export_diff_checked" => true)
+ end
+
+ Distillator::FetchCache.create!(attrs.merge(id: next_id))
+ if lavitrine
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url,
+ check_kind: "statement_delta",
+ status: "checked",
+ statement_count_delta_acceptable: true,
+ checked_at: 1.hour.ago
+ )
+ website.transition_evidences.create!(
+ id: next_id,
+ url: url,
+ check_kind: "export_diff",
+ status: "checked",
+ export_diff_checked: true,
+ checked_at: 1.hour.ago
+ )
+ end
+ website
+ end
+
+ def next_id
+ @next_id ||= 1_100_000_000 + ((Process.pid % 10_000) * 100_000)
+ @next_id += 1
+ end
+end
diff --git a/test/services/distillator/shadow_site_detail_test.rb b/test/services/distillator/shadow_site_detail_test.rb
new file mode 100644
index 00000000..5270912f
--- /dev/null
+++ b/test/services/distillator/shadow_site_detail_test.rb
@@ -0,0 +1,131 @@
+require "test_helper"
+
+class Distillator::ShadowSiteDetailTest < ActiveSupport::TestCase
+ test "builds read only detail sections without fetching" do
+ website = Website.create!(
+ name: "Hector Charland",
+ seedurl: "hector-charland-com",
+ graph_name: "https://example.org/hector-charland",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+ url = "https://example.org/hector-charland/event"
+ website.webpages.create!(url: url, language: "en", rdf_uri: "rdf:hector", rdfs_class: rdfs_classes(:one))
+ Distillator::FetchCache.create!(
+ uri_key: CGI.escape(url),
+ normalized_url: url,
+ html: "ok",
+ body: "ok",
+ scrape_date: 1.hour.ago,
+ successful_refresh: 1.hour.ago,
+ headers: {},
+ signals: { "transport_success" => true, "content_success" => true },
+ final_url: url
+ )
+
+ detail = Distillator::ShadowSiteDetail.call(website: website)
+
+ assert_equal website, detail.summary.website
+ assert_includes detail.rollout_notes.join(" "), "La Vitrine pipeline"
+ end
+
+ test "root cause prioritizes statement failures over secondary legacy lookup issues" do
+ website = Website.create!(
+ name: "Statement priority",
+ seedurl: "statement-priority",
+ graph_name: "https://example.org/statement-priority",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+ url = "https://example.org/statement-priority/event"
+ website.webpages.create!(url: url, language: "en", rdf_uri: "rdf:statement-priority", rdfs_class: rdfs_classes(:one))
+ Distillator::FetchCache.create!(
+ uri_key: CGI.escape(url),
+ normalized_url: url,
+ html: "ok",
+ body: "ok",
+ scrape_date: 1.hour.ago,
+ successful_refresh: 1.hour.ago,
+ headers: {},
+ signals: { "transport_success" => true, "content_success" => true },
+ final_url: url
+ )
+ website.transition_evidences.create!(
+ url: url,
+ check_kind: "fetch_parity",
+ status: "checked",
+ checked_at: 1.hour.ago,
+ details: {
+ reason: "legacy_lookup_missing_config",
+ representative_webpages: [url],
+ representative_webpage_count: 1,
+ representative_url_results: [
+ { url: url, fetch_status: "passed", fetch_reason: "ok", legacy_lookup_status: "missing_config", comparison_status: "review" }
+ ]
+ }
+ )
+ website.transition_evidences.create!(
+ url: url,
+ check_kind: "statement_delta",
+ status: "failed",
+ statement_count_delta_acceptable: false,
+ checked_at: 1.hour.ago,
+ details: {
+ reason: "statement_refresh_failed",
+ representative_webpages: [url],
+ representative_webpage_count: 1,
+ statements_failed_count: 1,
+ failing_statements: [{ id: 42, source: "json_url / en", webpage_url: url }],
+ refresh_errors: ["invalid url in json_url"]
+ }
+ )
+
+ detail = Distillator::ShadowSiteDetail.call(website: website)
+
+ assert_equal "statements", detail.root_cause[:failed_layer]
+ assert_equal "Legacy statement check failed before critical/optional classification was available.", detail.root_cause[:concrete_reason]
+ assert_equal true, detail.checked_scope[:legacy_statement_failure]
+ assert_equal 1, detail.checked_scope[:statements_failed_count]
+ assert_nil detail.checked_scope[:critical_statements_failed_count]
+ assert_nil detail.checked_scope[:optional_statements_failed_count]
+ assert_equal "unknown", detail.statement_failure_groups.first[:severity]
+ assert_equal "Invalid URL from json_url", detail.statement_failure_groups.first[:reason]
+ assert_equal "json_url / en", detail.statement_failure_groups.first[:source]
+ end
+
+ test "detail reports pending batch check until newer evidence is recorded" do
+ website = Website.create!(
+ name: "Pending freshness",
+ seedurl: "pending-freshness",
+ graph_name: "https://example.org/pending-freshness",
+ default_language: "en",
+ distillator_mode: "shadow",
+ transition_check_requested_at: Time.zone.parse("2026-05-25 12:15:00")
+ )
+ url = "https://example.org/pending-freshness/event"
+ website.transition_evidences.create!(
+ url: url,
+ check_kind: "fetch_parity",
+ status: "checked",
+ checked_at: Time.zone.parse("2026-05-25 12:00:00")
+ )
+
+ pending_detail = Distillator::ShadowSiteDetail.call(website: website)
+
+ assert_equal true, pending_detail.pending_transition_batch_check
+ assert_equal Time.zone.parse("2026-05-25 12:00:00"), pending_detail.latest_transition_evidence_checked_at
+
+ website.transition_evidences.create!(
+ url: url,
+ check_kind: "statement_delta",
+ status: "checked",
+ statement_count_delta_acceptable: true,
+ checked_at: Time.zone.parse("2026-05-25 12:20:00")
+ )
+
+ fresh_detail = Distillator::ShadowSiteDetail.call(website: website.reload)
+
+ assert_equal false, fresh_detail.pending_transition_batch_check
+ assert_equal Time.zone.parse("2026-05-25 12:20:00"), fresh_detail.latest_transition_evidence_checked_at
+ end
+end
diff --git a/test/services/distillator/shadow_site_summary_test.rb b/test/services/distillator/shadow_site_summary_test.rb
new file mode 100644
index 00000000..097f0327
--- /dev/null
+++ b/test/services/distillator/shadow_site_summary_test.rb
@@ -0,0 +1,279 @@
+require "test_helper"
+
+class Distillator::ShadowSiteSummaryTest < ActiveSupport::TestCase
+ test "unknown when no distillator cache evidence exists" do
+ website = create_shadow_website(name: "Unknown shadow", seedurl: "unknown-shadow")
+
+ summary = Distillator::ShadowSiteSummary.call(website: website, cache: nil)
+
+ assert_equal :not_checked, summary.status
+ assert_equal :wringer, summary.production_backend
+ assert_equal :condenser, summary.testing_backend
+ assert_equal :missing, summary.fetch_status
+ assert_equal "Shadow", summary.mode_label
+ assert_equal "Wringer", summary.production_backend_label
+ assert_equal "Not checked", summary.readiness_label
+ assert_equal "unknown", summary.severity
+ assert_equal "Run transition batch check.", summary.primary_action
+ end
+
+ test "blocked when latest attempt failed and last good content was preserved" do
+ website, cache = create_shadow_website_with_cache(
+ name: "Blocked shadow",
+ seedurl: "blocked-shadow",
+ url: "https://blocked-shadow.example/event",
+ health_status: "preserved_after_failure",
+ health_severity: "medium",
+ successful_refresh: 2.days.ago,
+ scrape_date: 1.hour.ago,
+ primary_issue_key: "redirect_to_listing",
+ primary_issue_label: "Redirect to listing",
+ primary_issue_severity: "failed",
+ signals: {
+ "transport_success" => true,
+ "content_success" => false,
+ "last_good_preserved_failure" => true
+ }
+ )
+
+ summary = Distillator::ShadowSiteSummary.call(website: website, cache: cache)
+
+ assert_equal :blocked, summary.status
+ assert_equal :failed, summary.fetch_status
+ assert_includes summary.blockers, "Cannot activate yet: fetch check failed."
+ assert_equal "Blocked", summary.readiness_label
+ assert_equal "high", summary.severity
+ assert_equal "Cannot activate yet: fetch check failed.", summary.primary_blocker
+ end
+
+ test "review when evidence is incomplete and redirect changed" do
+ website, cache = create_shadow_website_with_cache(
+ name: "Review shadow",
+ seedurl: "review-shadow",
+ url: "https://review-shadow.example/event",
+ health_status: "redirect_changed",
+ health_severity: "low",
+ successful_refresh: 10.days.ago,
+ scrape_date: Time.zone.now,
+ primary_issue_key: "queue_it",
+ primary_issue_label: "Queue-it waiting room",
+ primary_issue_severity: "warning",
+ final_url: "https://other.example/event",
+ redirected: true,
+ signals: {
+ "transport_success" => true
+ }
+ )
+
+ summary = Distillator::ShadowSiteSummary.call(website: website, cache: cache)
+
+ assert_equal :review, summary.status
+ assert_equal :stale, summary.fetch_status
+ assert_includes summary.warnings, "Needs review: fetch result redirected."
+ assert_equal "Needs review", summary.readiness_label
+ assert_equal "medium", summary.severity
+ assert_equal "Needs review: fetch check is stale.", summary.primary_blocker
+ end
+
+ test "ready when evidence exists and no blockers or warnings remain" do
+ website, cache = create_shadow_website_with_cache(
+ name: "Ready shadow",
+ seedurl: "ready-shadow",
+ url: "https://ready-shadow.example/event",
+ health_status: "healthy",
+ health_severity: "ok",
+ successful_refresh: 2.hours.ago,
+ scrape_date: 1.hour.ago,
+ signals: {
+ "transport_success" => true,
+ "content_success" => true,
+ "export_diff_checked" => true
+ }
+ )
+
+ summary = Distillator::ShadowSiteSummary.call(website: website, cache: cache)
+
+ assert_equal :review, summary.status
+ assert_equal [], summary.blockers
+ assert_includes summary.warnings, "Needs review: statements check is missing."
+ assert_equal :wringer, summary.production_backend
+ assert_equal :condenser, summary.testing_backend
+ assert_equal "Needs review", summary.readiness_label
+ assert_equal "medium", summary.severity
+ assert_equal "Needs review: statements check is missing.", summary.primary_blocker
+ end
+
+ test "la vitrine site with missing export evidence is blocked instead of ready" do
+ website, cache = create_shadow_website_with_cache(
+ name: "Hector Charland",
+ seedurl: "hector-charland-com",
+ url: "https://hector-charland-com.example/event",
+ signals: {
+ "transport_success" => true,
+ "content_success" => true,
+ "representative_urls_checked" => true,
+ "statement_count_delta_acceptable" => true
+ }
+ )
+
+ summary = Distillator::ShadowSiteSummary.call(website: website, cache: cache)
+
+ assert_equal "lavitrine_pipeline", summary.cohort_key
+ assert_equal :blocked, summary.status
+ assert_includes summary.blockers, "Cannot activate yet: export check is missing."
+ assert_equal "Cannot activate yet: statements check is missing.", summary.primary_blocker
+ end
+
+ test "non cohort site with missing export evidence is review" do
+ website, cache = create_shadow_website_with_cache(
+ name: "Outside Feed",
+ seedurl: "outside-feed",
+ url: "https://outside-feed.example/event",
+ signals: {
+ "transport_success" => true,
+ "content_success" => true
+ }
+ )
+
+ summary = Distillator::ShadowSiteSummary.call(website: website, cache: cache)
+
+ assert_nil summary.cohort_key
+ assert_equal :review, summary.status
+ assert_includes summary.warnings, "Needs review: export check is missing."
+ assert_equal "Needs review: statements check is missing.", summary.primary_blocker
+ end
+
+ test "la vitrine site with all stricter checks passes as ready" do
+ website, cache = create_shadow_website_with_cache(
+ name: "Hector Charland",
+ seedurl: "hector-charland-com",
+ url: "https://hector-charland-com.example/event",
+ signals: {
+ "transport_success" => true,
+ "content_success" => true
+ }
+ )
+ create_transition_evidence(website, "fetch_parity", details: { representative_urls_checked: true })
+ create_transition_evidence(website, "statement_delta", statement_count_delta_acceptable: true)
+ create_transition_evidence(website, "export_diff", export_diff_checked: true)
+
+ summary = Distillator::ShadowSiteSummary.call(website: website, cache: cache)
+
+ assert_equal :ready, summary.status
+ assert_equal [], summary.blockers
+ assert_equal [], summary.warnings
+ assert_equal "Ready", summary.readiness_label
+ assert_equal "ok", summary.severity
+ assert_nil summary.primary_blocker
+ assert_equal "Promote to Active.", summary.primary_action
+ end
+
+ test "latest fetch evidence failure overrides older healthy cache in the summary" do
+ website, cache = create_shadow_website_with_cache(
+ name: "Latest failed attempt",
+ seedurl: "latest-failed-attempt",
+ url: "https://latest-failed-attempt.example/event",
+ signals: {
+ "transport_success" => true,
+ "content_success" => true
+ }
+ )
+ create_transition_evidence(
+ website,
+ "fetch_parity",
+ status: "failed",
+ details: {
+ attempted_condenser_fetch: true,
+ comparison_performed: false,
+ representative_urls_checked: true,
+ reason: "empty_body"
+ }
+ )
+
+ summary = Distillator::ShadowSiteSummary.call(website: website, cache: cache)
+
+ assert_equal :blocked, summary.status
+ assert_equal :failed, summary.fetch_status
+ assert_includes summary.blockers, "Cannot activate yet: fetch check failed."
+ end
+
+ test "non cohort site with stale durable evidence is review" do
+ website, cache = create_shadow_website_with_cache(
+ name: "Outside Feed",
+ seedurl: "outside-feed",
+ url: "https://outside-feed.example/event",
+ signals: {
+ "transport_success" => true,
+ "content_success" => true,
+ "export_diff_checked" => true
+ }
+ )
+ create_transition_evidence(website, "export_diff", export_diff_checked: true, checked_at: 4.days.ago)
+
+ summary = Distillator::ShadowSiteSummary.call(website: website, cache: cache)
+
+ assert_equal :review, summary.status
+ assert_includes summary.warnings, "Needs review: export check is stale."
+ end
+
+ private
+
+ def create_shadow_website(name:, seedurl:)
+ Website.create!(
+ name: name,
+ seedurl: seedurl,
+ graph_name: "https://#{seedurl}.example/graph",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+ end
+
+ def create_shadow_website_with_cache(name:, seedurl:, url:, **cache_attrs)
+ website = create_shadow_website(name: name, seedurl: seedurl)
+ website.webpages.create!(
+ url: url,
+ language: "en",
+ rdf_uri: "adr:#{seedurl}",
+ rdfs_class: rdfs_classes(:one)
+ )
+
+ cache = Distillator::FetchCache.create!(
+ {
+ uri_key: CGI.escape(url),
+ normalized_url: url,
+ name: name,
+ html: cache_attrs.fetch(:html, "cached"),
+ body: cache_attrs.fetch(:body, "cached"),
+ http_response_code: 200,
+ scrape_date: Time.zone.now,
+ successful_refresh: Time.zone.now,
+ headers: {},
+ signals: { "transport_success" => true, "content_success" => true },
+ hints: Array(cache_attrs[:primary_issue_key]).compact,
+ final_url: url,
+ redirect_chain: [],
+ health_status: "healthy",
+ health_severity: "ok"
+ }.merge(cache_attrs).tap do |attrs|
+ attrs[:signals] = attrs.fetch(:signals, {}).merge(
+ "primary_issue_key" => attrs[:primary_issue_key],
+ "primary_issue_label" => attrs[:primary_issue_label],
+ "primary_issue_severity" => attrs[:primary_issue_severity]
+ ).compact
+ end
+ )
+
+ [website, cache]
+ end
+
+ def create_transition_evidence(website, check_kind, checked_at: 1.hour.ago, status: "checked", **attrs)
+ website.transition_evidences.create!(
+ {
+ url: "https://evidence.example/#{website.seedurl}/#{check_kind}",
+ check_kind: check_kind,
+ status: status,
+ checked_at: checked_at
+ }.merge(attrs)
+ )
+ end
+end
diff --git a/test/services/distillator/staging_rollout_repair_test.rb b/test/services/distillator/staging_rollout_repair_test.rb
new file mode 100644
index 00000000..f6bfb0a3
--- /dev/null
+++ b/test/services/distillator/staging_rollout_repair_test.rb
@@ -0,0 +1,96 @@
+require "test_helper"
+
+class Distillator::StagingRolloutRepairTest < ActiveSupport::TestCase
+ setup do
+ @old_runtime = ENV["DISTILLATOR_RUNTIME"]
+ Website.update_all(distillator_mode: "shadow")
+ end
+
+ teardown do
+ ENV["DISTILLATOR_RUNTIME"] = @old_runtime
+ end
+
+ test "dry run returns invalid website count and list without modifying rows" do
+ ENV["DISTILLATOR_RUNTIME"] = "staging"
+ legacy = create_website_with_mode("repair-legacy", "legacy")
+ blank = create_website_with_mode("repair-blank", "legacy")
+ blank.update_column(:distillator_mode, "")
+ active = create_website_with_mode("repair-active", "active")
+
+ result = Distillator::StagingRolloutRepair.call
+
+ assert_equal true, result.success?
+ assert_equal true, result.dry_run
+ assert_equal 2, result.invalid_count
+ assert_equal 0, result.repaired_count
+ assert_equal [], result.changed_websites
+ assert_equal [legacy.id, blank.id].sort, result.unchanged_websites.map(&:id).sort
+ assert_equal [legacy.id, blank.id].sort, result.websites.map(&:id).sort
+ assert_equal "legacy", legacy.reload.distillator_mode
+ assert_equal "", blank.reload.distillator_mode
+ assert_equal "active", active.reload.distillator_mode
+ end
+
+ test "apply changes legacy blank internal and replay to shadow and leaves valid rows unchanged" do
+ ENV["DISTILLATOR_RUNTIME"] = "staging"
+ legacy = create_website_with_mode("repair-apply-legacy", "legacy")
+ blank = create_website_with_mode("repair-apply-blank", "legacy")
+ blank.update_column(:distillator_mode, "")
+ internal = create_website_with_mode("repair-apply-internal", "legacy")
+ internal.update_column(:distillator_mode, "internal")
+ replay = create_website_with_mode("repair-apply-replay", "legacy")
+ replay.update_column(:distillator_mode, "replay")
+ shadow = create_website_with_mode("repair-apply-shadow", "shadow")
+ active = create_website_with_mode("repair-apply-active", "active")
+
+ result = Distillator::StagingRolloutRepair.call(apply: true, actor: "test", reason: "Repair invalid staging sites")
+
+ assert_equal true, result.success?
+ assert_equal false, result.dry_run
+ assert_equal true, result.applied
+ assert_equal 4, result.invalid_count
+ assert_equal 4, result.repaired_count
+ assert_equal [legacy.id, blank.id, internal.id, replay.id].sort, result.changed_websites.map(&:id).sort
+ assert_equal [], result.unchanged_websites
+ assert_equal "shadow", legacy.reload.distillator_mode
+ assert_equal "shadow", blank.reload.distillator_mode
+ assert_equal "shadow", internal.reload.distillator_mode
+ assert_equal "shadow", replay.reload.distillator_mode
+ assert_equal "shadow", shadow.reload.distillator_mode
+ assert_equal "active", active.reload.distillator_mode
+ assert_equal 1, legacy.rollout_events.where(to_mode: "shadow").count
+ assert_equal 1, internal.rollout_events.where(to_mode: "shadow").count
+ assert_equal 1, replay.rollout_events.where(to_mode: "shadow").count
+ end
+
+ test "apply refuses outside staging and changes nothing" do
+ ENV["DISTILLATOR_RUNTIME"] = "production"
+ legacy = create_website_with_mode("repair-production-legacy", "legacy")
+
+ result = Distillator::StagingRolloutRepair.call(apply: true)
+
+ assert_equal false, result.success?
+ assert_equal false, result.applied
+ assert_equal false, result.dry_run
+ assert_equal 1, result.invalid_count
+ assert_equal 0, result.repaired_count
+ assert_includes result.errors, "Staging rollout repair can only run on staging."
+ assert_equal [], result.changed_websites
+ assert_equal [legacy.id], result.websites.map(&:id)
+ assert_equal [legacy.id], result.unchanged_websites.map(&:id)
+ assert_equal "legacy", legacy.reload.distillator_mode
+ assert_equal 0, legacy.rollout_events.count
+ end
+
+ private
+
+ def create_website_with_mode(seedurl, mode)
+ Website.create!(
+ name: "Repair #{seedurl}",
+ seedurl: seedurl,
+ graph_name: "https://#{seedurl}.example/graph",
+ default_language: "en",
+ distillator_mode: mode
+ )
+ end
+end
diff --git a/test/services/distillator/transition_check_runner_test.rb b/test/services/distillator/transition_check_runner_test.rb
new file mode 100644
index 00000000..d7f1904b
--- /dev/null
+++ b/test/services/distillator/transition_check_runner_test.rb
@@ -0,0 +1,790 @@
+require "test_helper"
+
+class Distillator::TransitionCheckRunnerTest < ActiveSupport::TestCase
+ FetchResult = Struct.new(:cache, :transport_success_value, :content_success_value, :blocking_issue_key, keyword_init: true) do
+ def transport_success?
+ transport_success_value
+ end
+
+ def content_success?
+ content_success_value
+ end
+ end
+
+ class FakeRefreshRunner
+ def initialize(result = [])
+ @result = result
+ @calls = []
+ end
+
+ attr_reader :calls
+
+ def call(**kwargs)
+ @calls << kwargs
+ @result
+ end
+ end
+
+ class FakeExportService
+ def initialize(actual:, expected:)
+ @actual = actual
+ @expected = expected
+ end
+
+ def call(seedurl:)
+ @actual
+ end
+
+ def production_equivalent(seedurl:)
+ @expected
+ end
+ end
+
+ class FakeFetchCacheStore
+ def initialize(results_by_url)
+ @results_by_url = results_by_url
+ end
+
+ def fetch(uri:, **)
+ @results_by_url.fetch(uri)
+ end
+ end
+
+ class FakeCacheCompare
+ def initialize(results_by_url)
+ @results_by_url = results_by_url
+ end
+
+ def call(uri:, **)
+ @results_by_url[uri]
+ end
+ end
+
+ test "creates checked statement and export evidence from real transition checks without changing rollout mode" do
+ website = build_website(with_webpage: false)
+ cache = create_cache(website, signals: { "transport_success" => true, "content_success" => true })
+ create_selected_statement(website.webpages.first, status: "ok")
+ export_json = '[{"@id":"event:1","name":"Title"}]'
+
+ result = Distillator::TransitionCheckRunner.new(
+ website: website,
+ refresh_runner: FakeRefreshRunner.new,
+ export_service: FakeExportService.new(actual: export_json, expected: export_json),
+ transition_check_service: fake_transition_check_service(website: website, cache: cache),
+ fetch_cache_store: fake_fetch_cache_store_for(website, cache),
+ cache_compare: fake_cache_compare_for(website)
+ ).call
+
+ assert_equal "shadow", website.reload.distillator_mode
+ assert_equal %w[export_diff fetch_parity statement_delta], result.records.keys.map(&:to_s).sort
+ assert_equal %w[export_diff fetch_parity statement_delta], website.transition_evidences.order(:check_kind).pluck(:check_kind)
+ assert_equal "checked", result.records[:fetch_parity].status
+ assert_equal "checked", result.records[:statement_delta].status
+ assert_equal "checked", result.records[:export_diff].status
+ assert_equal cache.normalized_url, result.records[:fetch_parity].url
+ assert_equal 0, result.records[:statement_delta].statement_delta
+ assert_equal 0, result.records[:export_diff].rdf_added_count
+ assert_equal 0, result.records[:export_diff].rdf_removed_count
+ assert_equal 1, result.records[:statement_delta].details["representative_webpage_count"]
+ assert_equal 1, result.records[:statement_delta].details["candidate_webpage_count"]
+ assert_equal ["https://runner-site.example/event"], result.records[:statement_delta].details["representative_webpages"]
+ assert_equal 1, result.records[:statement_delta].details["statements_refreshed_count"]
+ assert_equal "current export vs production-equivalent export", result.records[:export_diff].details["export_basis"]
+ assert_equal true, result.records[:export_diff].details["export_compared"]
+
+ assert_no_difference("Distillator::TransitionEvidence.count") do
+ Distillator::TransitionCheckRunner.new(
+ website: website,
+ refresh_runner: FakeRefreshRunner.new,
+ export_service: FakeExportService.new(actual: export_json, expected: export_json),
+ transition_check_service: fake_transition_check_service(website: website, cache: cache)
+ ).call
+ end
+ end
+
+ test "failed fetch records failed fetch check" do
+ website = build_website(with_webpage: false)
+ cache = create_cache(website, signals: { "transport_success" => false, "content_success" => false }, health_status: "attempt_failed")
+
+ result = Distillator::TransitionCheckRunner.new(
+ website: website,
+ transition_check_service: fake_transition_check_service(website: website, cache: cache, fetch: :failed),
+ fetch_cache_store: fake_fetch_cache_store_for(website, cache, transport_success: false, content_success: false, blocking_issue_key: "cache_health_failed"),
+ cache_compare: fake_cache_compare_for(website)
+ ).call
+
+ assert_equal "failed", result.records[:fetch_parity].status
+ assert_equal "pending", result.records[:statement_delta].status
+ assert_equal "fetch_failed_before_statement_refresh", result.records[:statement_delta].details["reason"]
+ assert_equal 0, result.records[:statement_delta].details["statements_refreshed_count"]
+ assert_equal "pending", result.records[:export_diff].status
+ assert_equal "fetch_failed_before_export_comparison", result.records[:export_diff].details["reason"]
+ assert_equal "Transition check incomplete: fetch failed, statements not evaluated, export blocked by fetch", result.flash_message
+ end
+
+ test "statement check records failed reason when refresh fails" do
+ website = build_website(with_webpage: false)
+ cache = create_cache(website, signals: { "transport_success" => true, "content_success" => true })
+ create_selected_statement(website.webpages.first, status: "ok")
+ export_json = '[{"@id":"event:1","name":"Title"}]'
+
+ result = Distillator::TransitionCheckRunner.new(
+ website: website,
+ refresh_runner: FakeRefreshRunner.new([{ "Property id 1" => { cache: ["abort_update"] } }]),
+ export_service: FakeExportService.new(actual: export_json, expected: export_json),
+ transition_check_service: fake_transition_check_service(website: website, cache: cache),
+ fetch_cache_store: fake_fetch_cache_store_for(website, cache),
+ cache_compare: fake_cache_compare_for(website)
+ ).call
+
+ assert_equal "failed", result.records[:statement_delta].status
+ assert_equal "critical_statement_refresh_failed", result.records[:statement_delta].details["reason"]
+ assert_equal 1, result.records[:statement_delta].details["statements_failed_count"]
+ assert_equal ["https://runner-site.example/event"], result.records[:statement_delta].details["representative_webpages"]
+ end
+
+ test "optional statement refresh failures record warning without blocking critical readiness" do
+ website = build_website(with_webpage: false)
+ cache = create_cache(website, signals: { "transport_success" => true, "content_success" => true })
+ webpage = website.webpages.first
+ create_selected_statement(webpage, status: "ok", property: properties(:four), cache: "Runner Title")
+ create_selected_statement(webpage, status: "ok", property: properties(:location), cache: "https://example.org/place")
+ create_selected_statement(webpage, status: "ok", property: properties(:ten), cache: '["2026-06-01T19:30:00Z"]')
+ optional_statement = create_selected_statement(webpage, status: "problem", property: properties(:five), cache: "")
+ export_json = '[{"@id":"event:1","name":"Title"}]'
+
+ result = Distillator::TransitionCheckRunner.new(
+ website: website,
+ refresh_runner: FakeRefreshRunner.new([{ "Property id 5" => { cache: ["abort_update"] } }]),
+ export_service: FakeExportService.new(actual: export_json, expected: export_json),
+ transition_check_service: fake_transition_check_service(website: website, cache: cache),
+ fetch_cache_store: fake_fetch_cache_store_for(website, cache),
+ cache_compare: fake_cache_compare_for(website)
+ ).call
+
+ assert_equal "warning", result.records[:statement_delta].status
+ assert_equal "optional_statement_refresh_warning", result.records[:statement_delta].details["reason"]
+ assert_equal 0, result.records[:statement_delta].statement_delta
+ assert_equal true, result.records[:statement_delta].statement_count_delta_acceptable
+ assert_equal 0, result.records[:statement_delta].details["critical_statements_failed_count"]
+ assert_equal 1, result.records[:statement_delta].details["optional_statements_failed_count"]
+ assert_equal [optional_statement.id], result.records[:statement_delta].details["optional_failing_statement_ids"]
+ end
+
+ test "title failure remains a blocking statement failure" do
+ website = build_website(with_webpage: false)
+ cache = create_cache(website, signals: { "transport_success" => true, "content_success" => true })
+ webpage = website.webpages.first
+ title_statement = create_selected_statement(webpage, status: "problem", property: properties(:four), cache: "")
+ create_selected_statement(webpage, status: "ok", property: properties(:location), cache: "https://example.org/place")
+ create_selected_statement(webpage, status: "ok", property: properties(:ten), cache: '["2026-06-01T19:30:00Z"]')
+ export_json = '[{"@id":"event:1","name":"Title"}]'
+
+ result = Distillator::TransitionCheckRunner.new(
+ website: website,
+ refresh_runner: FakeRefreshRunner.new,
+ export_service: FakeExportService.new(actual: export_json, expected: export_json),
+ transition_check_service: fake_transition_check_service(website: website, cache: cache),
+ fetch_cache_store: fake_fetch_cache_store_for(website, cache),
+ cache_compare: fake_cache_compare_for(website)
+ ).call
+
+ assert_equal "failed", result.records[:statement_delta].status
+ assert_equal "critical_statement_refresh_failed", result.records[:statement_delta].details["reason"]
+ assert_equal 1, result.records[:statement_delta].statement_delta
+ assert_equal [title_statement.id], result.records[:statement_delta].details["critical_failing_statement_ids"]
+ end
+
+ test "dates failure remains a blocking statement failure" do
+ website = build_website(with_webpage: false)
+ cache = create_cache(website, signals: { "transport_success" => true, "content_success" => true })
+ webpage = website.webpages.first
+ create_selected_statement(webpage, status: "ok", property: properties(:four), cache: "Runner Title")
+ create_selected_statement(webpage, status: "ok", property: properties(:location), cache: "https://example.org/place")
+ dates_statement = create_selected_statement(webpage, status: "problem", property: properties(:ten), cache: "")
+ export_json = '[{"@id":"event:1","name":"Title"}]'
+
+ result = Distillator::TransitionCheckRunner.new(
+ website: website,
+ refresh_runner: FakeRefreshRunner.new,
+ export_service: FakeExportService.new(actual: export_json, expected: export_json),
+ transition_check_service: fake_transition_check_service(website: website, cache: cache),
+ fetch_cache_store: fake_fetch_cache_store_for(website, cache),
+ cache_compare: fake_cache_compare_for(website)
+ ).call
+
+ assert_equal "failed", result.records[:statement_delta].status
+ assert_equal "critical_statement_refresh_failed", result.records[:statement_delta].details["reason"]
+ assert_equal [dates_statement.id], result.records[:statement_delta].details["critical_failing_statement_ids"]
+ end
+
+ test "export diff records failed counts when comparison differs" do
+ website = build_website(with_webpage: false)
+ cache = create_cache(website, signals: { "transport_success" => true, "content_success" => true })
+ create_selected_statement(website.webpages.first, status: "ok")
+
+ result = Distillator::TransitionCheckRunner.new(
+ website: website,
+ refresh_runner: FakeRefreshRunner.new,
+ export_service: FakeExportService.new(
+ actual: ' "Actual" .',
+ expected: <<~NQUADS
+ "Expected" .
+ "Added" .
+ NQUADS
+ ),
+ transition_check_service: fake_transition_check_service(website: website, cache: cache),
+ fetch_cache_store: fake_fetch_cache_store_for(website, cache),
+ cache_compare: fake_cache_compare_for(website)
+ ).call
+
+ assert_equal "failed", result.records[:export_diff].status
+ assert_equal "failed", result.records[:export_diff].export_diff_status
+ assert_operator result.records[:export_diff].rdf_added_count, :>, 0
+ assert_operator result.records[:export_diff].rdf_removed_count, :>, 0
+ assert_equal true, result.records[:export_diff].details["export_compared"]
+ end
+
+ test "checks record pending reasons when no representative webpages exist" do
+ website = build_website(with_webpage: false)
+ result = Distillator::TransitionCheckRunner.new(
+ website: website,
+ refresh_runner: FakeRefreshRunner.new,
+ export_service: FakeExportService.new(actual: "[]", expected: "[]"),
+ transition_check_service: fake_transition_check_service(website: website, cache: nil, representative_webpages: [], representative_webpage_count: 0, candidate_webpage_count: 0),
+ fetch_cache_store: fake_fetch_cache_store_for(website, nil),
+ cache_compare: fake_cache_compare_for(website)
+ ).call
+
+ assert_equal "pending", result.records[:fetch_parity].status
+ assert_equal "no_representative_webpages", result.records[:fetch_parity].details["reason"]
+ assert_equal "pending", result.records[:statement_delta].status
+ assert_equal "no_representative_webpages", result.records[:statement_delta].details["reason"]
+ assert_equal 0, result.records[:statement_delta].details["representative_webpage_count"]
+ assert_equal "pending", result.records[:export_diff].status
+ assert_equal "no_representative_webpages", result.records[:export_diff].details["reason"]
+ assert_equal 0, result.records[:export_diff].details["representative_webpage_count"]
+ end
+
+ test "statement check records inconclusive when representative webpages have no selected statements" do
+ website = build_website(with_webpage: false)
+ cache = create_cache(website, signals: { "transport_success" => true, "content_success" => true })
+ export_json = '[{"@id":"event:1","name":"Title"}]'
+
+ result = Distillator::TransitionCheckRunner.new(
+ website: website,
+ refresh_runner: FakeRefreshRunner.new,
+ export_service: FakeExportService.new(actual: export_json, expected: export_json),
+ transition_check_service: fake_transition_check_service(website: website, cache: cache),
+ fetch_cache_store: fake_fetch_cache_store_for(website, cache),
+ cache_compare: fake_cache_compare_for(website)
+ ).call
+
+ assert_equal "pending", result.records[:statement_delta].status
+ assert_equal "no_selected_statements", result.records[:statement_delta].details["reason"]
+ assert_equal 0, result.records[:statement_delta].details["statements_refreshed_count"]
+ assert_equal 0, result.records[:statement_delta].details["statements_failed_count"]
+ assert_equal "Transition check incomplete: fetch checked, statements inconclusive, export checked", result.flash_message
+ end
+
+ test "fetch parity records review-needed difference without classifying it as fetch failure" do
+ website = build_website(with_webpage: false)
+ cache = create_cache(website, signals: { "transport_success" => true, "content_success" => true })
+ create_selected_statement(website.webpages.first, status: "ok")
+ export_json = '[{"@id":"event:1","name":"Title"}]'
+
+ result = Distillator::TransitionCheckRunner.new(
+ website: website,
+ refresh_runner: FakeRefreshRunner.new,
+ export_service: FakeExportService.new(actual: export_json, expected: export_json),
+ transition_check_service: fake_transition_check_service(
+ website: website,
+ cache: cache,
+ comparison: {
+ legacy_source: "remote_wringer",
+ legacy_lookup_status: "ok",
+ legacy_lookup_error: nil,
+ condenser_source: "local_fetch_cache",
+ missing: { legacy: false, condenser: false },
+ summary: {
+ blocking_regressions: [],
+ metadata_only_diffs: [],
+ review_needed_diffs: [:content_type],
+ unknown_diffs: []
+ }
+ }
+ ),
+ fetch_cache_store: fake_fetch_cache_store_for(website, cache),
+ cache_compare: fake_cache_compare_for(website, website.webpages.first.url => {
+ legacy_source: "remote_wringer",
+ legacy_lookup_status: "ok",
+ legacy_lookup_error: nil,
+ condenser_source: "local_fetch_cache",
+ missing: { legacy: false, condenser: false },
+ summary: {
+ blocking_regressions: [],
+ metadata_only_diffs: [],
+ review_needed_diffs: [:content_type],
+ unknown_diffs: []
+ }
+ })
+ ).call
+
+ assert_equal "checked", result.records[:fetch_parity].status
+ assert_equal "review_needed_difference", result.records[:fetch_parity].details["reason"]
+ assert_equal "operator", result.records[:fetch_parity].details["comparison_policy"]
+ end
+
+ test "fetch parity records metadata-only difference as checked metadata notes" do
+ website = build_website(with_webpage: false)
+ cache = create_cache(website, signals: { "transport_success" => true, "content_success" => true })
+ create_selected_statement(website.webpages.first, status: "ok")
+ export_json = '[{"@id":"event:1","name":"Title"}]'
+
+ result = Distillator::TransitionCheckRunner.new(
+ website: website,
+ refresh_runner: FakeRefreshRunner.new,
+ export_service: FakeExportService.new(actual: export_json, expected: export_json),
+ transition_check_service: fake_transition_check_service(
+ website: website,
+ cache: cache,
+ comparison: {
+ legacy_source: "remote_wringer",
+ legacy_lookup_status: "ok",
+ legacy_lookup_error: nil,
+ condenser_source: "local_fetch_cache",
+ missing: { legacy: false, condenser: false },
+ summary: {
+ blocking_regressions: [],
+ metadata_only_diffs: [:redirect_chain],
+ review_needed_diffs: [],
+ unknown_diffs: []
+ }
+ }
+ ),
+ fetch_cache_store: fake_fetch_cache_store_for(website, cache),
+ cache_compare: fake_cache_compare_for(website, website.webpages.first.url => {
+ legacy_source: "remote_wringer",
+ legacy_lookup_status: "ok",
+ legacy_lookup_error: nil,
+ condenser_source: "local_fetch_cache",
+ missing: { legacy: false, condenser: false },
+ summary: {
+ blocking_regressions: [],
+ metadata_only_diffs: [:redirect_chain],
+ review_needed_diffs: [],
+ unknown_diffs: []
+ }
+ })
+ ).call
+
+ assert_equal "checked", result.records[:fetch_parity].status
+ assert_equal "metadata_only_difference", result.records[:fetch_parity].details["reason"]
+ assert_equal "operator", result.records[:fetch_parity].details["comparison_policy"]
+ end
+
+ test "fetch parity stores per-url results and does not classify compare failures as fetch failures" do
+ website = build_website(with_webpage: false)
+ first = website.webpages.create!(url: "https://runner-site.example/one", language: "en", rdf_uri: "rdf:one", rdfs_class: rdfs_classes(:one))
+ second = website.webpages.create!(url: "https://runner-site.example/two", language: "en", rdf_uri: "rdf:two", rdfs_class: rdfs_classes(:one))
+ third = website.webpages.create!(url: "https://runner-site.example/three", language: "en", rdf_uri: "rdf:three", rdfs_class: rdfs_classes(:one))
+ cache = Distillator::FetchCache.create!(
+ uri_key: CGI.escape(first.url),
+ normalized_url: first.url,
+ html: "ok",
+ body: "ok",
+ scrape_date: 1.hour.ago,
+ successful_refresh: 1.hour.ago,
+ headers: {},
+ signals: { "transport_success" => true, "content_success" => true },
+ final_url: first.url,
+ health_status: "healthy"
+ )
+ create_selected_statement(first, status: "ok")
+ create_selected_statement(second, status: "ok")
+ create_selected_statement(third, status: "ok")
+ export_json = '[{"@id":"event:1","name":"Title"}]'
+
+ result = Distillator::TransitionCheckRunner.new(
+ website: website,
+ refresh_runner: FakeRefreshRunner.new,
+ export_service: FakeExportService.new(actual: export_json, expected: export_json),
+ transition_check_service: fake_transition_check_service(
+ website: website,
+ cache: cache,
+ representative_webpages: [first, second, third]
+ ),
+ fetch_cache_store: FakeFetchCacheStore.new(
+ first.url => fetch_result_for(cache: cache),
+ second.url => fetch_result_for(cache: cache),
+ third.url => fetch_result_for(cache: cache)
+ ),
+ cache_compare: FakeCacheCompare.new(
+ first.url => { legacy_lookup_status: "ok", missing: { legacy: false, condenser: false }, summary: { blocking_regressions: [], metadata_only_diffs: [], review_needed_diffs: [], unknown_diffs: [] } },
+ second.url => { legacy_lookup_status: "ok", missing: { legacy: false, condenser: false }, summary: { blocking_regressions: [:html_sha256], metadata_only_diffs: [], review_needed_diffs: [], unknown_diffs: [] } },
+ third.url => { legacy_lookup_status: "unreachable", legacy_lookup_error: "timeout", missing: { legacy: false, condenser: false }, summary: { blocking_regressions: [], metadata_only_diffs: [], review_needed_diffs: [], unknown_diffs: [] } }
+ )
+ ).call
+
+ representative_results = result.records[:fetch_parity].details["representative_url_results"]
+ assert_equal 3, representative_results.size
+ assert_equal %w[passed failed checked].sort, [result.records[:fetch_parity].details["representative_url_results"][0]["comparison_status"], result.records[:fetch_parity].details["representative_url_results"][1]["comparison_status"], result.records[:fetch_parity].status].map(&:to_s).sort
+ assert_equal "legacy_lookup_unreachable", result.records[:fetch_parity].details["reason"]
+ assert_equal "legacy_lookup", result.records[:fetch_parity].details["failed_layer"]
+ assert_equal 1, result.records[:fetch_parity].details["affected_url_count"]
+ refute_equal "cache_health_failed", result.records[:fetch_parity].details["reason"]
+ end
+
+ test "partial representative fetch failure blocks statement and export coverage only for the failed url" do
+ website = build_website(with_webpage: false)
+ first = website.webpages.create!(url: "https://runner-site.example/one", language: "en", rdf_uri: "rdf:one", rdfs_class: rdfs_classes(:one))
+ second = website.webpages.create!(url: "https://runner-site.example/two", language: "en", rdf_uri: "rdf:two", rdfs_class: rdfs_classes(:one))
+ third = website.webpages.create!(url: "https://runner-site.example/three", language: "en", rdf_uri: "rdf:three", rdfs_class: rdfs_classes(:one))
+ cache = Distillator::FetchCache.create!(
+ uri_key: CGI.escape(first.url),
+ normalized_url: first.url,
+ html: "ok",
+ body: "ok",
+ scrape_date: 1.hour.ago,
+ successful_refresh: 1.hour.ago,
+ headers: {},
+ signals: { "transport_success" => true, "content_success" => true },
+ final_url: first.url,
+ health_status: "healthy"
+ )
+ [first, second, third].each { |webpage| create_selected_statement(webpage, status: "ok") }
+ export_json = '[{"@id":"event:1","name":"Title"}]'
+
+ result = Distillator::TransitionCheckRunner.new(
+ website: website,
+ refresh_runner: FakeRefreshRunner.new,
+ export_service: FakeExportService.new(actual: export_json, expected: export_json),
+ transition_check_service: fake_transition_check_service(
+ website: website,
+ cache: cache,
+ representative_webpages: [first, second, third]
+ ),
+ fetch_cache_store: FakeFetchCacheStore.new(
+ first.url => fetch_result_for(cache: cache),
+ second.url => fetch_result_for(cache: cache),
+ third.url => fetch_result_for(cache: cache, transport_success: false, content_success: false, blocking_issue_key: "cache_health_failed")
+ ),
+ cache_compare: fake_cache_compare_for(website)
+ ).call
+
+ assert_equal "failed", result.records[:fetch_parity].status
+ assert_equal "fetch", result.records[:fetch_parity].details["failed_layer"]
+ assert_equal 1, result.records[:fetch_parity].details["affected_url_count"]
+
+ assert_equal "pending", result.records[:statement_delta].status
+ assert_equal "partial_fetch_failed_before_statement_refresh", result.records[:statement_delta].details["reason"]
+ assert_equal(
+ {
+ first.url => "passed",
+ second.url => "passed",
+ third.url => "blocked_by_fetch"
+ },
+ result.records[:statement_delta].details["representative_url_statement_results"].to_h { |row| [row["url"], row["status"]] }
+ )
+
+ assert_equal "pending", result.records[:export_diff].status
+ assert_equal false, result.records[:export_diff].export_diff_checked
+ assert_equal "partial", result.records[:export_diff].export_diff_status
+ assert_equal "partial_fetch_failed_before_export_comparison", result.records[:export_diff].details["reason"]
+ assert_equal false, result.records[:export_diff].details["export_compared"]
+ assert_equal true, result.records[:export_diff].details["graph_diff_performed"]
+ assert_equal(
+ {
+ first.url => "checked",
+ second.url => "checked",
+ third.url => "blocked_by_fetch"
+ },
+ result.records[:export_diff].details["representative_url_export_results"].to_h { |row| [row["url"], row["status"]] }
+ )
+ assert_equal "Transition check incomplete: fetch failed, statements inconclusive, export inconclusive", result.flash_message
+ end
+
+ test "timeout budget records incomplete evidence and preserves partial representative rows" do
+ website = build_website(with_webpage: false)
+ first = website.webpages.create!(url: "https://runner-site.example/one", language: "en", rdf_uri: "rdf:one", rdfs_class: rdfs_classes(:one))
+ second = website.webpages.create!(url: "https://runner-site.example/two", language: "en", rdf_uri: "rdf:two", rdfs_class: rdfs_classes(:one))
+ third = website.webpages.create!(url: "https://runner-site.example/three", language: "en", rdf_uri: "rdf:three", rdfs_class: rdfs_classes(:one))
+ cache = create_fetch_cache_for_url(first.url, signals: { "transport_success" => true, "content_success" => true })
+ create_selected_statement(first, status: "ok")
+ create_selected_statement(second, status: "ok")
+ create_selected_statement(third, status: "ok")
+ refresh_runner = FakeRefreshRunner.new
+ clock_values = [0.0, 0.0, 17.5, 17.5, 17.5]
+
+ result = Distillator::TransitionCheckRunner.new(
+ website: website,
+ refresh_runner: refresh_runner,
+ export_service: FakeExportService.new(actual: "[]", expected: "[]"),
+ transition_check_service: fake_transition_check_service(
+ website: website,
+ cache: cache,
+ representative_webpages: [first, second, third]
+ ),
+ fetch_cache_store: FakeFetchCacheStore.new(
+ first.url => fetch_result_for(cache: cache)
+ ),
+ cache_compare: fake_cache_compare_for(website),
+ budget_seconds: 20,
+ timeout_guard_seconds: 3,
+ clock: -> { clock_values.shift || 17.5 }
+ ).call
+
+ assert_equal "failed", result.records[:fetch_parity].status
+ assert_equal "transition_check_timeout_budget_exceeded", result.records[:fetch_parity].details["reason"]
+ assert_equal 2, result.records[:fetch_parity].details["affected_url_count"]
+
+ assert_equal "pending", result.records[:statement_delta].status
+ assert_equal "transition_check_timeout_budget_exceeded", result.records[:statement_delta].details["reason"]
+ assert_equal(
+ {
+ first.url => "inconclusive",
+ second.url => "blocked_by_fetch",
+ third.url => "blocked_by_fetch"
+ },
+ result.records[:statement_delta].details["representative_url_statement_results"].to_h { |row| [row["url"], row["status"]] }
+ )
+
+ assert_equal "pending", result.records[:export_diff].status
+ assert_equal false, result.records[:export_diff].export_diff_checked
+ assert_equal "pending", result.records[:export_diff].export_diff_status
+ assert_equal "transition_check_timeout_budget_exceeded", result.records[:export_diff].details["reason"]
+ assert_equal false, result.records[:export_diff].details["export_compared"]
+ assert_equal(
+ {
+ first.url => "inconclusive",
+ second.url => "blocked_by_fetch",
+ third.url => "blocked_by_fetch"
+ },
+ result.records[:export_diff].details["representative_url_export_results"].to_h { |row| [row["url"], row["status"]] }
+ )
+ assert_empty refresh_runner.calls
+ end
+
+ test "captcha fetch failure is recorded explicitly" do
+ website = build_website(with_webpage: false)
+ cache = create_fetch_cache_for_url(
+ "https://runner-site.example/captcha",
+ signals: { "transport_success" => false, "content_success" => false, "blocking_issue_key" => "system_captcha" },
+ health_status: "attempt_failed"
+ )
+ webpage = website.webpages.create!(url: cache.normalized_url, language: "en", rdf_uri: "rdf:captcha", rdfs_class: rdfs_classes(:one))
+
+ result = Distillator::TransitionCheckRunner.new(
+ website: website,
+ transition_check_service: fake_transition_check_service(website: website, cache: cache, representative_webpages: [webpage], fetch: :failed),
+ fetch_cache_store: FakeFetchCacheStore.new(
+ webpage.url => fetch_result_for(cache: cache, transport_success: false, content_success: false, blocking_issue_key: "system_captcha")
+ ),
+ cache_compare: fake_cache_compare_for(website)
+ ).call
+
+ assert_equal "failed", result.records[:fetch_parity].status
+ assert_equal "captcha_detected", result.records[:fetch_parity].details["reason"]
+ assert_equal "fetch", result.records[:fetch_parity].details["failed_layer"]
+ assert_equal "captcha_detected", result.records[:statement_delta].details["representative_url_statement_results"].first["reason"]
+ assert_equal "captcha_detected", result.records[:export_diff].details["representative_url_export_results"].first["reason"]
+ end
+
+ test "missing phantomjs api key is recorded explicitly for rendered fetch failure" do
+ website = build_website(with_webpage: false)
+ url = "https://runner-site.example/rendered"
+ cache = create_fetch_cache_for_url(
+ url,
+ signals: {
+ "transport_success" => false,
+ "content_success" => false,
+ "renderer_unavailable" => true,
+ "use_phantomjs" => true
+ },
+ health_status: "attempt_failed"
+ )
+ webpage = website.webpages.create!(url: url, language: "en", rdf_uri: "rdf:rendered", rdfs_class: rdfs_classes(:one))
+ original_api_key = ENV["PHANTOMJS_API_KEY"]
+ ENV["PHANTOMJS_API_KEY"] = nil
+
+ result = Distillator::TransitionCheckRunner.new(
+ website: website,
+ transition_check_service: fake_transition_check_service(website: website, cache: cache, representative_webpages: [webpage], fetch: :failed),
+ fetch_cache_store: FakeFetchCacheStore.new(
+ webpage.url => fetch_result_for(cache: cache, transport_success: false, content_success: false)
+ ),
+ cache_compare: fake_cache_compare_for(website)
+ ).call
+
+ assert_equal "phantomjs_api_key_missing", result.records[:fetch_parity].details["reason"]
+ ensure
+ ENV["PHANTOMJS_API_KEY"] = original_api_key
+ end
+
+ test "statement refresh reuses the representative cache instead of forcing another immediate scrape" do
+ website = build_website(with_webpage: false)
+ cache = create_fetch_cache_for_url("https://runner-site.example/event", signals: { "transport_success" => true, "content_success" => true })
+ webpage = website.webpages.create!(url: cache.normalized_url, language: "en", rdf_uri: "rdf:runner-site", rdfs_class: rdfs_classes(:one))
+ create_selected_statement(webpage, status: "initial")
+ create_selected_statement(webpage, status: "initial")
+ refresh_runner = FakeRefreshRunner.new
+ export_json = '[{"@id":"event:1","name":"Title"}]'
+
+ Distillator::TransitionCheckRunner.new(
+ website: website,
+ refresh_runner: refresh_runner,
+ export_service: FakeExportService.new(actual: export_json, expected: export_json),
+ transition_check_service: fake_transition_check_service(website: website, cache: cache, representative_webpages: [webpage]),
+ fetch_cache_store: FakeFetchCacheStore.new(
+ webpage.url => fetch_result_for(cache: cache)
+ ),
+ cache_compare: fake_cache_compare_for(website)
+ ).call
+
+ assert_equal 1, refresh_runner.calls.size
+ assert_equal({}, refresh_runner.calls.first[:scrape_options])
+ end
+
+ private
+
+ def build_website(with_webpage: true)
+ website = Website.create!(
+ name: "Runner site",
+ seedurl: "runner-site",
+ graph_name: "https://example.org/runner-site",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+ create_cache(website, signals: { "transport_success" => true, "content_success" => true }) if with_webpage
+ website
+ end
+
+ def create_cache(website, signals:, health_status: "healthy")
+ url = "https://runner-site.example/event"
+ website.webpages.create!(url: url, language: "en", rdf_uri: "rdf:runner-site", rdfs_class: rdfs_classes(:one))
+ create_fetch_cache_for_url(url, signals: signals, health_status: health_status)
+ end
+
+ def create_fetch_cache_for_url(url, signals:, health_status: "healthy")
+ Distillator::FetchCache.create!(
+ uri_key: CGI.escape(url),
+ normalized_url: url,
+ html: "ok",
+ body: "ok",
+ scrape_date: 1.hour.ago,
+ successful_refresh: 1.hour.ago,
+ headers: {},
+ signals: signals,
+ final_url: url,
+ health_status: health_status
+ )
+ end
+
+ def create_selected_statement(webpage, status:, property: properties(:four), cache: "Runner Title")
+ source = Source.create!(
+ algorithm_value: "manual=Runner Title",
+ selected: true,
+ selected_by: "test",
+ language: "en",
+ render_js: false,
+ property: property,
+ website: webpage.website
+ )
+ statement = Statement.create!(
+ cache: cache,
+ status: status,
+ status_origin: "transition_check_test",
+ cache_refreshed: 1.hour.ago,
+ cache_changed: 1.hour.ago,
+ source: source,
+ webpage: webpage,
+ selected_individual: true
+ )
+ statement.update_columns(
+ cache: cache,
+ status: status,
+ cache_refreshed: 1.hour.ago,
+ cache_changed: 1.hour.ago
+ )
+ statement
+ end
+
+ def fetch_result_for(cache:, transport_success: true, content_success: true, blocking_issue_key: nil)
+ FetchResult.new(
+ cache: cache,
+ transport_success_value: transport_success,
+ content_success_value: content_success,
+ blocking_issue_key: blocking_issue_key
+ )
+ end
+
+ def fake_fetch_cache_store_for(website, cache, transport_success: true, content_success: true, blocking_issue_key: nil)
+ results = website.webpages.index_with do
+ fetch_result_for(
+ cache: cache,
+ transport_success: transport_success,
+ content_success: content_success,
+ blocking_issue_key: blocking_issue_key
+ )
+ end
+ FakeFetchCacheStore.new(results.transform_keys(&:url))
+ end
+
+ def fake_cache_compare_for(website, overrides = {})
+ defaults = website.webpages.index_with do
+ {
+ legacy_source: "remote_wringer",
+ legacy_lookup_status: "ok",
+ legacy_lookup_error: nil,
+ condenser_source: "local_fetch_cache",
+ missing: { legacy: false, condenser: false },
+ summary: {
+ blocking_regressions: [],
+ metadata_only_diffs: [],
+ review_needed_diffs: [],
+ unknown_diffs: []
+ }
+ }
+ end
+ FakeCacheCompare.new(defaults.transform_keys(&:url).merge(overrides))
+ end
+
+ def fake_transition_check_service(website:, cache:, fetch: :passed, representative_webpages: nil, representative_webpage_count: nil, candidate_webpage_count: nil, publishable_event_page_count: nil, comparison: nil)
+ representative_webpages = representative_webpages.nil? ? Array(website.webpages.first).compact : representative_webpages
+ representative_webpage = representative_webpages.first
+
+ stub(
+ call: Distillator::TransitionCheck::Result.new(
+ website_id: website.id,
+ website: website,
+ mode: website.distillator_mode.to_sym,
+ priority: false,
+ active_backend: :wringer,
+ latest_cache_status: cache&.health_status.to_s.presence || "unknown",
+ cache_present: cache.present?,
+ compare_available: true,
+ blocking_issues: [],
+ warnings: [],
+ promotable: false,
+ status: :review,
+ fetch: fetch,
+ statements: :missing,
+ export: :missing,
+ representative_webpage: representative_webpage,
+ representative_webpages: representative_webpages,
+ representative_url: representative_webpage&.url,
+ representative_webpage_count: representative_webpage_count || representative_webpages.count,
+ candidate_webpage_count: candidate_webpage_count || representative_webpages.count,
+ publishable_event_page_count: publishable_event_page_count || representative_webpages.count,
+ selection_rule: Distillator::TransitionCheck::SELECTION_RULE,
+ attempted_condenser_fetch: false,
+ condenser_fetch_result: nil,
+ comparison: comparison,
+ comparison_policy: :operator,
+ cache: cache,
+ cache_link_payload: {},
+ primary_action: "Run transition batch check."
+ )
+ )
+ end
+end
diff --git a/test/services/distillator/transition_check_test.rb b/test/services/distillator/transition_check_test.rb
new file mode 100644
index 00000000..6b3f5c50
--- /dev/null
+++ b/test/services/distillator/transition_check_test.rb
@@ -0,0 +1,559 @@
+require "test_helper"
+
+class Distillator::TransitionCheckTest < ActiveSupport::TestCase
+ test "blocked cache health makes promotable false" do
+ website = build_website("blocked-transition-check")
+ cache = build_cache(
+ website: website,
+ url: "https://blocked-transition-check.example/event",
+ signals: { "transport_success" => false, "content_success" => false },
+ health_status: "attempt_failed"
+ )
+
+ result = Distillator::TransitionCheck.call(website: website, cache: cache)
+
+ assert_equal website.id, result.website_id
+ assert_equal :shadow, result.mode
+ assert_equal false, result.promotable
+ assert_equal :wringer, result.active_backend
+ assert_equal cache.reload.health_status.to_s.presence || "unknown", result.latest_cache_status
+ assert_equal true, result.cache_present
+ assert result.blocking_issues.any?
+ end
+
+ test "healthy shadow cache with representative checks makes promotable true" do
+ Distillator::WringerEndpoint.stubs(:current).returns(
+ Distillator::WringerEndpoint::Result.new(
+ compatibility_base_url: "http://compat.example",
+ legacy_lookup_base_url: "http://wringer.example",
+ compatibility_source: "DISTILLATOR_COMPAT_BASE_URL",
+ state: :remote_configured,
+ status_label: "Current Wringer: Remote configured",
+ status_detail: "http://compat.example via DISTILLATOR_COMPAT_BASE_URL"
+ )
+ )
+ website = build_website("ready-transition-check")
+ url = "https://ready-transition-check.example/event"
+ cache = build_cache(
+ website: website,
+ url: url,
+ signals: { "transport_success" => true, "content_success" => true }
+ )
+ website.transition_evidences.create!(url: url, check_kind: "statement_delta", status: "checked", statement_count_delta_acceptable: true, checked_at: 1.hour.ago)
+ website.transition_evidences.create!(url: url, check_kind: "export_diff", status: "checked", export_diff_checked: true, checked_at: 1.hour.ago)
+
+ result = Distillator::TransitionCheck.call(website: website, cache: cache)
+
+ assert_equal :ready, result.status
+ assert_equal true, result.promotable
+ assert_equal :passed, result.fetch
+ assert_equal :passed, result.statements
+ assert_equal :passed, result.export
+ assert_equal true, result.compare_available
+ end
+
+ test "la vitrine site does not become promotable from cache health alone" do
+ website = build_website("hector-charland-com")
+ cache = build_cache(
+ website: website,
+ url: "https://hector-charland-com.example/event",
+ signals: { "transport_success" => true, "content_success" => true },
+ health_status: "healthy"
+ )
+
+ result = Distillator::TransitionCheck.call(website: website, cache: cache)
+
+ assert_equal :blocked, result.status
+ assert_equal false, result.promotable
+ assert_includes result.blocking_issues, "Cannot activate yet: statements check is missing."
+ end
+
+ test "run fetch forces internal condenser evidence before compare and uses the fresh cache" do
+ website = build_website("forced-fetch-transition-check")
+ stale_cache = build_cache(
+ website: website,
+ url: "https://forced-fetch-transition-check.example/event",
+ signals: { "transport_success" => false, "content_success" => false, "empty_body" => true },
+ health_status: "empty_body"
+ )
+ fresh_cache = stale_cache.dup
+ fresh_cache.assign_attributes(
+ html: "fresh",
+ body: "fresh",
+ scrape_date: Time.current,
+ successful_refresh: Time.current,
+ headers: {},
+ signals: { "transport_success" => true, "content_success" => true },
+ final_url: stale_cache.normalized_url,
+ health_status: "healthy"
+ )
+ fetch_result = Distillator::FetchCacheStore::Result.new(
+ status: :ok,
+ body: "fresh",
+ html: "fresh",
+ headers: {},
+ final_url: stale_cache.normalized_url,
+ redirect_chain: [],
+ http_response_code: 200,
+ signals: { "transport_success" => true, "content_success" => true },
+ hints: [],
+ duration_ms: 10,
+ cache_hit: false,
+ cache_write: true,
+ cache_reason: "force_scrape",
+ uri_key: stale_cache.uri_key,
+ normalized_url: stale_cache.normalized_url,
+ fetch_path: "native",
+ name: "fresh",
+ scrape_date: Time.current,
+ successful_refresh: Time.current,
+ cache: fresh_cache
+ )
+ fetch_cache_store = mock
+ fetch_cache_store.expects(:fetch).with do |kwargs|
+ assert_equal stale_cache.normalized_url, kwargs[:uri]
+ assert_equal true, kwargs[:force_scrape]
+ assert_equal "internal", kwargs[:mode]
+ assert_equal website, kwargs[:website]
+ assert_equal "transition_check", kwargs.dig(:log_context, :source)
+ true
+ end.returns(fetch_result)
+ comparison = {
+ summary: { promotable: true, blocking_regressions: [] },
+ missing: { legacy: false, condenser: false },
+ legacy_source: "remote_wringer",
+ legacy_lookup_error: nil,
+ condenser_source: "local_fetch_cache"
+ }
+ cache_compare = mock
+ cache_compare.expects(:call).with(uri: stale_cache.normalized_url, condenser_result: fetch_result, comparison_policy: :operator).returns(comparison)
+
+ result = Distillator::TransitionCheck.call(
+ website: website,
+ cache: stale_cache,
+ run_fetch: true,
+ fetch_cache_store: fetch_cache_store,
+ cache_compare: cache_compare
+ )
+
+ assert_equal true, result.attempted_condenser_fetch
+ assert_equal stale_cache.normalized_url, result.representative_url
+ assert_equal fresh_cache, result.cache
+ assert_equal :passed, result.fetch
+ assert_equal comparison, result.comparison
+ end
+
+ test "publishable representative webpages prefer nearest future then recently updated then recent past" do
+ website = build_website("future-event-order")
+ nearest_future = create_publishable_event_webpage(
+ website,
+ suffix: "nearest-future",
+ url: "https://future-event-order.example/nearest-future",
+ archive_date: Time.zone.parse("2026-06-01 12:00:00"),
+ updated_at: Time.zone.parse("2026-05-24 10:00:00"),
+ start_at: "2026-06-01T12:00:00-04:00"
+ )
+ later_future = create_publishable_event_webpage(
+ website,
+ suffix: "later-future",
+ url: "https://future-event-order.example/later-future",
+ archive_date: Time.zone.parse("2026-06-12 12:00:00"),
+ updated_at: Time.zone.parse("2026-05-24 11:00:00"),
+ start_at: "2026-06-12T12:00:00-04:00"
+ )
+ newest_nil_archive = create_publishable_event_webpage(
+ website,
+ suffix: "newest-nil-archive-event",
+ url: "https://future-event-order.example/newest-nil-archive-event",
+ archive_date: nil,
+ updated_at: Time.zone.parse("2026-05-24 13:00:00"),
+ start_at: "2026-06-20T20:00:00-04:00"
+ )
+ older_nil_archive = create_publishable_event_webpage(
+ website,
+ suffix: "older-nil-archive-event",
+ url: "https://future-event-order.example/older-nil-archive-event",
+ archive_date: nil,
+ updated_at: Time.zone.parse("2026-05-24 12:00:00"),
+ start_at: "2026-06-21T20:00:00-04:00"
+ )
+ recent_past = create_publishable_event_webpage(
+ website,
+ suffix: "recent-past",
+ url: "https://future-event-order.example/recent-past",
+ archive_date: Time.zone.parse("2026-03-15 19:00:00"),
+ updated_at: Time.zone.parse("2026-05-22 09:00:00"),
+ start_at: "2026-03-15T19:00:00-04:00"
+ )
+ create_publishable_event_webpage(
+ website,
+ suffix: "older-past",
+ url: "https://future-event-order.example/older-past",
+ archive_date: Time.zone.parse("2026-01-29 19:00:00"),
+ updated_at: Time.zone.parse("2026-05-10 09:00:00"),
+ start_at: "2026-01-29T19:00:00-05:00"
+ )
+ create_non_event_webpage(
+ website,
+ suffix: "about-page",
+ url: "https://future-event-order.example/about-page",
+ updated_at: Time.zone.parse("2026-05-24 13:00:00")
+ )
+
+ result = Distillator::TransitionCheck.call(website: website)
+
+ assert_equal nearest_future, result.representative_webpage
+ assert_equal [nearest_future, later_future, newest_nil_archive, older_nil_archive, recent_past], result.representative_webpages
+ assert_equal "https://future-event-order.example/nearest-future", result.representative_url
+ assert_equal 7, result.candidate_webpage_count
+ assert_equal 6, result.publishable_event_page_count
+ assert_equal 6, result.selected_candidate_tier_count
+ assert_equal Distillator::TransitionCheck::SELECTION_RULE, result.selection_rule
+ end
+
+ test "publishable representative sampling follows the target matrix" do
+ {
+ 1 => 1,
+ 3 => 3,
+ 5 => 5,
+ 20 => 5,
+ 26 => 6,
+ 50 => 10,
+ 100 => 20,
+ 125 => 25,
+ 300 => 25
+ }.each do |publishable_count, expected_sample_size|
+ website = build_website("sampling-#{publishable_count}")
+ create_publishable_event_pages(website, publishable_count)
+
+ result = Distillator::TransitionCheck.call(website: website)
+
+ assert_equal publishable_count, result.publishable_event_page_count
+ assert_equal expected_sample_size, result.representative_webpage_count
+ assert_equal expected_sample_size, result.representative_webpages.count
+ assert_equal publishable_count, result.selected_candidate_tier_count
+ assert result.representative_webpages.all? { |webpage| webpage.rdfs_class.name == "Event" }, "expected only event pages for #{publishable_count}"
+ end
+ end
+
+ test "representative webpages fall back to generic webpages in deterministic recency order when no publishable event pages exist" do
+ website = build_website("generic-page-fallback")
+ newest_page = create_non_event_webpage(
+ website,
+ suffix: "newest-page",
+ url: "https://generic-page-fallback.example/newest-page",
+ updated_at: Time.zone.parse("2026-05-24 12:00:00")
+ )
+ create_non_event_webpage(
+ website,
+ suffix: "middle-page",
+ url: "https://generic-page-fallback.example/middle-page",
+ updated_at: Time.zone.parse("2026-05-24 11:00:00")
+ )
+ create_non_event_webpage(
+ website,
+ suffix: "oldest-page",
+ url: "https://generic-page-fallback.example/oldest-page",
+ updated_at: Time.zone.parse("2026-05-24 10:00:00")
+ )
+ create_non_event_webpage(
+ website,
+ suffix: "fourth-page",
+ url: "https://generic-page-fallback.example/fourth-page",
+ updated_at: Time.zone.parse("2026-05-24 09:00:00")
+ )
+ create_event_webpage(
+ website,
+ suffix: "unpublishable-event",
+ url: "https://generic-page-fallback.example/unpublishable-event",
+ archive_date: Time.zone.parse("2026-05-24 08:00:00"),
+ updated_at: Time.zone.parse("2026-05-24 08:00:00")
+ )
+
+ result = Distillator::TransitionCheck.call(website: website)
+
+ assert_equal newest_page, result.representative_webpage
+ assert_equal 3, result.representative_webpages.count
+ assert_equal 5, result.candidate_webpage_count
+ assert_equal 0, result.publishable_event_page_count
+ assert_equal 5, result.selected_candidate_tier_count
+ end
+
+ test "candidate webpage count reflects all transition candidates, not only the selected tier" do
+ website = build_website("candidate-count-honest")
+ create_publishable_event_webpage(
+ website,
+ suffix: "future-one",
+ url: "https://candidate-count-honest.example/future-one",
+ archive_date: Time.zone.parse("2026-06-01 12:00:00"),
+ updated_at: Time.zone.parse("2026-05-24 10:00:00"),
+ start_at: "2026-06-01T12:00:00-04:00"
+ )
+ create_publishable_event_webpage(
+ website,
+ suffix: "future-two",
+ url: "https://candidate-count-honest.example/future-two",
+ archive_date: Time.zone.parse("2026-06-03 12:00:00"),
+ updated_at: Time.zone.parse("2026-05-24 09:00:00"),
+ start_at: "2026-06-03T12:00:00-04:00"
+ )
+ 4.times do |index|
+ create_publishable_event_webpage(
+ website,
+ suffix: "past-#{index}",
+ url: "https://candidate-count-honest.example/past-#{index}",
+ archive_date: Time.zone.parse("2026-02-#{index + 10} 12:00:00"),
+ updated_at: Time.zone.parse("2026-05-2#{index} 08:00:00"),
+ start_at: "2026-02-#{index + 10}T12:00:00-05:00"
+ )
+ end
+ 3.times do |index|
+ create_non_event_webpage(
+ website,
+ suffix: "generic-#{index}",
+ url: "https://candidate-count-honest.example/generic-#{index}",
+ updated_at: Time.zone.parse("2026-05-2#{index} 07:00:00")
+ )
+ end
+
+ result = Distillator::TransitionCheck.call(website: website)
+
+ assert_equal 5, result.representative_webpage_count
+ assert_equal 9, result.candidate_webpage_count
+ assert_equal 6, result.publishable_event_page_count
+ assert_equal 6, result.selected_candidate_tier_count
+ end
+
+ test "latest successful condenser fetch stays passed when legacy lookup is incomplete" do
+ website = build_website("legacy-lookup-incomplete")
+ cache = build_cache(
+ website: website,
+ url: "https://legacy-lookup-incomplete.example/event",
+ signals: { "transport_success" => true, "content_success" => true },
+ health_status: "healthy"
+ )
+ website.transition_evidences.create!(
+ url: cache.normalized_url,
+ check_kind: "fetch_parity",
+ status: "checked",
+ checked_at: Time.current,
+ details: {
+ attempted_condenser_fetch: true,
+ condenser_fetch_success: true,
+ comparison_performed: false,
+ legacy_lookup_status: "missing_config",
+ legacy_lookup_error: "missing_config",
+ reason: "legacy_lookup_missing_config"
+ }
+ )
+
+ result = Distillator::TransitionCheck.call(website: website, cache: cache)
+
+ assert_equal :passed, result.fetch
+ assert_equal :review, result.status
+ assert_includes result.warnings, "Needs review: legacy Wringer endpoint is not configured for this environment."
+ end
+
+ test "latest successful condenser fetch stays passed when legacy lookup is unreachable" do
+ website = build_website("legacy-lookup-unreachable")
+ cache = build_cache(
+ website: website,
+ url: "https://legacy-lookup-unreachable.example/event",
+ signals: { "transport_success" => true, "content_success" => true },
+ health_status: "healthy"
+ )
+ website.transition_evidences.create!(
+ url: cache.normalized_url,
+ check_kind: "fetch_parity",
+ status: "checked",
+ checked_at: Time.current,
+ details: {
+ attempted_condenser_fetch: true,
+ condenser_fetch_success: true,
+ comparison_performed: false,
+ legacy_lookup_status: "unreachable",
+ legacy_lookup_error: "connection refused",
+ reason: "legacy_lookup_unreachable"
+ }
+ )
+
+ result = Distillator::TransitionCheck.call(website: website, cache: cache)
+
+ assert_equal :passed, result.fetch
+ assert_equal :review, result.status
+ assert_includes result.warnings, "Needs review: legacy Wringer lookup failed during the latest transition batch check."
+ end
+
+ test "run fetch skips compare when no representative webpage exists" do
+ website = Website.create!(
+ name: "No representative",
+ seedurl: "no-representative",
+ graph_name: "https://example.org/no-representative",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+ fetch_cache_store = mock
+ fetch_cache_store.expects(:fetch).never
+ cache_compare = mock
+ cache_compare.expects(:call).never
+
+ result = Distillator::TransitionCheck.call(
+ website: website,
+ run_fetch: true,
+ fetch_cache_store: fetch_cache_store,
+ cache_compare: cache_compare
+ )
+
+ assert_equal false, result.attempted_condenser_fetch
+ assert_nil result.representative_url
+ assert_nil result.comparison
+ end
+
+ test "tourisme des chenaux style detail cache proceeds past fetch parity" do
+ website = build_website("tourisme-des-chenaux")
+ cache = build_cache(
+ website: website,
+ url: "https://tourismedeschenaux.ca/evenements",
+ signals: {
+ "transport_success" => true,
+ "content_success" => true,
+ "content_type" => "html"
+ },
+ health_status: "healthy"
+ )
+ website.transition_evidences.create!(
+ url: cache.normalized_url,
+ check_kind: "fetch_parity",
+ status: "checked",
+ checked_at: Time.current,
+ details: {
+ attempted_condenser_fetch: true,
+ representative_urls_checked: true
+ }
+ )
+
+ result = Distillator::TransitionCheck.call(website: website, cache: cache)
+
+ assert_equal :passed, result.fetch
+ assert_not_includes result.blocking_issues, "Cannot activate yet: fetch check failed."
+ end
+
+ private
+
+ def build_website(seedurl)
+ Website.create!(
+ name: seedurl,
+ seedurl: seedurl,
+ graph_name: "https://example.org/#{seedurl}",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+ end
+
+ def build_cache(website:, url:, signals:, health_status: "healthy")
+ website.webpages.create!(url: url, language: "en", rdf_uri: "rdf:#{website.seedurl}", rdfs_class: rdfs_classes(:one))
+ Distillator::FetchCache.create!(
+ uri_key: CGI.escape(url),
+ normalized_url: url,
+ html: "ok",
+ body: "ok",
+ scrape_date: 1.hour.ago,
+ successful_refresh: 1.hour.ago,
+ headers: {},
+ signals: signals,
+ final_url: url,
+ health_status: health_status
+ )
+ end
+
+ def create_event_webpage(website, suffix:, url:, archive_date:, updated_at: nil)
+ webpage = website.webpages.create!(
+ url: url,
+ language: "en",
+ rdf_uri: "rdf:#{website.seedurl}:#{suffix}",
+ rdfs_class: rdfs_classes(:one),
+ archive_date: archive_date
+ )
+ webpage.update_columns(archive_date: nil) if archive_date.nil?
+ webpage.update_columns(updated_at: updated_at, created_at: updated_at) if updated_at.present?
+ webpage.reload
+ end
+
+ def create_non_event_webpage(website, suffix:, url:, updated_at:)
+ webpage = website.webpages.create!(
+ url: url,
+ language: "en",
+ rdf_uri: "rdf:#{website.seedurl}:#{suffix}",
+ rdfs_class: rdfs_classes(:person)
+ )
+ webpage.update_columns(updated_at: updated_at, created_at: updated_at)
+ webpage.reload
+ end
+
+ def create_publishable_event_webpage(website, suffix:, url:, archive_date:, updated_at:, start_at:)
+ webpage = create_event_webpage(
+ website,
+ suffix: suffix,
+ url: url,
+ archive_date: archive_date,
+ updated_at: updated_at
+ )
+ create_publishable_statements_for(webpage, start_at: start_at)
+ webpage
+ end
+
+ def create_publishable_event_pages(website, count)
+ count.times.map do |index|
+ archive_date, updated_at =
+ if index < 10
+ [
+ Time.zone.parse("2026-06-#{format('%02d', index + 1)} 12:00:00"),
+ Time.zone.parse("2026-05-24 10:#{format('%02d', index)}:00")
+ ]
+ elsif index < 20
+ [
+ nil,
+ Time.zone.parse("2026-05-23 10:#{format('%02d', index - 10)}:00")
+ ]
+ else
+ [
+ Time.zone.parse("2026-02-#{format('%02d', ((index - 20) % 28) + 1)} 12:00:00"),
+ Time.zone.parse("2026-05-22 10:#{format('%02d', index % 60)}:00")
+ ]
+ end
+
+ create_publishable_event_webpage(
+ website,
+ suffix: "publishable-#{index}",
+ url: "https://#{website.seedurl}.example/publishable-#{index}",
+ archive_date: archive_date,
+ updated_at: updated_at,
+ start_at: "2026-06-#{format('%02d', (index % 28) + 1)}T12:00:00-04:00"
+ )
+ end
+ end
+
+ def create_publishable_statements_for(webpage, start_at:)
+ [
+ [properties(:four), "Representative title"],
+ [properties(:location), '[["Salle","uri:place"]]'],
+ [properties(:six), "[\"#{start_at}\"]"]
+ ].each do |property, cache|
+ source = Source.create!(
+ website: webpage.website,
+ property: property,
+ language: "en",
+ selected: true,
+ algorithm_value: "transition-check-test"
+ )
+
+ Statement.create!(
+ webpage: webpage,
+ source: source,
+ cache: cache,
+ status: "ok"
+ )
+ Statement.where(webpage: webpage, source: source).update_all(status: "ok")
+ end
+ end
+end
diff --git a/test/services/distillator/transition_evidence_explanation_test.rb b/test/services/distillator/transition_evidence_explanation_test.rb
new file mode 100644
index 00000000..9b44055c
--- /dev/null
+++ b/test/services/distillator/transition_evidence_explanation_test.rb
@@ -0,0 +1,209 @@
+require "test_helper"
+
+class Distillator::TransitionEvidenceExplanationTest < ActiveSupport::TestCase
+ test "fetch explanation distinguishes comparison failure from fetch failure" do
+ website = websites(:one)
+ evidence = Distillator::TransitionEvidence.new(
+ website: website,
+ url: "https://example.org/event",
+ check_kind: "fetch_parity",
+ status: "failed",
+ checked_at: Time.current,
+ details: {
+ reason: "cache_compare_blocking_regression",
+ failed_layer: "cache_compare",
+ affected_url_count: 1,
+ attempted_condenser_fetch: true,
+ condenser_fetch_success: true,
+ comparison_performed: true,
+ legacy_lookup_status: "ok"
+ }
+ )
+
+ explanation = Distillator::TransitionEvidenceExplanation.call(
+ check_kind: "fetch_parity",
+ evidence: evidence,
+ website: website,
+ state: :failed
+ )
+
+ assert_equal "Condenser and Wringer have a blocking parity mismatch.", explanation.headline
+ assert_includes explanation.details, "Failed layer: Cache compare"
+ assert_includes explanation.details, "Affected sampled URLs: 1"
+ assert_includes explanation.details, "Condenser fetch: passed"
+ assert_equal "Review the Condenser vs Wringer comparison for the affected URLs.", explanation.next_action
+ end
+
+ test "fetch explanation keeps fetch failure wording for real fetch failures" do
+ website = websites(:one)
+ evidence = Distillator::TransitionEvidence.new(
+ website: website,
+ url: "https://example.org/event",
+ check_kind: "fetch_parity",
+ status: "failed",
+ checked_at: Time.current,
+ details: {
+ reason: "cache_health_failed",
+ failed_layer: "fetch",
+ affected_url_count: 2,
+ attempted_condenser_fetch: true,
+ condenser_fetch_success: false
+ }
+ )
+
+ explanation = Distillator::TransitionEvidenceExplanation.call(
+ check_kind: "fetch_parity",
+ evidence: evidence,
+ website: website,
+ state: :failed
+ )
+
+ assert_equal "Condenser fetch/cache failed for one or more sampled URLs.", explanation.headline
+ assert_equal "Fix the fetch/cache failure first, then rerun the transition batch check.", explanation.next_action
+ end
+
+ test "fetch explanation uses explicit captcha wording" do
+ website = websites(:one)
+ evidence = Distillator::TransitionEvidence.new(
+ website: website,
+ url: "https://example.org/event",
+ check_kind: "fetch_parity",
+ status: "failed",
+ checked_at: Time.current,
+ details: {
+ reason: "captcha_detected",
+ failed_layer: "fetch",
+ affected_url_count: 1,
+ attempted_condenser_fetch: true,
+ condenser_fetch_success: false
+ }
+ )
+
+ explanation = Distillator::TransitionEvidenceExplanation.call(
+ check_kind: "fetch_parity",
+ evidence: evidence,
+ website: website,
+ state: :failed
+ )
+
+ assert_equal "Captcha was detected while fetching one or more sampled URLs.", explanation.headline
+ assert_equal "Resolve the captcha or use the direct inspection links for the affected URLs, then rerun the transition batch check.", explanation.next_action
+ end
+
+ test "export explanation does not claim pass when runtime budget is exhausted" do
+ website = websites(:one)
+ evidence = Distillator::TransitionEvidence.new(
+ website: website,
+ url: "https://example.org/event",
+ check_kind: "export_diff",
+ status: "pending",
+ checked_at: Time.current,
+ details: {
+ reason: "transition_check_timeout_budget_exceeded"
+ }
+ )
+
+ explanation = Distillator::TransitionEvidenceExplanation.call(
+ check_kind: "export_diff",
+ evidence: evidence,
+ website: website,
+ state: :inconclusive
+ )
+
+ assert_equal "Transition check reached its runtime budget before export coverage completed.", explanation.headline
+ assert_equal "Rerun the transition batch check with enough runtime budget to finish export coverage.", explanation.next_action
+ end
+
+ test "statement explanation distinguishes optional warnings from blocking failures" do
+ website = websites(:one)
+ evidence = Distillator::TransitionEvidence.new(
+ website: website,
+ url: "https://example.org/event",
+ check_kind: "statement_delta",
+ status: "warning",
+ checked_at: Time.current,
+ details: {
+ reason: "optional_statement_refresh_warning",
+ critical_statements_failed_count: 0,
+ optional_statements_failed_count: 2,
+ optional_failing_statements: [
+ { id: 201, source: "Description / en", webpage_url: "https://example.org/event", severity: "warning" }
+ ],
+ refresh_errors: ["Property id 5: {:cache=>[\"abort_update\"]}"]
+ }
+ )
+
+ explanation = Distillator::TransitionEvidenceExplanation.call(
+ check_kind: "statement_delta",
+ evidence: evidence,
+ website: website,
+ state: :warning
+ )
+
+ assert_equal "warning", explanation.severity
+ assert_equal "Critical statements passed; optional statement refresh warnings need review.", explanation.headline
+ assert_includes explanation.details, "Optional statement warnings: 2"
+ assert_equal "Review the optional statement refresh warnings before activating.", explanation.next_action
+ end
+
+ test "statement explanation keeps stale generic failures legacy and asks for rerun" do
+ website = websites(:one)
+ evidence = Distillator::TransitionEvidence.new(
+ website: website,
+ url: "https://example.org/event",
+ check_kind: "statement_delta",
+ status: "failed",
+ statement_delta: 99,
+ checked_at: Time.current,
+ details: {
+ reason: "statement_refresh_failed",
+ statements_failed_count: 99,
+ refresh_errors: ["InvalidURL from json_url"]
+ }
+ )
+
+ explanation = Distillator::TransitionEvidenceExplanation.call(
+ check_kind: "statement_delta",
+ evidence: evidence,
+ website: website,
+ state: :failed
+ )
+
+ assert_equal "Legacy statement check failed before critical/optional classification was available.", explanation.headline
+ assert_includes explanation.details, "Legacy statement failures recorded: 99"
+ assert_includes explanation.details, "Critical/optional split: not recorded"
+ assert_equal "Rerun the transition batch check to record current statement evidence with critical/optional classification.", explanation.next_action
+ refute_includes explanation.details, "Critical statement failures: 99"
+ refute_includes explanation.details, "Critical statement failures: 0"
+ end
+
+ test "statement explanation uses split warning copy when only optional failures remain" do
+ website = websites(:one)
+ evidence = Distillator::TransitionEvidence.new(
+ website: website,
+ url: "https://example.org/event",
+ check_kind: "statement_delta",
+ status: "warning",
+ statement_delta: 0,
+ checked_at: Time.current,
+ details: {
+ reason: "optional_statement_refresh_warning",
+ statements_failed_count: 99,
+ critical_statements_failed_count: 0,
+ optional_statements_failed_count: 99
+ }
+ )
+
+ explanation = Distillator::TransitionEvidenceExplanation.call(
+ check_kind: "statement_delta",
+ evidence: evidence,
+ website: website,
+ state: :warning
+ )
+
+ assert_equal "Critical statements passed; optional statement refresh warnings need review.", explanation.headline
+ assert_includes explanation.details, "Optional statement warnings: 99"
+ refute_includes explanation.details, "Critical statement failures: 0"
+ refute_match(/Statement refresh found 99 failing statements\./, explanation.headline)
+ end
+end
diff --git a/test/services/distillator/transition_evidence_recorder_test.rb b/test/services/distillator/transition_evidence_recorder_test.rb
new file mode 100644
index 00000000..35634084
--- /dev/null
+++ b/test/services/distillator/transition_evidence_recorder_test.rb
@@ -0,0 +1,60 @@
+require "test_helper"
+
+class Distillator::TransitionEvidenceRecorderTest < ActiveSupport::TestCase
+ test "records evidence per website and url" do
+ website = Website.create!(
+ name: "Recorder site",
+ seedurl: "hector-charland-com",
+ graph_name: "https://example.org/recorder-site",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+
+ evidence = Distillator::TransitionEvidenceRecorder.call(
+ website: website,
+ url: "hector-charland-com/events/1",
+ check_kind: :export_diff,
+ status: :checked,
+ export_diff_checked: true,
+ rdf_added_count: 0,
+ rdf_removed_count: 0
+ )
+
+ assert_equal website, evidence.website
+ assert_equal "export_diff", evidence.check_kind
+ assert_equal true, evidence.export_diff_checked
+ assert_equal "lavitrine_pipeline", evidence.cohort_key
+ assert_match %r{\Ahttp://hector-charland-com/events/1\z}, evidence.url
+ end
+
+ test "updates the latest matching evidence record instead of duplicating it" do
+ website = Website.create!(
+ name: "Recorder site",
+ seedurl: "outside-feed",
+ graph_name: "https://example.org/recorder-site-2",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+
+ first = Distillator::TransitionEvidenceRecorder.call(
+ website: website,
+ url: "outside-feed/events/1",
+ check_kind: :statement_delta,
+ status: :pending
+ )
+
+ assert_no_difference("Distillator::TransitionEvidence.count") do
+ second = Distillator::TransitionEvidenceRecorder.call(
+ website: website,
+ url: "outside-feed/events/1",
+ check_kind: :statement_delta,
+ status: :checked,
+ statement_delta: 4,
+ statement_count_delta_acceptable: true
+ )
+ assert_equal first.id, second.id
+ assert_equal "checked", second.status
+ assert_equal 4, second.statement_delta
+ end
+ end
+end
diff --git a/test/services/distillator/transition_runtime_test.rb b/test/services/distillator/transition_runtime_test.rb
new file mode 100644
index 00000000..68a63613
--- /dev/null
+++ b/test/services/distillator/transition_runtime_test.rb
@@ -0,0 +1,95 @@
+require "test_helper"
+
+class Distillator::TransitionRuntimeTest < ActiveSupport::TestCase
+ setup do
+ @old_runtime = ENV["DISTILLATOR_RUNTIME"]
+ @old_rails_env = ENV["RAILS_ENV"]
+ @old_override_flag = ENV["DISTILLATOR_ALLOW_ACTIVE_OVERRIDE"]
+ @old_heroku_app_name = ENV["HEROKU_APP_NAME"]
+ @old_app_name = ENV["APP_NAME"]
+ @old_heroku_parent_app_name = ENV["HEROKU_PARENT_APP_NAME"]
+ end
+
+ teardown do
+ ENV["DISTILLATOR_RUNTIME"] = @old_runtime
+ ENV["RAILS_ENV"] = @old_rails_env
+ ENV["DISTILLATOR_ALLOW_ACTIVE_OVERRIDE"] = @old_override_flag
+ ENV["HEROKU_APP_NAME"] = @old_heroku_app_name
+ ENV["APP_NAME"] = @old_app_name
+ ENV["HEROKU_PARENT_APP_NAME"] = @old_heroku_parent_app_name
+ end
+
+ test "staging is true for explicit runtime" do
+ ENV["DISTILLATOR_RUNTIME"] = "staging"
+ ENV["RAILS_ENV"] = "production"
+ ENV["HEROKU_APP_NAME"] = nil
+
+ assert_equal true, Distillator::TransitionRuntime.staging?
+ end
+
+ test "staging is true for known staging app" do
+ ENV["DISTILLATOR_RUNTIME"] = nil
+ ENV["RAILS_ENV"] = "production"
+ ENV["HEROKU_APP_NAME"] = "footlight-condenser-test-c24c162bb7c8"
+
+ assert_equal true, Distillator::TransitionRuntime.staging?
+ end
+
+ test "staging stays false for random app names containing test" do
+ ENV["DISTILLATOR_RUNTIME"] = nil
+ ENV["RAILS_ENV"] = "production"
+ ENV["HEROKU_APP_NAME"] = nil
+ ENV["APP_NAME"] = "random-test-app"
+
+ assert_equal false, Distillator::TransitionRuntime.staging?
+ end
+
+ test "allow active override remains true on staging" do
+ ENV["DISTILLATOR_RUNTIME"] = "staging"
+ Rails.stubs(:env).returns(ActiveSupport::StringInquirer.new("production"))
+
+ assert_equal true, Distillator::TransitionRuntime.allow_active_override?
+ ensure
+ Rails.unstub(:env)
+ end
+
+ test "staging invalid rollout mode scope includes legacy blank and internal values" do
+ shadow = create_website_with_mode("shadow-scope", "shadow")
+ active = create_website_with_mode("active-scope", "active")
+ legacy = create_website_with_mode("legacy-scope", "legacy")
+ blank = create_website_with_mode("blank-scope", "legacy")
+ blank.update_column(:distillator_mode, "")
+ internal = create_website_with_mode("internal-scope", "legacy")
+ internal.update_column(:distillator_mode, "internal")
+
+ ids = Distillator::TransitionRuntime.staging_invalid_rollout_mode_scope.order(:id).pluck(:id)
+
+ assert_includes ids, legacy.id
+ assert_includes ids, blank.id
+ assert_includes ids, internal.id
+ assert_not_includes ids, shadow.id
+ assert_not_includes ids, active.id
+ end
+
+ test "staging invalid rollout mode scope excludes shadow and active from repair set" do
+ shadow = create_website_with_mode("shadow-repair-scope", "shadow")
+ active = create_website_with_mode("active-repair-scope", "active")
+
+ ids = Distillator::TransitionRuntime.staging_invalid_rollout_mode_scope.order(:id).pluck(:id)
+
+ assert_not_includes ids, shadow.id
+ assert_not_includes ids, active.id
+ end
+
+ private
+
+ def create_website_with_mode(seedurl, mode)
+ Website.create!(
+ name: "Runtime #{seedurl}",
+ seedurl: seedurl,
+ graph_name: "https://#{seedurl}.example/graph",
+ default_language: "en",
+ distillator_mode: mode
+ )
+ end
+end
diff --git a/test/services/distillator/transition_status_test.rb b/test/services/distillator/transition_status_test.rb
new file mode 100644
index 00000000..64d3d767
--- /dev/null
+++ b/test/services/distillator/transition_status_test.rb
@@ -0,0 +1,641 @@
+require "test_helper"
+
+class Distillator::TransitionStatusTest < ActiveSupport::TestCase
+ test "no cache or evidence returns not checked" do
+ website = build_website("Outside Feed", "outside-feed")
+
+ status = Distillator::TransitionStatus.call(website: website, cache: nil)
+
+ assert_equal :not_checked, status.status
+ assert_equal :missing, status.fetch
+ assert_equal :missing, status.statements
+ assert_equal :missing, status.export
+ assert_equal :unknown, status.safety
+ assert_equal :not_checked, status.confidence
+ end
+
+ test "failed transport returns blocked and fetch failed" do
+ website = build_website("Outside Feed", "outside-feed")
+ cache = build_cache(signals: { "transport_success" => false, "content_success" => true }, health_status: "attempt_failed")
+
+ status = Distillator::TransitionStatus.call(website: website, cache: cache)
+
+ assert_equal :blocked, status.status
+ assert_equal :failed, status.fetch
+ assert_equal :unsafe, status.safety
+ assert_equal "Fix fetch/cache first, then rerun the transition batch check.", status.activation_recommendation[:next_action]
+ end
+
+ test "failed content returns blocked and fetch failed" do
+ website = build_website("Outside Feed", "outside-feed")
+ cache = build_cache(signals: { "transport_success" => true, "content_success" => false }, health_status: "attempt_failed")
+
+ status = Distillator::TransitionStatus.call(website: website, cache: cache)
+
+ assert_equal :blocked, status.status
+ assert_equal :failed, status.fetch
+ end
+
+ test "la vitrine missing statement evidence returns blocked" do
+ website = build_website("Tout Culture", "outside-seed")
+ cache = build_cache(signals: { "transport_success" => true, "content_success" => true, "export_diff_checked" => true })
+
+ status = Distillator::TransitionStatus.call(website: website, cache: cache)
+
+ assert_equal :blocked, status.status
+ assert_equal :missing, status.statements
+ end
+
+ test "la vitrine missing export evidence returns blocked" do
+ website = build_website("Tout Culture", "outside-seed")
+ cache = build_cache(signals: { "transport_success" => true, "content_success" => true, "statement_count_delta_acceptable" => true })
+
+ status = Distillator::TransitionStatus.call(website: website, cache: cache)
+
+ assert_equal :blocked, status.status
+ assert_equal :missing, status.export
+ end
+
+ test "la vitrine cache signals alone do not satisfy statements and export checks" do
+ website = build_website("Tout Culture", "outside-seed")
+ cache = build_cache(
+ signals: {
+ "transport_success" => true,
+ "content_success" => true,
+ "statement_count_delta_acceptable" => true,
+ "export_diff_checked" => true
+ }
+ )
+
+ status = Distillator::TransitionStatus.call(website: website, cache: cache)
+
+ assert_equal :blocked, status.status
+ assert_equal :missing, status.statements
+ assert_equal :missing, status.export
+ end
+
+ test "ordinary missing export evidence returns review" do
+ website = build_website("Outside Feed", "outside-feed")
+ cache = build_cache(signals: { "transport_success" => true, "content_success" => true, "statement_count_delta_acceptable" => true })
+
+ status = Distillator::TransitionStatus.call(website: website, cache: cache)
+
+ assert_equal :review, status.status
+ assert_equal :missing, status.export
+ end
+
+ test "ordinary sites still fall back to cache signals for statements and export" do
+ website = build_website("Outside Feed", "outside-feed")
+ cache = build_cache(
+ signals: {
+ "transport_success" => true,
+ "content_success" => true,
+ "statement_count_delta_acceptable" => true,
+ "export_diff_checked" => true
+ }
+ )
+
+ status = Distillator::TransitionStatus.call(website: website, cache: cache)
+
+ assert_equal :ready, status.status
+ assert_equal :passed, status.statements
+ assert_equal :passed, status.export
+ assert_equal :safe, status.safety
+ assert_equal :high, status.confidence
+ end
+
+ test "all required fresh evidence returns ready" do
+ website = build_website("Tout Culture", "outside-seed")
+ cache = build_cache(signals: { "transport_success" => true, "content_success" => true })
+ website.transition_evidences.create!(url: "https://example.org/event", check_kind: "statement_delta", status: "checked", statement_count_delta_acceptable: true, checked_at: 1.hour.ago)
+ website.transition_evidences.create!(url: "https://example.org/event", check_kind: "export_diff", status: "checked", export_diff_checked: true, checked_at: 1.hour.ago)
+
+ status = Distillator::TransitionStatus.call(website: website, cache: cache)
+
+ assert_equal :ready, status.status
+ assert_equal :passed, status.fetch
+ assert_equal :passed, status.statements
+ assert_equal :passed, status.export
+ assert_equal "Ready", status.activation_recommendation[:label]
+ end
+
+ test "evidence statuses expose checked missing failed and stale states" do
+ website = build_website("Tout Culture", "outside-seed")
+ cache = build_cache(signals: { "transport_success" => true, "content_success" => true })
+ website.transition_evidences.create!(url: "https://example.org/event", check_kind: "fetch_parity", status: "checked", checked_at: 1.hour.ago)
+ website.transition_evidences.create!(url: "https://example.org/event", check_kind: "statement_delta", status: "failed", statement_count_delta_acceptable: false, checked_at: 1.hour.ago)
+ website.transition_evidences.create!(url: "https://example.org/event", check_kind: "export_diff", status: "checked", export_diff_checked: true, checked_at: 4.days.ago)
+
+ status = Distillator::TransitionStatus.call(website: website, cache: cache)
+
+ assert_equal :checked, status.evidence_statuses["fetch_parity"]
+ assert_equal :failed, status.evidence_statuses["statement_delta"]
+ assert_equal :stale, status.evidence_statuses["export_diff"]
+ assert_includes status.blockers, "Cannot activate yet: statements check failed."
+ assert_not_includes status.blockers, "Cannot activate yet: statements check is missing."
+ assert_equal "Blocked", status.activation_recommendation[:label]
+ assert_equal "Cannot activate yet: statements check failed.", status.activation_recommendation[:reason]
+ end
+
+ test "activation can be blocked while export still passes" do
+ website = build_website("Tout Culture", "outside-seed")
+ cache = build_cache(signals: { "transport_success" => true, "content_success" => true })
+ website.transition_evidences.create!(url: "https://example.org/event", check_kind: "statement_delta", status: "failed", statement_count_delta_acceptable: false, checked_at: 1.hour.ago)
+ website.transition_evidences.create!(url: "https://example.org/event", check_kind: "export_diff", status: "checked", export_diff_checked: true, checked_at: 1.hour.ago)
+
+ status = Distillator::TransitionStatus.call(website: website, cache: cache)
+
+ assert_equal :blocked, status.status
+ assert_equal :failed, status.statements
+ assert_equal :passed, status.export
+ assert_equal "Blocked", status.activation_recommendation[:label]
+ assert_equal "Export", status.checks.last[:label]
+ assert_equal :passed, status.checks.last[:state]
+ end
+
+ test "optional statement warnings keep readiness in review instead of blocked" do
+ website = build_website("Tout Culture", "outside-seed")
+ cache = build_cache(signals: { "transport_success" => true, "content_success" => true })
+ website.transition_evidences.create!(
+ url: "https://example.org/event",
+ check_kind: "statement_delta",
+ status: "warning",
+ statement_count_delta_acceptable: true,
+ checked_at: 1.hour.ago,
+ details: {
+ reason: "optional_statement_refresh_warning",
+ critical_statements_failed_count: 0,
+ optional_statements_failed_count: 3
+ }
+ )
+ website.transition_evidences.create!(
+ url: "https://example.org/event",
+ check_kind: "export_diff",
+ status: "checked",
+ export_diff_checked: true,
+ checked_at: 1.hour.ago
+ )
+
+ status = Distillator::TransitionStatus.call(website: website, cache: cache)
+
+ assert_equal :review, status.status
+ assert_equal :warning, status.statements
+ assert_equal :passed, status.export
+ assert_equal :review, status.safety
+ assert_not_includes status.blockers, "Cannot activate yet: statements check failed."
+ assert_includes status.warnings, "Critical statements passed; optional statement refresh warnings need review."
+ assert_equal "Review the optional statement refresh warnings before activating.", status.activation_recommendation[:next_action]
+ end
+
+ test "legacy generic statement refresh failure stays blocked without pretending critical split exists" do
+ website = build_website("Tout Culture", "outside-seed")
+ cache = build_cache(signals: { "transport_success" => true, "content_success" => true })
+ website.transition_evidences.create!(
+ url: "https://example.org/event",
+ check_kind: "statement_delta",
+ status: "failed",
+ statement_delta: 99,
+ statement_count_delta_acceptable: false,
+ checked_at: 1.hour.ago,
+ details: {
+ reason: "statement_refresh_failed",
+ statements_failed_count: 99,
+ refresh_errors: ["InvalidURL from json_url"]
+ }
+ )
+ website.transition_evidences.create!(
+ url: "https://example.org/event",
+ check_kind: "export_diff",
+ status: "checked",
+ export_diff_checked: true,
+ checked_at: 1.hour.ago
+ )
+
+ status = Distillator::TransitionStatus.call(website: website, cache: cache)
+
+ assert_equal :blocked, status.status
+ assert_equal :failed, status.statements
+ assert_includes status.blockers, "Cannot activate yet: statements check failed."
+ refute_includes status.warnings, "Critical statements passed; optional statement refresh warnings need review."
+ end
+
+ test "title failure remains blocked even when export passes" do
+ website = build_website("Tout Culture", "outside-seed")
+ cache = build_cache(signals: { "transport_success" => true, "content_success" => true })
+ website.transition_evidences.create!(
+ url: "https://example.org/event",
+ check_kind: "statement_delta",
+ status: "failed",
+ statement_count_delta_acceptable: false,
+ checked_at: 1.hour.ago,
+ details: {
+ reason: "critical_statement_refresh_failed",
+ critical_statements_failed_count: 1,
+ optional_statements_failed_count: 0,
+ critical_failing_statements: [{ id: 101, source: "Title / en", severity: "blocker" }]
+ }
+ )
+ website.transition_evidences.create!(
+ url: "https://example.org/event",
+ check_kind: "export_diff",
+ status: "checked",
+ export_diff_checked: true,
+ checked_at: 1.hour.ago
+ )
+
+ status = Distillator::TransitionStatus.call(website: website, cache: cache)
+
+ assert_equal :blocked, status.status
+ assert_equal :unsafe, status.safety
+ assert_equal :failed, status.statements
+ assert_includes status.blockers, "Cannot activate yet: statements check failed."
+ end
+
+ test "dates failure remains blocked even when export passes" do
+ website = build_website("Tout Culture", "outside-seed")
+ cache = build_cache(signals: { "transport_success" => true, "content_success" => true })
+ website.transition_evidences.create!(
+ url: "https://example.org/event",
+ check_kind: "statement_delta",
+ status: "failed",
+ statement_count_delta_acceptable: false,
+ checked_at: 1.hour.ago,
+ details: {
+ reason: "critical_statement_refresh_failed",
+ critical_statements_failed_count: 1,
+ optional_statements_failed_count: 2,
+ critical_failing_statements: [{ id: 102, source: "Dates / en", severity: "blocker" }],
+ optional_failing_statements: [{ id: 103, source: "Description / en", severity: "warning" }]
+ }
+ )
+ website.transition_evidences.create!(
+ url: "https://example.org/event",
+ check_kind: "export_diff",
+ status: "checked",
+ export_diff_checked: true,
+ checked_at: 1.hour.ago
+ )
+
+ status = Distillator::TransitionStatus.call(website: website, cache: cache)
+
+ assert_equal :blocked, status.status
+ assert_equal :unsafe, status.safety
+ assert_equal :failed, status.statements
+ assert_includes status.blockers, "Cannot activate yet: statements check failed."
+ assert_not_includes status.warnings, "Critical statements passed; optional statement refresh warnings need review."
+ end
+
+ test "failed fetch with zero statement work marks statements not evaluated" do
+ website = build_website("Tout Culture", "outside-seed")
+ cache = build_cache(signals: { "transport_success" => false, "content_success" => false }, health_status: "empty_body")
+ website.transition_evidences.create!(
+ url: "https://example.org/event",
+ check_kind: "statement_delta",
+ status: "pending",
+ checked_at: 1.hour.ago,
+ details: {
+ reason: "fetch_failed_before_statement_refresh",
+ statements_refreshed_count: 0,
+ statements_failed_count: 0
+ }
+ )
+ website.transition_evidences.create!(
+ url: "https://example.org/event",
+ check_kind: "export_diff",
+ status: "checked",
+ export_diff_checked: true,
+ checked_at: 1.hour.ago
+ )
+
+ status = Distillator::TransitionStatus.call(website: website, cache: cache)
+
+ assert_equal :failed, status.fetch
+ assert_equal :not_evaluated, status.statements
+ assert_equal :passed, status.export
+ assert_equal "Cannot activate yet: fetch check failed.", status.activation_recommendation[:reason]
+ assert_equal "Fix fetch/cache first, then rerun the transition batch check.", status.activation_recommendation[:next_action]
+ end
+
+ test "no selected statements marks statements inconclusive" do
+ website = build_website("Outside Feed", "outside-feed")
+ cache = build_cache(signals: { "transport_success" => true, "content_success" => true })
+ website.transition_evidences.create!(
+ url: "https://example.org/event",
+ check_kind: "statement_delta",
+ status: "pending",
+ checked_at: 1.hour.ago,
+ details: {
+ reason: "no_selected_statements",
+ statements_refreshed_count: 0,
+ statements_failed_count: 0
+ }
+ )
+
+ status = Distillator::TransitionStatus.call(website: website, cache: cache)
+
+ assert_equal :inconclusive, status.statements
+ assert_equal "Verify selected sources/statements for the sampled webpages.", status.activation_recommendation[:next_action]
+ end
+
+ test "partial representative fetch failure keeps statements and export inconclusive" do
+ website = build_website("Outside Feed", "outside-feed")
+ cache = build_cache(signals: { "transport_success" => true, "content_success" => true })
+ website.transition_evidences.create!(
+ url: "https://example.org/event",
+ check_kind: "fetch_parity",
+ status: "failed",
+ checked_at: 1.hour.ago,
+ details: {
+ reason: "cache_health_failed",
+ failed_layer: "fetch",
+ affected_url_count: 1
+ }
+ )
+ website.transition_evidences.create!(
+ url: "https://example.org/event",
+ check_kind: "statement_delta",
+ status: "pending",
+ checked_at: 1.hour.ago,
+ details: {
+ reason: "partial_fetch_failed_before_statement_refresh",
+ representative_url_statement_results: [
+ { url: "https://example.org/one", status: "passed", reason: "ok" },
+ { url: "https://example.org/two", status: "passed", reason: "ok" },
+ { url: "https://example.org/three", status: "blocked_by_fetch", reason: "cache_health_failed" }
+ ]
+ }
+ )
+ website.transition_evidences.create!(
+ url: "https://example.org/event",
+ check_kind: "export_diff",
+ status: "pending",
+ export_diff_checked: false,
+ export_diff_status: "partial",
+ checked_at: 1.hour.ago,
+ details: {
+ reason: "partial_fetch_failed_before_export_comparison",
+ representative_url_export_results: [
+ { url: "https://example.org/one", status: "checked", reason: "ok" },
+ { url: "https://example.org/two", status: "checked", reason: "ok" },
+ { url: "https://example.org/three", status: "blocked_by_fetch", reason: "cache_health_failed" }
+ ]
+ }
+ )
+
+ status = Distillator::TransitionStatus.call(website: website, cache: cache)
+
+ assert_equal :blocked, status.status
+ assert_equal :unsafe, status.safety
+ assert_equal :failed, status.fetch
+ assert_equal :inconclusive, status.statements
+ assert_equal :inconclusive, status.export
+ assert_includes status.blockers, "Cannot activate yet: statements check is inconclusive."
+ assert_includes status.blockers, "Cannot activate yet: export check is inconclusive."
+ end
+
+ test "legacy lookup missing config keeps condenser fetch passed but marks review" do
+ website = build_website("Outside Feed", "outside-feed")
+ cache = build_cache(
+ signals: {
+ "transport_success" => true,
+ "content_success" => true,
+ "statement_count_delta_acceptable" => true,
+ "export_diff_checked" => true
+ }
+ )
+ website.transition_evidences.create!(
+ url: "https://example.org/event",
+ check_kind: "fetch_parity",
+ status: "checked",
+ checked_at: 1.hour.ago,
+ details: {
+ attempted_condenser_fetch: true,
+ condenser_fetch_success: true,
+ comparison_performed: false,
+ legacy_lookup_status: "missing_config",
+ legacy_lookup_error: "missing_config",
+ reason: "legacy_lookup_missing_config"
+ }
+ )
+
+ status = Distillator::TransitionStatus.call(website: website, cache: cache)
+
+ assert_equal :review, status.status
+ assert_equal :passed, status.fetch
+ assert_includes status.warnings, "Needs review: legacy Wringer endpoint is not configured for this environment."
+ assert_equal "Configure the Wringer endpoint for staging, then rerun the transition batch check.", status.activation_recommendation[:next_action]
+ end
+
+ test "legacy lookup unreachable keeps condenser fetch passed but marks review" do
+ website = build_website("Outside Feed", "outside-feed")
+ cache = build_cache(
+ signals: {
+ "transport_success" => true,
+ "content_success" => true,
+ "statement_count_delta_acceptable" => true,
+ "export_diff_checked" => true
+ }
+ )
+ website.transition_evidences.create!(
+ url: "https://example.org/event",
+ check_kind: "fetch_parity",
+ status: "checked",
+ checked_at: 1.hour.ago,
+ details: {
+ attempted_condenser_fetch: true,
+ condenser_fetch_success: true,
+ comparison_performed: false,
+ legacy_lookup_status: "unreachable",
+ legacy_lookup_error: "connection refused",
+ reason: "legacy_lookup_unreachable"
+ }
+ )
+
+ status = Distillator::TransitionStatus.call(website: website, cache: cache)
+
+ assert_equal :review, status.status
+ assert_equal :passed, status.fetch
+ assert_includes status.warnings, "Needs review: legacy Wringer lookup failed during the latest transition batch check."
+ assert_equal "Fix the legacy Wringer endpoint, then rerun the transition batch check.", status.activation_recommendation[:next_action]
+ end
+
+ test "legacy lookup body omitted keeps condenser fetch passed but marks review" do
+ website = build_website("Outside Feed", "outside-feed")
+ cache = build_cache(
+ signals: {
+ "transport_success" => true,
+ "content_success" => true,
+ "statement_count_delta_acceptable" => true,
+ "export_diff_checked" => true
+ }
+ )
+ website.transition_evidences.create!(
+ url: "https://example.org/event",
+ check_kind: "fetch_parity",
+ status: "checked",
+ checked_at: 1.hour.ago,
+ details: {
+ attempted_condenser_fetch: true,
+ condenser_fetch_success: true,
+ comparison_performed: false,
+ legacy_lookup_status: "body_omitted",
+ legacy_lookup_error: "legacy_body_omitted",
+ reason: "legacy_lookup_body_omitted"
+ }
+ )
+
+ status = Distillator::TransitionStatus.call(website: website, cache: cache)
+
+ assert_equal :review, status.status
+ assert_equal :passed, status.fetch
+ assert_includes status.warnings, "Needs review: legacy Wringer body was omitted from the comparison endpoint."
+ assert_not_includes status.blockers, "Cannot activate yet: fetch check failed."
+ assert_equal false, status.review_activation_eligible
+ assert_equal true, status.manual_review_required
+ assert_equal "Verify the legacy Wringer body endpoint or compare using the legacy inspection link.", status.activation_recommendation[:next_action]
+ end
+
+ test "review-needed parity difference stays in review without claiming fetch failure" do
+ website = build_website("Outside Feed", "outside-feed")
+ cache = build_cache(
+ signals: {
+ "transport_success" => true,
+ "content_success" => true,
+ "statement_count_delta_acceptable" => true,
+ "export_diff_checked" => true
+ }
+ )
+ website.transition_evidences.create!(
+ url: "https://example.org/event",
+ check_kind: "fetch_parity",
+ status: "checked",
+ checked_at: 1.hour.ago,
+ details: {
+ attempted_condenser_fetch: true,
+ condenser_fetch_success: true,
+ comparison_performed: true,
+ reason: "review_needed_difference"
+ }
+ )
+
+ status = Distillator::TransitionStatus.call(website: website, cache: cache)
+
+ assert_equal :review, status.status
+ assert_equal :passed, status.fetch
+ assert_not_includes status.blockers, "Cannot activate yet: fetch check failed."
+ assert_includes status.warnings, "Needs review: Condenser and Wringer differ in fields that need manual verification."
+ assert_equal :review, status.safety
+ assert_equal :low, status.confidence
+ assert_equal true, status.review_activation_eligible
+ assert_equal false, status.manual_review_required
+ assert_equal "Review the checklist, then activate with a recorded reason.", status.activation_recommendation[:next_action]
+ end
+
+ test "metadata only difference stays ready with medium confidence" do
+ website = build_website("Outside Feed", "outside-feed")
+ cache = build_cache(
+ signals: {
+ "transport_success" => true,
+ "content_success" => true,
+ "statement_count_delta_acceptable" => true,
+ "export_diff_checked" => true
+ }
+ )
+ website.transition_evidences.create!(
+ url: "https://example.org/event",
+ check_kind: "fetch_parity",
+ status: "checked",
+ checked_at: 1.hour.ago,
+ details: {
+ attempted_condenser_fetch: true,
+ condenser_fetch_success: true,
+ comparison_performed: true,
+ reason: "metadata_only_difference"
+ }
+ )
+
+ status = Distillator::TransitionStatus.call(website: website, cache: cache)
+
+ assert_equal :ready, status.status
+ assert_equal :safe, status.safety
+ assert_equal :medium, status.confidence
+ assert_equal false, status.review_activation_eligible
+ assert_equal false, status.manual_review_required
+ assert_equal "Metadata notes only. Promote to active when you are satisfied with the evidence.", status.activation_recommendation[:next_action]
+ end
+
+ test "timeout budget exceeded keeps statements and export inconclusive while fetch stays failed" do
+ website = build_website("Outside Feed", "outside-feed")
+ cache = build_cache(signals: { "transport_success" => true, "content_success" => true })
+ website.transition_evidences.create!(
+ url: "https://example.org/event",
+ check_kind: "fetch_parity",
+ status: "failed",
+ checked_at: 1.hour.ago,
+ details: {
+ reason: "transition_check_timeout_budget_exceeded",
+ failed_layer: "fetch",
+ affected_url_count: 1
+ }
+ )
+ website.transition_evidences.create!(
+ url: "https://example.org/event",
+ check_kind: "statement_delta",
+ status: "pending",
+ checked_at: 1.hour.ago,
+ details: {
+ reason: "transition_check_timeout_budget_exceeded",
+ representative_url_statement_results: [
+ { url: "https://example.org/one", status: "inconclusive", reason: "transition_check_timeout_budget_exceeded" },
+ { url: "https://example.org/two", status: "blocked_by_fetch", reason: "transition_check_timeout_budget_exceeded" }
+ ]
+ }
+ )
+ website.transition_evidences.create!(
+ url: "https://example.org/event",
+ check_kind: "export_diff",
+ status: "pending",
+ export_diff_checked: false,
+ export_diff_status: "pending",
+ checked_at: 1.hour.ago,
+ details: {
+ reason: "transition_check_timeout_budget_exceeded",
+ representative_url_export_results: [
+ { url: "https://example.org/one", status: "inconclusive", reason: "transition_check_timeout_budget_exceeded" },
+ { url: "https://example.org/two", status: "blocked_by_fetch", reason: "transition_check_timeout_budget_exceeded" }
+ ]
+ }
+ )
+
+ status = Distillator::TransitionStatus.call(website: website, cache: cache)
+
+ assert_equal :blocked, status.status
+ assert_equal :unsafe, status.safety
+ assert_equal :failed, status.fetch
+ assert_equal :inconclusive, status.statements
+ assert_equal :inconclusive, status.export
+ end
+
+ private
+
+ def build_website(name, seedurl)
+ Website.create!(
+ name: name,
+ seedurl: seedurl,
+ graph_name: "https://example.org/#{seedurl}",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+ end
+
+ def build_cache(signals:, health_status: "healthy")
+ Distillator::FetchCache.new(
+ uri_key: CGI.escape("https://example.org/event"),
+ normalized_url: "https://example.org/event",
+ signals: signals,
+ health_status: health_status,
+ successful_refresh: 1.hour.ago,
+ scrape_date: 1.hour.ago
+ )
+ end
+end
diff --git a/test/services/distillator/url_safety_policy_test.rb b/test/services/distillator/url_safety_policy_test.rb
new file mode 100644
index 00000000..bda68c60
--- /dev/null
+++ b/test/services/distillator/url_safety_policy_test.rb
@@ -0,0 +1,73 @@
+require "test_helper"
+
+class Distillator::UrlSafetyPolicyTest < ActiveSupport::TestCase
+ class StaticResolver
+ def initialize(addresses)
+ @addresses = addresses
+ end
+
+ def getaddresses(_host)
+ @addresses
+ end
+ end
+
+ class RaisingResolver
+ def initialize(error)
+ @error = error
+ end
+
+ def getaddresses(_host)
+ raise @error
+ end
+ end
+
+ test "blocks blank dns resolution result" do
+ decision = Distillator::UrlSafetyPolicy.check_url(
+ "https://missing.example/events",
+ resolver: StaticResolver.new([])
+ )
+
+ refute decision.allowed?
+ assert_equal :dns_resolution_failed, decision.reason
+ assert_includes decision.message, "no DNS resolution result"
+ end
+
+ test "blocks dns resolver exceptions" do
+ decision = Distillator::UrlSafetyPolicy.check_url(
+ "https://timeout.example/events",
+ resolver: RaisingResolver.new(Timeout::Error.new("execution expired"))
+ )
+
+ refute decision.allowed?
+ assert_equal :dns_resolution_error, decision.reason
+ assert_includes decision.message, "Timeout::Error"
+ end
+
+ test "allows public http and https urls when dns resolves publicly" do
+ http_decision = Distillator::UrlSafetyPolicy.check_url(
+ "http://example.org/events",
+ resolver: StaticResolver.new(["93.184.216.34"])
+ )
+ https_decision = Distillator::UrlSafetyPolicy.check_url(
+ "https://example.org/events",
+ resolver: StaticResolver.new(["93.184.216.34"])
+ )
+
+ assert http_decision.allowed?
+ assert https_decision.allowed?
+ end
+
+ test "blocks final url that resolves to private ip after redirect handling" do
+ decision = Distillator::UrlSafetyPolicy.check_response(
+ {
+ final_url: "https://private.example/final",
+ redirect_chain: ["https://example.org/start"]
+ },
+ resolver: StaticResolver.new(["10.0.0.1"])
+ )
+
+ refute decision.allowed?
+ assert_equal :blocked_private_ip, decision.reason
+ assert_includes decision.message, "10.0.0.1"
+ end
+end
diff --git a/test/services/distillator/webpage_removal_candidate_test.rb b/test/services/distillator/webpage_removal_candidate_test.rb
new file mode 100644
index 00000000..25282778
--- /dev/null
+++ b/test/services/distillator/webpage_removal_candidate_test.rb
@@ -0,0 +1,148 @@
+require "test_helper"
+
+class Distillator::WebpageRemovalCandidateTest < ActiveSupport::TestCase
+ setup do
+ Distillator::FetchCache.delete_all
+ end
+
+ test "allows deletion when cache is a non retryable delete candidate with no newer success" do
+ cache = Distillator::FetchCache.create!(
+ uri_key: Distillator::WringerUrlKey.call("https://example.org/events").uri_key,
+ normalized_url: "https://example.org/events",
+ scrape_date: Time.zone.now,
+ successful_refresh: 2.days.ago,
+ http_response_code: 404,
+ final_url: "https://example.org/events",
+ signals: { "primary_issue_key" => "redirect_to_listing", "redirect_type" => "normal", "network_status" => "ok" },
+ hints: ["redirect_to_listing"],
+ primary_issue_key: "redirect_to_listing",
+ delete_candidate: true
+ )
+
+ result = Distillator::WebpageRemovalCandidate.call("https://example.org/events")
+
+ assert_equal cache, result.cache
+ assert_equal true, result.delete?
+ assert_equal :delete_candidate, result.reason
+ end
+
+ test "does not delete retryable anti bot issues" do
+ Distillator::FetchCache.create!(
+ uri_key: Distillator::WringerUrlKey.call("https://example.org/events").uri_key,
+ normalized_url: "https://example.org/events",
+ scrape_date: Time.zone.now,
+ successful_refresh: 2.days.ago,
+ http_response_code: 200,
+ signals: { "primary_issue_key" => "queue_it" },
+ hints: ["queue_it"],
+ primary_issue_key: "queue_it",
+ delete_candidate: false
+ )
+
+ result = Distillator::WebpageRemovalCandidate.call("https://example.org/events")
+
+ assert_equal false, result.delete?
+ assert_equal :not_delete_candidate, result.reason
+ end
+
+ test "does not delete when a newer successful refresh exists after the failed scrape" do
+ Distillator::FetchCache.create!(
+ uri_key: Distillator::WringerUrlKey.call("https://example.org/events").uri_key,
+ normalized_url: "https://example.org/events",
+ scrape_date: 2.days.ago,
+ successful_refresh: 1.day.ago,
+ http_response_code: 404,
+ final_url: "https://example.org/events",
+ signals: { "primary_issue_key" => "redirect_to_listing", "redirect_type" => "normal", "network_status" => "ok" },
+ hints: ["redirect_to_listing"],
+ primary_issue_key: "redirect_to_listing",
+ delete_candidate: true
+ )
+
+ result = Distillator::WebpageRemovalCandidate.call("https://example.org/events")
+
+ assert_not result.delete?
+ assert_equal :newer_successful_refresh, result.reason
+ end
+
+ test "does not delete when cache delete candidate is stale but yaml policy no longer allows delete" do
+ cache = Distillator::FetchCache.create!(
+ uri_key: Distillator::WringerUrlKey.call("https://example.org/events").uri_key,
+ normalized_url: "https://example.org/events",
+ scrape_date: Time.zone.now,
+ successful_refresh: 2.days.ago,
+ primary_issue_key: "queue_it",
+ delete_candidate: true,
+ signals: { "primary_issue_key" => "queue_it" },
+ hints: ["queue_it"]
+ )
+
+ result = Distillator::WebpageRemovalCandidate.call("https://example.org/events")
+
+ assert_not result.delete?
+ assert_equal :not_delete_candidate, result.reason
+ end
+
+ test "does not delete when successful refresh is at same time as scrape date" do
+ timestamp = Time.zone.now
+ source_url = "https://example.org/source-page"
+
+ cache = Distillator::FetchCache.create!(
+ uri_key: Distillator::WringerUrlKey.call(source_url).uri_key,
+ normalized_url: source_url,
+ scrape_date: timestamp,
+ successful_refresh: timestamp,
+ http_response_code: 200,
+ final_url: "https://example.org/events",
+ redirect_chain: [source_url, "https://example.org/events"],
+ signals: {
+ "primary_issue_key" => "redirect_to_listing",
+ "primary_issue_delete" => true,
+ "redirect_type" => "normal",
+ "network_status" => "ok"
+ },
+ hints: ["redirect_to_listing"],
+ primary_issue_key: "redirect_to_listing",
+ delete_candidate: true
+ )
+
+ cache.reload
+ assert_equal true, cache.delete_candidate
+ assert_equal "redirect_to_listing", cache.primary_issue_key
+
+ result = Distillator::WebpageRemovalCandidate.call(source_url)
+
+ assert_equal false, result.delete?
+ assert_equal :newer_successful_refresh, result.reason
+ end
+
+ test "uses primary issue key from signals when materialized column is missing" do
+ source_url = "https://example.org/source-page"
+
+ cache = Distillator::FetchCache.create!(
+ uri_key: Distillator::WringerUrlKey.call(source_url).uri_key,
+ normalized_url: source_url,
+ scrape_date: Time.zone.now,
+ successful_refresh: 2.days.ago,
+ http_response_code: 200,
+ final_url: "https://example.org/events",
+ redirect_chain: [source_url, "https://example.org/events"],
+ signals: {
+ "primary_issue_key" => "redirect_to_listing",
+ "primary_issue_delete" => true,
+ "redirect_type" => "normal",
+ "network_status" => "ok"
+ },
+ hints: ["redirect_to_listing"],
+ delete_candidate: true
+ )
+
+ cache.update_column(:primary_issue_key, nil)
+
+ result = Distillator::WebpageRemovalCandidate.call(source_url)
+
+ assert_equal true, result.delete?
+ assert_equal :delete_candidate, result.reason
+ end
+
+end
diff --git a/test/services/distillator/website_webpage_summary_test.rb b/test/services/distillator/website_webpage_summary_test.rb
new file mode 100644
index 00000000..f0320b9b
--- /dev/null
+++ b/test/services/distillator/website_webpage_summary_test.rb
@@ -0,0 +1,90 @@
+require "test_helper"
+
+class Distillator::WebsiteWebpageSummaryTest < ActiveSupport::TestCase
+ test "summarizes webpage totals kinds classes and publishability per website" do
+ website_a = build_website("summary-a")
+ website_b = build_website("summary-b")
+
+ resource_list_class = RdfsClass.create!(name: "ResourceList")
+ web_page_class = RdfsClass.create!(name: "WebPage")
+ unknown_class = RdfsClass.create!(name: "Thingish")
+
+ publishable_event = create_webpage(website_a, suffix: "event-publishable", url: "https://example.org/events/1", rdf_uri: "rdf:summary:event:1", rdfs_class: rdfs_classes(:one))
+ create_publishable_statements_for(publishable_event)
+
+ create_webpage(website_a, suffix: "event-blocked", url: "https://example.org/events/2", rdf_uri: "rdf:summary:event:2", rdfs_class: rdfs_classes(:one))
+ create_webpage(website_a, suffix: "person", url: "footlight:summary:person", rdf_uri: "rdf:summary:person", rdfs_class: rdfs_classes(:person))
+ create_webpage(website_a, suffix: "place", url: "footlight:summary:place", rdf_uri: "rdf:summary:place", rdfs_class: rdfs_classes(:place))
+ create_webpage(website_a, suffix: "resource-list", url: "https://example.org/resources", rdf_uri: "rdf:summary:resources", rdfs_class: resource_list_class)
+ create_webpage(website_a, suffix: "webpage", url: "https://example.org/page", rdf_uri: "rdf:summary:page", rdfs_class: web_page_class)
+ create_webpage(website_a, suffix: "other", url: "footlight:summary:other", rdf_uri: "rdf:summary:other", rdfs_class: unknown_class)
+ nil_class_page = create_webpage(website_a, suffix: "nil-class", url: "footlight:summary:nil", rdf_uri: "rdf:summary:nil", rdfs_class: rdfs_classes(:one))
+ nil_class_page.update_column(:rdfs_class_id, nil)
+
+ create_webpage(website_b, suffix: "website-b", url: "https://example.org/website-b", rdf_uri: "rdf:summary:website-b", rdfs_class: rdfs_classes(:place))
+
+ summaries = Distillator::WebsiteWebpageSummary.for_websites([website_a.id, website_b.id])
+
+ assert_equal 8, summaries.fetch(website_a.id)[:total]
+ assert_equal 4, summaries.fetch(website_a.id)[:public_urls]
+ assert_equal 4, summaries.fetch(website_a.id)[:internal_uris]
+ assert_equal 2, summaries.fetch(website_a.id)[:by_class]["Event"]
+ assert_equal 1, summaries.fetch(website_a.id)[:by_class]["Person"]
+ assert_equal 1, summaries.fetch(website_a.id)[:by_class]["Place"]
+ assert_equal 1, summaries.fetch(website_a.id)[:by_class]["ResourceList"]
+ assert_equal 1, summaries.fetch(website_a.id)[:by_class]["WebPage"]
+ assert_equal 2, summaries.fetch(website_a.id)[:by_class]["Other"]
+ assert_equal 1, summaries.fetch(website_a.id)[:publishable]
+ assert_equal 7, summaries.fetch(website_a.id)[:not_publishable]
+
+ assert_equal 1, summaries.fetch(website_b.id)[:total]
+ assert_equal 0, summaries.fetch(website_b.id)[:by_class]["Event"]
+ assert_equal 0, summaries.fetch(website_b.id)[:publishable]
+ assert_equal 1, summaries.fetch(website_b.id)[:not_publishable]
+ end
+
+ private
+
+ def build_website(seedurl)
+ Website.create!(
+ name: "Summary #{seedurl}",
+ seedurl: seedurl,
+ graph_name: "https://example.org/#{seedurl}",
+ default_language: "en"
+ )
+ end
+
+ def create_webpage(website, suffix:, url:, rdf_uri:, rdfs_class:)
+ Webpage.create!(
+ url: "#{url}-#{suffix}",
+ language: "en",
+ rdf_uri: rdf_uri,
+ rdfs_class: rdfs_class,
+ website: website
+ )
+ end
+
+ def create_publishable_statements_for(webpage)
+ [
+ [properties(:four), "Publishable title"],
+ [properties(:location), '[["Salle","uri:place"]]'],
+ [properties(:six), '["2026-06-01T20:00:00-04:00"]']
+ ].each do |property, cache|
+ source = Source.create!(
+ website: webpage.website,
+ property: property,
+ language: "en",
+ selected: true,
+ algorithm_value: "summary-test"
+ )
+
+ Statement.create!(
+ webpage: webpage,
+ source: source,
+ cache: cache,
+ status: "ok"
+ )
+ Statement.where(webpage: webpage, source: source).update_all(status: "ok")
+ end
+ end
+end
diff --git a/test/services/distillator/wringer_endpoint_test.rb b/test/services/distillator/wringer_endpoint_test.rb
new file mode 100644
index 00000000..00c5a4e7
--- /dev/null
+++ b/test/services/distillator/wringer_endpoint_test.rb
@@ -0,0 +1,108 @@
+require "test_helper"
+
+class Distillator::WringerEndpointTest < ActiveSupport::TestCase
+ test "staging resolves remote endpoint from DISTILLATOR_COMPATIBILITY_BASE_URL when config is nil" do
+ endpoint = Distillator::WringerEndpoint.new(
+ config: nil,
+ env: ActiveSupport::StringInquirer.new("staging"),
+ env_vars: {
+ "DISTILLATOR_COMPATIBILITY_BASE_URL" => "https://footlight-wringer.herokuapp.com"
+ }
+ ).call
+
+ assert_equal :remote_configured, endpoint.state
+ assert_equal "https://footlight-wringer.herokuapp.com", endpoint.compatibility_base_url
+ assert_equal "https://footlight-wringer.herokuapp.com", endpoint.legacy_lookup_base_url
+ assert_equal "DISTILLATOR_COMPATIBILITY_BASE_URL", endpoint.compatibility_source
+ assert_equal "Current Wringer: Remote configured", endpoint.status_label
+ assert_equal "https://footlight-wringer.herokuapp.com via DISTILLATOR_COMPATIBILITY_BASE_URL", endpoint.status_detail
+ end
+
+ test "staging resolves remote endpoint from DISTILLATOR_COMPAT_BASE_URL when config is nil" do
+ endpoint = Distillator::WringerEndpoint.new(
+ config: nil,
+ env: ActiveSupport::StringInquirer.new("staging"),
+ env_vars: {
+ "DISTILLATOR_COMPAT_BASE_URL" => "https://footlight-wringer.herokuapp.com"
+ }
+ ).call
+
+ assert_equal :remote_configured, endpoint.state
+ assert_equal "https://footlight-wringer.herokuapp.com", endpoint.compatibility_base_url
+ assert_equal "https://footlight-wringer.herokuapp.com", endpoint.legacy_lookup_base_url
+ assert_equal "DISTILLATOR_COMPAT_BASE_URL", endpoint.compatibility_source
+ assert_equal "https://footlight-wringer.herokuapp.com via DISTILLATOR_COMPAT_BASE_URL", endpoint.status_detail
+ end
+
+ test "staging is missing config when both compatibility env aliases are blank" do
+ endpoint = Distillator::WringerEndpoint.new(
+ config: nil,
+ env: ActiveSupport::StringInquirer.new("staging"),
+ env_vars: {}
+ ).call
+
+ assert_equal :missing_config, endpoint.state
+ assert_nil endpoint.compatibility_base_url
+ assert_nil endpoint.legacy_lookup_base_url
+ end
+
+ test "development still allows localhost by default when config is nil" do
+ endpoint = Distillator::WringerEndpoint.new(
+ config: nil,
+ env: ActiveSupport::StringInquirer.new("development"),
+ env_vars: {}
+ ).call
+
+ assert_equal :local_development, endpoint.state
+ assert_equal "http://localhost:3000", endpoint.compatibility_base_url
+ assert_equal "http://localhost:3009", endpoint.legacy_lookup_base_url
+ assert_equal "development_default", endpoint.compatibility_source
+ end
+
+ test "legacy wringer env overrides compatibility fallback when configured" do
+ endpoint = Distillator::WringerEndpoint.new(
+ config: nil,
+ env: ActiveSupport::StringInquirer.new("staging"),
+ env_vars: {
+ "DISTILLATOR_COMPATIBILITY_BASE_URL" => "https://footlight-wringer.herokuapp.com",
+ "LEGACY_WRINGER_BASE_URL" => "https://legacy.example"
+ }
+ ).call
+
+ assert_equal :remote_configured, endpoint.state
+ assert_equal "https://footlight-wringer.herokuapp.com", endpoint.compatibility_base_url
+ assert_equal "https://legacy.example", endpoint.legacy_lookup_base_url
+ assert_equal "DISTILLATOR_COMPATIBILITY_BASE_URL", endpoint.compatibility_source
+ end
+
+ test "canonical compat env wins over alias when both are set" do
+ endpoint = Distillator::WringerEndpoint.new(
+ config: nil,
+ env: ActiveSupport::StringInquirer.new("staging"),
+ env_vars: {
+ "DISTILLATOR_COMPAT_BASE_URL" => "https://canonical.example",
+ "DISTILLATOR_COMPATIBILITY_BASE_URL" => "https://alias.example"
+ }
+ ).call
+
+ assert_equal :remote_configured, endpoint.state
+ assert_equal "https://canonical.example", endpoint.compatibility_base_url
+ assert_equal "DISTILLATOR_COMPAT_BASE_URL", endpoint.compatibility_source
+ assert_equal "https://canonical.example via DISTILLATOR_COMPAT_BASE_URL", endpoint.status_detail
+ end
+
+ test "configured endpoint is only marked unreachable when a recent lookup error is provided" do
+ endpoint = Distillator::WringerEndpoint.new(
+ config: nil,
+ env: ActiveSupport::StringInquirer.new("staging"),
+ env_vars: {
+ "DISTILLATOR_COMPAT_BASE_URL" => "https://canonical.example"
+ },
+ last_error: "connection refused"
+ ).call
+
+ assert_equal :unreachable, endpoint.state
+ assert_equal "Current Wringer: Unreachable", endpoint.status_label
+ assert_equal "last lookup failed", endpoint.status_detail
+ end
+end
diff --git a/test/services/distillator/wringer_issue_set_test.rb b/test/services/distillator/wringer_issue_set_test.rb
new file mode 100644
index 00000000..73f3ce35
--- /dev/null
+++ b/test/services/distillator/wringer_issue_set_test.rb
@@ -0,0 +1,48 @@
+require "test_helper"
+
+class Distillator::WringerIssueSetTest < ActiveSupport::TestCase
+ test "select_primary prefers higher severity issue over yaml order" do
+ matches = [
+ {
+ key: "empty_body",
+ rule: { "category" => "content", "severity" => "warning" }
+ },
+ {
+ key: "http_5xx",
+ rule: { "category" => "http", "severity" => "failed" }
+ }
+ ]
+
+ assert_equal "http_5xx", Distillator::WringerIssueSet.select_primary(matches)[:key]
+ end
+
+ test "call returns severity-aware primary issue" do
+ issue_set = Distillator::WringerIssueSet.call(
+ body: "",
+ http_code: 500,
+ final_url: "https://example.org/failure",
+ hints: ["empty_body"],
+ signals: {}
+ )
+
+ assert_equal "http_5xx", issue_set.primary[:key]
+ assert_equal "http_server_error", issue_set.primary[:error_type]
+ end
+
+ test "phantomjs unavailable is primary when no stronger issue exists" do
+ issue_set = Distillator::WringerIssueSet.call(
+ body: nil,
+ http_code: nil,
+ final_url: "https://example.org/rendered",
+ hints: ["legacy_phantomjs", "phantomjs_unavailable"],
+ signals: { renderer_unavailable: true, renderer: "legacy_phantomjs" }
+ )
+
+ assert_includes issue_set.matches.map { |match| match[:key] }, "legacy_phantomjs"
+ assert_includes issue_set.matches.map { |match| match[:key] }, "phantomjs_unavailable"
+ assert_equal "phantomjs_unavailable", issue_set.primary[:key]
+ assert_equal "abort_update", issue_set.primary[:action]
+ assert_equal false, issue_set.primary[:cache]
+ assert_equal true, issue_set.primary[:retry]
+ end
+end
diff --git a/test/services/distillator/wringer_rules_test.rb b/test/services/distillator/wringer_rules_test.rb
new file mode 100644
index 00000000..6252c7b3
--- /dev/null
+++ b/test/services/distillator/wringer_rules_test.rb
@@ -0,0 +1,54 @@
+require "test_helper"
+
+class Distillator::WringerRulesTest < ActiveSupport::TestCase
+ setup do
+ Distillator::WringerRules.reset!
+ end
+
+ teardown do
+ Distillator::WringerRules.reset!
+ end
+
+ test "loads rules in declared yaml order" do
+ keys = Distillator::WringerRules.all.map(&:first)
+
+ assert_operator keys.index("cloudflare"), :<, keys.index("empty_body")
+ assert_operator keys.index("queue_it"), :<, keys.index("http_403")
+ assert_operator keys.index("akamai_bot_protection"), :<, keys.index("http_5xx")
+ assert_operator keys.index("redirect_to_listing"), :<, keys.index("http_404")
+ end
+
+ test "all rules include match policy and ui metadata" do
+ Distillator::WringerRules.all.each do |key, rule|
+ assert rule["match"].present?, "#{key} is missing match"
+ assert rule["policy"].present?, "#{key} is missing policy"
+ assert rule["label"].present?, "#{key} is missing label"
+ assert rule["severity"].present?, "#{key} is missing severity"
+ assert rule["category"].present?, "#{key} is missing category"
+ end
+ end
+
+ test "apify legacy keywords are covered by exported yaml rules" do
+ rules = Distillator::WringerRules.all.map(&:last)
+ body_terms = rules.flat_map { |rule| Array(rule.dig("match", "body_contains")) }
+ body_text_terms = rules.flat_map { |rule| Array(rule.dig("match", "body_text_contains")) }
+ http_codes = rules.flat_map { |rule| Array(rule.dig("match", "http_code")) }.map(&:to_i)
+
+ %w[Reservatech Queue-it captcha Forbidden Waiting\ room POST\ call].each do |term|
+ assert (body_terms.include?(term) || body_text_terms.include?(term)), "#{term.inspect} should be covered by a rule"
+ end
+ assert_includes body_terms, "Salle d'attente"
+ assert_includes body_text_terms, "Une erreur est survenue"
+ assert_includes body_text_terms, "An error occurred"
+ assert_includes http_codes, 403
+ assert_includes http_codes, 500
+ end
+
+ test "generic error text rule uses body text phrases instead of a broad html substring" do
+ generic = Distillator::WringerRules.find("generic_error_text").last
+
+ assert_equal [], Array(generic.dig("match", "body_contains"))
+ assert_includes Array(generic.dig("match", "body_text_contains")), "Une erreur est survenue"
+ assert_includes Array(generic.dig("match", "body_text_contains")), "An error occurred"
+ end
+end
diff --git a/test/services/distillator/wringer_system_error_matcher_test.rb b/test/services/distillator/wringer_system_error_matcher_test.rb
new file mode 100644
index 00000000..efcbad6e
--- /dev/null
+++ b/test/services/distillator/wringer_system_error_matcher_test.rb
@@ -0,0 +1,168 @@
+require "test_helper"
+
+class Distillator::WringerSystemErrorMatcherTest < ActiveSupport::TestCase
+ test "matches high specificity and generic yaml rules in order" do
+ assert_equal "system_cloudflare", match_for(body: "Attention Required by Cloudflare", http_code: 200)[:error_type]
+ assert_equal "system_queue", match_for(body: "Queue-it Please wait while we redirect you", http_code: 200)[:error_type]
+ assert_equal "system_akamai", match_for(body: "Access Denied Reference #123", http_code: 200)[:error_type]
+ assert_equal "empty_body", match_for(body: "", http_code: 200)[:error_type]
+ assert_equal "html_error_page", match_for(body: "Internal Server Error", http_code: 200)[:error_type]
+ assert_equal "redirect_to_listing", match_for(body: "ok", http_code: 200, final_url: "https://example.org/events", signals: { redirect_type: "normal" })[:error_type]
+ assert_equal "http_404", match_for(body: "Not Found", http_code: 404)[:error_type]
+ assert_equal "http_server_error", match_for(body: "Server Error", http_code: 500)[:error_type]
+ end
+
+ test "body blank matches nil and empty string" do
+ rules = [["empty_body", { "match" => { "body_blank" => true }, "policy" => { "error_code" => "empty_body", "retry" => false, "cache" => false, "delete" => false } }]]
+
+ nil_match = Distillator::WringerSystemErrorMatcher.call(body: nil, http_code: 200, final_url: "https://example.org/item", rules: rules)
+ empty_match = Distillator::WringerSystemErrorMatcher.call(body: "", http_code: 200, final_url: "https://example.org/item", rules: rules)
+
+ assert_equal "empty_body", nil_match[:error_type]
+ assert_equal false, nil_match[:retry]
+ assert_equal false, nil_match[:cache]
+ assert_equal false, nil_match[:delete]
+ assert_equal "empty_body", empty_match[:error_type]
+ end
+
+ test "matches apify legacy additions" do
+ assert_equal "system_reservatech_waiting_room", match_for(body: "Reservatech waiting room", http_code: 200)[:error_type]
+ assert_equal "system_salle_attente", match_for(body: "Salle d'attente", http_code: 200)[:error_type]
+ assert_equal "system_captcha", match_for(body: "captcha", http_code: 200)[:error_type]
+ assert_equal "forbidden_text", match_for(body: "Forbidden", http_code: 200)[:error_type]
+ assert_equal "generic_error_text", match_for(body: "Une erreur est survenue ", http_code: 200)[:error_type]
+ assert_equal "post_call_observed", match_for(body: "POST call", http_code: 200)[:error_type]
+ assert_equal "http_403", match_for(body: "Nope", http_code: 403)[:error_type]
+ end
+
+ test "generic error text captures a matched snippet from body text" do
+ issue = match_for(body: "Une erreur est survenue Veuillez reessayer.
", http_code: 200)
+
+ assert_equal "generic_error_text", issue[:key]
+ assert_equal "body_text", issue.dig(:match_details, :source)
+ assert_equal "Une erreur est survenue", issue.dig(:match_details, :pattern)
+ assert_match "Une erreur est survenue", issue.dig(:match_details, :snippet)
+ end
+
+ test "generic error text does not match ordinary page text with error words in markup" do
+ issue = match_for(body: "Festival program Welcome.
", http_code: 200)
+
+ assert_nil issue
+ end
+
+ test "matches by hint and signal" do
+ assert_equal "blocked_url", match_for(body: ["abort_update"], http_code: nil, hints: ["blocked_url"], signals: { network_status: "blocked" })[:error_type]
+ assert_equal "timeout", match_for(body: ["abort_update"], http_code: nil, hints: ["timeout"], signals: { network_status: "failed" })[:error_type]
+ assert_equal "ssl_verify_none_fallback", match_for(body: "ok", http_code: 200, hints: ["ssl_verify_none_fallback"], signals: { ssl_verify_none_fallback: true })[:error_type]
+ assert_equal "legacy_phantomjs", match_for(body: "ok", http_code: 200, hints: ["legacy_phantomjs"], signals: { fetch_backend: "phantomjs" })[:error_type]
+ assert_equal "phantomjs_unavailable", match_for(body: nil, http_code: nil, hints: ["phantomjs_unavailable"], signals: { renderer_unavailable: true, renderer: "legacy_phantomjs" })[:error_type]
+ assert_equal "json_post", match_for(body: '{"ok":true}', http_code: 200, signals: { request_method: "POST", content_type: "json" })[:error_type]
+ end
+
+ test "all matches can prefer failure over info for primary issue selection" do
+ issue_set = Distillator::WringerIssueSet.call(
+ body: nil,
+ http_code: 500,
+ final_url: "https://example.org/item",
+ hints: ["legacy_phantomjs"],
+ signals: { request_method: "POST", content_type: "json" }
+ )
+
+ assert_includes issue_set.matches.map { |match| match[:key] }, "json_post"
+ assert_includes issue_set.matches.map { |match| match[:key] }, "http_5xx"
+ assert_equal "http_5xx", issue_set.primary[:key]
+ end
+
+ test "call prefers failed http issue over warning empty body" do
+ issue = Distillator::WringerSystemErrorMatcher.call(
+ body: "",
+ http_code: 500,
+ final_url: "https://example.org/failure",
+ hints: ["empty_body"],
+ signals: {}
+ )
+
+ assert_equal "http_5xx", issue[:key]
+ assert_equal "http_server_error", issue[:error_type]
+ end
+
+ test "call prefers redirect delete issue over empty body when redirect signals match" do
+ issue = Distillator::WringerSystemErrorMatcher.call(
+ body: "",
+ http_code: 200,
+ final_url: "https://example.org/events",
+ hints: ["empty_body"],
+ signals: { redirect_type: "normal" }
+ )
+
+ assert_equal "redirect_to_listing", issue[:key]
+ assert_equal true, issue[:delete]
+ end
+
+ test "detail page markers block redirect_to_listing legacy body classification" do
+ body = <<~HTML
+
+
+
+
Listing styles are present
+
Match d'improvisation | LPIA | Sainte-Anne-de-la-Pérade
+
29 janvier 2026
+
19:00
+
+
+
+ HTML
+
+ issue = match_for(body: body, http_code: 200, final_url: "https://tourismedeschenaux.ca/evenements")
+
+ assert_nil issue
+ end
+
+ test "genuine legacy listing fallback still matches redirect_to_listing with snippet" do
+ body = <<~HTML
+
+
+ Listing fallback card
+ All events
+
+
+ HTML
+
+ issue = match_for(body: body, http_code: 200, final_url: "https://tourismedeschenaux.ca/evenements")
+
+ assert_equal "redirect_to_listing", issue[:error_type]
+ assert_equal "html", issue.dig(:match_details, :source)
+ assert_equal "jet-listing-dynamic-field", issue.dig(:match_details, :pattern)
+ assert_match "jet-listing-dynamic-field", issue.dig(:match_details, :snippet)
+ end
+
+ test "invalid regex remains non fatal" do
+ result = Distillator::WringerSystemErrorMatcher.call(
+ body: "ok",
+ http_code: 200,
+ final_url: "https://example.org/events",
+ rules: [["bad_regex", { "match" => { "final_url_patterns" => ["*invalid["] }, "policy" => { "error_code" => "bad_regex" } }]]
+ )
+
+ assert_nil result
+ end
+
+ test "specific anti bot issue wins over redirect listing when both match" do
+ issue_set = Distillator::WringerIssueSet.call(
+ body: "Queue-it Please wait while we redirect you",
+ http_code: 200,
+ final_url: "https://example.com/events",
+ signals: { redirect_type: "normal", network_status: "ok" }
+ )
+
+ assert_includes issue_set.matches.map { |m| m[:key] }, "queue_it"
+ assert_includes issue_set.matches.map { |m| m[:key] }, "redirect_to_listing"
+ assert_equal "queue_it", issue_set.primary[:key]
+ end
+
+ private
+
+ def match_for(body:, http_code:, final_url: "https://example.org/item", hints: [], signals: {})
+ Distillator::WringerSystemErrorMatcher.call(body: body, http_code: http_code, final_url: final_url, hints: hints, signals: signals)
+ end
+end
diff --git a/test/services/distillator/wringer_url_key_test.rb b/test/services/distillator/wringer_url_key_test.rb
new file mode 100644
index 00000000..8923a87c
--- /dev/null
+++ b/test/services/distillator/wringer_url_key_test.rb
@@ -0,0 +1,97 @@
+require "test_helper"
+
+class Distillator::WringerUrlKeyTest < ActiveSupport::TestCase
+ test "http culturecreates root matches legacy make_uri_key" do
+ result = Distillator::WringerUrlKey.call("http://culturecreates.com/")
+
+ assert_equal "http%3A%2F%2Fculturecreates.com%2F", result.uri_key
+ assert_equal "http://culturecreates.com/", result.normalized_url
+ end
+
+ test "uri key with no scheme injects http like legacy make_uri_key" do
+ result = Distillator::WringerUrlKey.call("culturecreates.com/")
+
+ assert_equal "http%3A%2F%2Fculturecreates.com%2F", result.uri_key
+ assert_equal "http://culturecreates.com/", result.normalized_url
+ end
+
+ test "uri key preserves query like legacy make_uri_key" do
+ result = Distillator::WringerUrlKey.call("https://billetterie.lachapelle.org/dates.aspx?codeEvent=TEM2017-2018")
+
+ assert_equal "https%3A%2F%2Fbilletterie.lachapelle.org%2Fdates.aspx%3FcodeEvent%3DTEM2017-2018", result.uri_key
+ assert_equal "https://billetterie.lachapelle.org/dates.aspx?codeEvent=TEM2017-2018", result.normalized_url
+ end
+
+ test "uri key excludes fragment by default like legacy make_uri_key" do
+ result = Distillator::WringerUrlKey.call("https://culturecreates.com/people#gregory")
+
+ assert_equal "https%3A%2F%2Fculturecreates.com%2Fpeople", result.uri_key
+ assert_equal "https://culturecreates.com/people", result.normalized_url
+ end
+
+ test "uri key includes fragment when requested like legacy make_uri_key" do
+ result = Distillator::WringerUrlKey.call("https://culturecreates.com/people#gregory", include_fragment: true)
+
+ assert_equal "https%3A%2F%2Fculturecreates.com%2Fpeople%23gregory", result.uri_key
+ assert_equal "https://culturecreates.com/people#gregory", result.normalized_url
+ end
+
+ test "uri key includes fragment when include_fragment is string true" do
+ result = Distillator::WringerUrlKey.call("https://culturecreates.com/people#gregory", include_fragment: "true")
+
+ assert_equal "https%3A%2F%2Fculturecreates.com%2Fpeople%23gregory", result.uri_key
+ end
+
+ test "uri key with no scheme preserves trailing path slash like legacy make_uri_key" do
+ result = Distillator::WringerUrlKey.call("culturecreates.com/people/")
+
+ assert_equal "http%3A%2F%2Fculturecreates.com%2Fpeople%2F", result.uri_key
+ assert_equal "http://culturecreates.com/people/", result.normalized_url
+ end
+
+ test "uri key preserves legacy case-sensitive scheme quirk for uppercase scheme input" do
+ result = Distillator::WringerUrlKey.call("HTTPS://Example.org/People/")
+
+ # This intentionally matches Wringer's current case-sensitive `http` prefix check.
+ assert_equal "http%3A%2F%2FHTTPS%2F%2FExample.org%2FPeople%2F", result.uri_key
+ assert_equal "http://HTTPS//Example.org/People/", result.normalized_url
+ end
+
+ test "blank URL raises invalid uri error instead of generating a cache key" do
+ assert_raises(Addressable::URI::InvalidURIError) do
+ Distillator::WringerUrlKey.call("")
+ end
+ end
+
+ test "uri key exact outputs match rollout contract cases" do
+ assert_equal "http%3A%2F%2Fculturecreates.com%2F", Distillator::WringerUrlKey.call("http://culturecreates.com/").uri_key
+ assert_equal "https%3A%2F%2Fculturecreates.com%2Fpeople", Distillator::WringerUrlKey.call("https://culturecreates.com/people#gregory").uri_key
+ assert_equal "https%3A%2F%2Fculturecreates.com%2Fpeople%23gregory", Distillator::WringerUrlKey.call("https://culturecreates.com/people#gregory", include_fragment: true).uri_key
+ assert_equal "http%3A%2F%2Fculturecreates.com%2Fpeople%2F", Distillator::WringerUrlKey.call("culturecreates.com/people/").uri_key
+ end
+
+ test "invalid URI raises so controller can return no_content" do
+ assert_raises(URI::InvalidURIError) do
+ Distillator::WringerUrlKey.call("http://[invalid")
+ end
+ end
+
+ test "already escaped URL stays on the legacy error path deterministically" do
+ result = Distillator::WringerUrlKey.call("https%3A%2F%2Fculturecreates.com%2Fpeople%2F")
+
+ assert_equal "Error%3A+not+a+URI", result.uri_key
+ assert_equal "Error: not a URI", result.normalized_url
+ end
+
+ test "uri key excludes fragment when include_fragment is false even if upstream helper preserved it" do
+ result = Distillator::WringerUrlKey.call("https://culturecreates.com/people#gregory", include_fragment: false)
+
+ assert_equal "https://culturecreates.com/people", result.normalized_url
+ end
+
+ test "uri key excludes fragment when include_fragment is string false" do
+ result = Distillator::WringerUrlKey.call("https://culturecreates.com/people#gregory", include_fragment: "false")
+
+ assert_equal "https://culturecreates.com/people", result.normalized_url
+ end
+end
diff --git a/test/services/dsl/distillator_golden_fixture_test.rb b/test/services/dsl/distillator_golden_fixture_test.rb
new file mode 100644
index 00000000..3e8ba46f
--- /dev/null
+++ b/test/services/dsl/distillator_golden_fixture_test.rb
@@ -0,0 +1,136 @@
+require "test_helper"
+
+class DistillatorGoldenFixtureTest < ActiveSupport::TestCase
+ include StatementsHelper
+
+ FetchResult = Struct.new(
+ :status,
+ :body,
+ :html,
+ :headers,
+ :final_url,
+ :redirect_chain,
+ :http_response_code,
+ :signals,
+ :hints,
+ :duration_ms,
+ :cache_hit,
+ :cache_write,
+ :cache_reason,
+ :uri_key,
+ :normalized_url,
+ :fetch_path,
+ keyword_init: true
+ )
+
+ FIXTURE_ROOT = Rails.root.join("test/fixtures/files/distillator_dsl")
+
+ test "golden fixtures cover real DSL patterns through the Distillator runner path" do
+ cases.each do |fixture_case|
+ body = File.read(FIXTURE_ROOT.join(fixture_case.fetch(:fixture_file)))
+ url = fixture_case.fetch(:url)
+
+ fetch_stub = lambda do |**kwargs|
+ assert_equal url, kwargs[:uri], failure_prefix(fixture_case, "unexpected fetch uri #{kwargs[:uri].inspect}")
+ assert_equal false, kwargs[:render_js], failure_prefix(fixture_case, "expected render_js=false")
+ assert_equal true, kwargs[:include_fragment], failure_prefix(fixture_case, "expected include_fragment=true")
+ build_fetch_result(url: url, body: body)
+ end
+
+ singleton = class << Distillator::FetchCacheStore
+ self
+ end
+ original_fetch = Distillator::FetchCacheStore.method(:fetch)
+ singleton.send(:define_method, :fetch) do |**kwargs|
+ fetch_stub.call(**kwargs.symbolize_keys)
+ end
+
+ result, trace = begin
+ run_dsl(
+ algorithm: fixture_case.fetch(:algorithm_value),
+ url: url,
+ trace: true
+ )
+ ensure
+ singleton.send(:define_method, :fetch, original_fetch)
+ end
+
+ actual = Array(result)
+ expected = fixture_case.fetch(:expected_array)
+
+ assert_equal expected, actual, failure_prefix(fixture_case, "expected #{expected.inspect}, got #{actual.inspect}")
+
+ trace_types = Array(trace).map { |event| event[:type] || event["type"] }
+ fixture_case.fetch(:expected_trace_presence).each do |trace_type|
+ assert_includes trace_types, trace_type, failure_prefix(fixture_case, "missing trace step #{trace_type.inspect} in #{trace_types.inspect}")
+ end
+ end
+ end
+
+ private
+
+ def cases
+ [
+ {
+ algorithm_value: "xpath=//title",
+ fixture_file: "simple_title.html",
+ url: "https://fixtures.example/distillator-dsl/simple-title",
+ expected_array: ["Simple Fixture Title"],
+ expected_trace_presence: ["xpath"]
+ },
+ {
+ algorithm_value: "xpath=//meta[@property='og:title']/@content",
+ fixture_file: "simple_title.html",
+ url: "https://fixtures.example/distillator-dsl/og-title",
+ expected_array: ["OG Fixture Title"],
+ expected_trace_presence: ["xpath"]
+ },
+ {
+ algorithm_value: "xpath_sanitize=//div[@class='description'];ruby=$array.map { |str| str.squish }",
+ fixture_file: "price_block.html",
+ url: "https://fixtures.example/distillator-dsl/price-block",
+ expected_array: ["Opening night gala"],
+ expected_trace_presence: ["xpath_sanitize", "ruby"]
+ },
+ {
+ algorithm_value: "xpath=//script[@type='application/ld+json'];ruby=JSON.parse($array[0]).dig('name')",
+ fixture_file: "json_ld_event.html",
+ url: "https://fixtures.example/distillator-dsl/json-ld",
+ expected_array: ["JSON-LD Fixture Event"],
+ expected_trace_presence: ["xpath", "ruby"]
+ },
+ {
+ algorithm_value: "xpath=//a/@href;ruby=$array.map { |url| \"footlight:test_\#{url.split('/').last}\" }",
+ fixture_file: "resource_list.html",
+ url: "https://fixtures.example/distillator-dsl/resource-list",
+ expected_array: ["footlight:test_opening-night", "footlight:test_main-hall"],
+ expected_trace_presence: ["xpath", "ruby"]
+ }
+ ]
+ end
+
+ def build_fetch_result(url:, body:)
+ FetchResult.new(
+ status: :ok,
+ body: body,
+ html: body,
+ headers: { content_type: "text/html" },
+ final_url: url,
+ redirect_chain: [url],
+ http_response_code: 200,
+ signals: { "network_status" => "ok", "content_type" => "html" },
+ hints: [],
+ duration_ms: 1.0,
+ cache_hit: false,
+ cache_write: true,
+ cache_reason: "missing_cache",
+ uri_key: CGI.escape(url),
+ normalized_url: url,
+ fetch_path: "native"
+ )
+ end
+
+ def failure_prefix(fixture_case, detail)
+ "fixture=#{fixture_case[:fixture_file]} algorithm=#{fixture_case[:algorithm_value]} #{detail}"
+ end
+end
diff --git a/test/services/dsl/identity/url_identifier_test.rb b/test/services/dsl/identity/url_identifier_test.rb
new file mode 100644
index 00000000..19f82192
--- /dev/null
+++ b/test/services/dsl/identity/url_identifier_test.rb
@@ -0,0 +1,123 @@
+require "test_helper"
+
+class Dsl::Identity::UrlIdentifierTest < ActiveSupport::TestCase
+ test "extracts id from /events/:id" do
+ url = "http://site.com/events/abc123"
+
+ result = Dsl::Identity::UrlIdentifier.call(url)
+
+ assert_equal "abc123", result
+ end
+
+ test "extracts id from /billets/:id" do
+ url = "http://site.com/billets/mti260327001"
+
+ result = Dsl::Identity::UrlIdentifier.call(url)
+
+ assert_equal "mti260327001", result
+ end
+
+ test "extracts id from /tickets/:id" do
+ url = "http://site.com/tickets/999"
+
+ result = Dsl::Identity::UrlIdentifier.call(url)
+
+ assert_equal "999", result
+ end
+
+ test "avoids trailing segments" do
+ url = "http://site.com/events/123/details"
+
+ result = Dsl::Identity::UrlIdentifier.call(url)
+
+ assert_equal "123", result
+ end
+
+ test "avoids generic segments" do
+ url = "http://site.com/events/show"
+
+ result = Dsl::Identity::UrlIdentifier.call(url)
+
+ assert_not_equal "show", result
+ end
+
+ test "extracts slug without digits" do
+ url = "http://site.com/events/festival-jazz"
+
+ result = Dsl::Identity::UrlIdentifier.call(url)
+
+ assert_equal "festival-jazz", result
+ end
+
+ test "avoids event generic segment" do
+ url = "http://site.com/events/event"
+
+ result = Dsl::Identity::UrlIdentifier.call(url)
+
+ assert_not_equal "event", result
+ end
+
+ test "keeps mixed case slug before normalization" do
+ url = "http://site.com/events/Festival-Jazz"
+
+ result = Dsl::Identity::UrlIdentifier.call(url)
+
+ assert_equal "Festival-Jazz", result
+ end
+
+ test "fallback still works for short path segments" do
+ url = "http://site.com/a/b/"
+
+ result = Dsl::Identity::UrlIdentifier.call(url)
+
+ assert_equal "b", result
+ end
+
+ test "extracts lepointdevente billets id" do
+ url = "https://lepointdevente.com/billets/mti260327001"
+
+ result = Dsl::Identity::UrlIdentifier.call(url)
+
+ assert_equal "mti260327001", result
+ end
+
+ test "extracts eventbrite trailing numeric id" do
+ url = "https://www.eventbrite.com/e/my-event-name-tickets-123456789"
+
+ result = Dsl::Identity::UrlIdentifier.call(url)
+
+ assert_equal "123456789", result
+ end
+
+ test "extracts ticketweb last numeric segment" do
+ url = "https://www.ticketweb.ca/event/foo/14122674"
+
+ result = Dsl::Identity::UrlIdentifier.call(url)
+
+ assert_equal "14122674", result
+ end
+
+ test "lepointdevente billets empty match is not empty" do
+ url = "https://lepointdevente.com/billets/"
+
+ result = Dsl::Identity::UrlIdentifier.call(url)
+
+ assert_not_equal "", result.to_s
+ end
+
+ test "eventbrite extracts id with trailing characters" do
+ url = "https://eventbrite.com/e/foo-123456789-extra"
+
+ result = Dsl::Identity::UrlIdentifier.call(url)
+
+ assert_equal "123456789", result
+ end
+
+ test "not eventbrite domain does not use eventbrite extraction" do
+ url = "https://not-eventbrite.com/e/foo-123456789"
+
+ result = Dsl::Identity::UrlIdentifier.call(url)
+
+ assert_not_equal "123456789", result
+ end
+end
diff --git a/test/services/dsl/instructions/make_uri_test.rb b/test/services/dsl/instructions/make_uri_test.rb
new file mode 100644
index 00000000..49962fdb
--- /dev/null
+++ b/test/services/dsl/instructions/make_uri_test.rb
@@ -0,0 +1,133 @@
+require "test_helper"
+
+class Dsl::Instructions::MakeUriTest < ActiveSupport::TestCase
+ def dummy_ctx(seedurl: "default")
+ { seedurl: seedurl }
+ end
+
+ test "make_uri preserves cardinality (1:1)" do
+ input = ["a", "b", "c"]
+
+ result = Dsl::Instructions::MakeUri.call(input, "", context: dummy_ctx)
+
+ assert_equal input.size, result.size
+ end
+
+ test "make_uri never returns nil entries" do
+ input = ["", nil, "http://example.com"]
+
+ result = Dsl::Instructions::MakeUri.call(input, "", context: dummy_ctx)
+
+ assert result.all?(&:present?)
+ end
+
+ test "fallback id is deterministic" do
+ url = "http://example.com/no-id"
+
+ r1 = Dsl::Instructions::MakeUri.call([url], "", context: dummy_ctx).first
+ r2 = Dsl::Instructions::MakeUri.call([url], "", context: dummy_ctx).first
+
+ assert_equal r1, r2
+ end
+
+ test "extracts id from query param" do
+ url = "http://site.com/event?id=123"
+
+ result = Dsl::Instructions::MakeUri.call([url], "", context: dummy_ctx).first
+
+ assert_match(/123$/, result)
+ end
+
+ test "extracts id from path segment" do
+ url = "http://site.com/events/abc123"
+
+ result = Dsl::Instructions::MakeUri.call([url], "", context: dummy_ctx).first
+
+ assert_match(/abc123$/, result)
+ end
+
+ test "expand=true uses expanded url" do
+ Dsl::Network::UrlExpander.stubs(:call).returns("http://final.com/event/999")
+
+ result = Dsl::Instructions::MakeUri.call(
+ ["http://short.url/x"],
+ "expand=true",
+ context: dummy_ctx
+ ).first
+
+ assert_match(/999$/, result)
+ end
+
+ test "expand=false does not call expander" do
+ Dsl::Network::UrlExpander.expects(:call).never
+
+ Dsl::Instructions::MakeUri.call(["http://site.com/x"], "expand=false", context: dummy_ctx)
+ end
+
+ test "normalizes id" do
+ url = "http://site.com/Event Name.html"
+
+ result = Dsl::Instructions::MakeUri.call([url], "", context: dummy_ctx).first
+
+ assert_match(/event-name$/, result)
+ end
+
+ test "uses prefix from params" do
+ result = Dsl::Instructions::MakeUri.call(["http://x.com/a"], "prefix=test", context: dummy_ctx).first
+
+ assert_match(/^footlight:test_/, result)
+ end
+
+ test "uses seedurl as default prefix" do
+ ctx = { seedurl: "mysite" }
+
+ result = Dsl::Instructions::MakeUri.call(["http://x.com/a"], "", context: ctx).first
+
+ assert_match(/^footlight:mysite_/, result)
+ end
+
+ test "ignores unrelated query params" do
+ url = "http://site.com/event?utm_source=abc"
+
+ result = Dsl::Instructions::MakeUri.call([url], "", context: dummy_ctx).first
+
+ assert_no_match(/abc$/, result)
+ end
+
+ test "multiple query keys priority uses eventId" do
+ url = "http://site.com/event?eventId=456"
+
+ result = Dsl::Instructions::MakeUri.call([url], "", context: dummy_ctx).first
+
+ assert_match(/456$/, result)
+ end
+
+ test "known short url expansion detection calls expander when expand is auto" do
+ Dsl::Network::UrlExpander.expects(:call).with("https://lpdv.co/abc123", context: dummy_ctx).returns("https://final.site/events/789")
+
+ result = Dsl::Instructions::MakeUri.call(["https://lpdv.co/abc123"], "expand=auto", context: dummy_ctx).first
+
+ assert_match(/789$/, result)
+ end
+
+ test "does not expand normal urls" do
+ Dsl::Network::UrlExpander.expects(:call).never
+
+ Dsl::Instructions::MakeUri.call(["http://site.com/events/123"], "expand=auto", context: dummy_ctx)
+ end
+
+ test "normalization stability collapses duplicate separators" do
+ url = "http://site.com/Event--Name.html"
+
+ result = Dsl::Instructions::MakeUri.call([url], "", context: dummy_ctx).first
+
+ assert_match(/event-name$/, result)
+ end
+
+ test "fallback remains normalized and present for invalid or empty input" do
+ result = Dsl::Instructions::MakeUri.call([nil], "", context: dummy_ctx).first
+
+ assert result.present?
+ assert_match(/\Afootlight:[a-z0-9-]+_[a-z0-9-]+\z/, result)
+ end
+end
diff --git a/test/services/dsl/pipeline_diagnosis_test.rb b/test/services/dsl/pipeline_diagnosis_test.rb
new file mode 100644
index 00000000..8283a2b6
--- /dev/null
+++ b/test/services/dsl/pipeline_diagnosis_test.rb
@@ -0,0 +1,347 @@
+require "test_helper"
+
+class Dsl::PipelineDiagnosisTest < ActiveSupport::TestCase
+ def base_metrics(overrides = {})
+ {
+ error_present: false,
+ data_loss: false,
+ extraction_empty: false,
+ extraction_attempted: false,
+ suspicious_navigation: false,
+ failure_step: nil,
+ recovered_after_loss: false,
+ steps_count: 0,
+ final_empty: false,
+ has_navigation: false
+ }.merge(overrides)
+ end
+
+ def base_wringer(overrides = {})
+ {
+ unreachable: false,
+ received_404: false,
+ system_error: false,
+ policy_action: nil
+ }.merge(overrides)
+ end
+
+ test "extraction failure without wringer signals" do
+ diagnosis = Dsl::PipelineDiagnosis.new(
+ metrics: base_metrics(extraction_attempted: true, extraction_empty: true),
+ wringer: base_wringer
+ ).result
+
+ assert_equal :warning, diagnosis[:status]
+ assert_equal :extraction_failure, diagnosis[:category]
+ assert_equal :check_xpath, diagnosis[:suggested_action]
+ end
+
+ test "suspicious navigation without wringer unreachable" do
+ diagnosis = Dsl::PipelineDiagnosis.new(
+ metrics: base_metrics(suspicious_navigation: true, has_navigation: true, extraction_attempted: true),
+ wringer: base_wringer(unreachable: false)
+ ).result
+
+ assert_equal :warning, diagnosis[:status]
+ assert_equal :navigation_failure, diagnosis[:category]
+ assert_equal :check_url, diagnosis[:suggested_action]
+ end
+
+ test "suspicious navigation with wringer unreachable becomes wringer failure" do
+ diagnosis = Dsl::PipelineDiagnosis.new(
+ metrics: base_metrics(suspicious_navigation: true, has_navigation: true, extraction_attempted: true),
+ wringer: base_wringer(unreachable: true)
+ ).result
+
+ assert_equal :error, diagnosis[:status]
+ assert_equal :wringer_failure, diagnosis[:category]
+ assert_equal :retry, diagnosis[:suggested_action]
+ end
+
+ test "wringer 404 diagnosis" do
+ diagnosis = Dsl::PipelineDiagnosis.new(
+ metrics: base_metrics,
+ wringer: base_wringer(received_404: true)
+ ).result
+
+ assert_equal :error, diagnosis[:status]
+ assert_equal :wringer_failure, diagnosis[:category]
+ assert_equal :check_url, diagnosis[:suggested_action]
+ end
+
+ test "wringer network failure from signals triggers wringer diagnosis" do
+ diagnosis = Dsl::PipelineDiagnosis.new(
+ metrics: {},
+ wringer: { signals: { network_status: "failed" } }
+ ).result
+
+ assert_equal :error, diagnosis[:status]
+ assert_equal :wringer_failure, diagnosis[:category]
+ end
+
+ test "data loss after extraction diagnosis" do
+ diagnosis = Dsl::PipelineDiagnosis.new(
+ metrics: base_metrics(data_loss: true, extraction_attempted: true),
+ wringer: base_wringer
+ ).result
+
+ assert_equal :warning, diagnosis[:status]
+ assert_equal :data_loss, diagnosis[:category]
+ assert_equal :investigate, diagnosis[:suggested_action]
+ end
+
+ test "error present diagnosis" do
+ diagnosis = Dsl::PipelineDiagnosis.new(
+ metrics: base_metrics(error_present: true, failure_step: 4, data_loss: true),
+ wringer: base_wringer(unreachable: true)
+ ).result
+
+ assert_equal :error, diagnosis[:status]
+ assert_equal :error, diagnosis[:category]
+ assert_equal :investigate, diagnosis[:suggested_action]
+ end
+
+ test "healthy pipeline diagnosis" do
+ diagnosis = Dsl::PipelineDiagnosis.new(
+ metrics: base_metrics,
+ wringer: base_wringer
+ ).result
+
+ assert_equal :ok, diagnosis[:status]
+ assert_equal :healthy, diagnosis[:category]
+ end
+
+ test "healthy diagnosis has no action" do
+ diagnosis = Dsl::PipelineDiagnosis.new(
+ metrics: base_metrics,
+ wringer: base_wringer
+ ).result
+
+ assert_equal :none, diagnosis[:suggested_action]
+ end
+
+ test "priority data loss overrides extraction failure" do
+ diagnosis = Dsl::PipelineDiagnosis.new(
+ metrics: base_metrics(
+ data_loss: true,
+ extraction_attempted: true,
+ extraction_empty: true
+ ),
+ wringer: base_wringer
+ ).result
+
+ assert_equal :data_loss, diagnosis[:category]
+ end
+
+ test "priority wringer failure overrides navigation and extraction" do
+ diagnosis = Dsl::PipelineDiagnosis.new(
+ metrics: base_metrics(
+ suspicious_navigation: true,
+ extraction_attempted: true,
+ extraction_empty: true
+ ),
+ wringer: base_wringer(system_error: true)
+ ).result
+
+ assert_equal :wringer_failure, diagnosis[:category]
+ end
+
+ test "priority error overrides everything else" do
+ diagnosis = Dsl::PipelineDiagnosis.new(
+ metrics: base_metrics(
+ error_present: true,
+ data_loss: true,
+ suspicious_navigation: true,
+ extraction_attempted: true,
+ extraction_empty: true
+ ),
+ wringer: base_wringer(unreachable: true, received_404: true, system_error: true)
+ ).result
+
+ assert_equal :error, diagnosis[:category]
+ end
+
+ test "extraction failure can be derived from extract primitive steps" do
+ diagnosis = Dsl::PipelineDiagnosis.new(
+ metrics: base_metrics(extraction_attempted: false, extraction_empty: true),
+ wringer: base_wringer,
+ steps: [
+ { step: 1, type: "xpath", primitive: :extract, output: [] }
+ ]
+ ).result
+
+ assert_equal :warning, diagnosis[:status]
+ assert_equal :extraction_failure, diagnosis[:category]
+ assert_equal :check_xpath, diagnosis[:suggested_action]
+ end
+
+ test "navigation diagnosis details can be derived from primitive steps" do
+ diagnosis = Dsl::PipelineDiagnosis.new(
+ metrics: base_metrics(
+ suspicious_navigation: true,
+ has_navigation: false,
+ extraction_attempted: false
+ ),
+ wringer: base_wringer,
+ steps: [
+ { step: 1, type: "url", primitive: :navigate, output: nil },
+ { step: 2, type: "xpath", primitive: :extract, output: [] }
+ ]
+ ).result
+
+ assert_equal :warning, diagnosis[:status]
+ assert_equal :navigation_failure, diagnosis[:category]
+ assert_equal true, diagnosis[:details][:has_navigation]
+ assert_equal true, diagnosis[:details][:extraction_attempted]
+ end
+
+ test "extraction failure message is semantic" do
+ diagnosis = Dsl::PipelineDiagnosis.new(
+ metrics: {
+ extraction_attempted: true,
+ extraction_empty: true
+ },
+ steps: [
+ { primitive: :extract, output: [] }
+ ]
+ ).result
+
+ assert_equal :extraction_failure, diagnosis[:category]
+ assert_includes diagnosis[:message], "failed; no data was produced"
+ end
+
+ test "navigation followed by failed extraction produces contextual message" do
+ diagnosis = Dsl::PipelineDiagnosis.new(
+ metrics: {
+ extraction_attempted: true,
+ extraction_empty: true,
+ has_navigation: true,
+ suspicious_navigation: true
+ },
+ steps: [
+ { primitive: :navigate, output: ["url"] },
+ { primitive: :extract, output: [] }
+ ]
+ ).result
+
+ assert_equal :navigation_failure, diagnosis[:category]
+ assert_includes diagnosis[:message], "after navigation"
+ end
+
+ test "wringer failure message unchanged" do
+ diagnosis = Dsl::PipelineDiagnosis.new(
+ metrics: {},
+ wringer: { received_404: true },
+ steps: []
+ ).result
+
+ assert_equal :wringer_failure, diagnosis[:category]
+ assert_includes diagnosis[:message], "Wringer"
+ end
+
+ test "wringer fetch error diagnosis is source-aware with fetch category" do
+ diagnosis = Dsl::PipelineDiagnosis.new(
+ metrics: base_metrics(error_present: true, failure_step: 1),
+ wringer: base_wringer(error_type: "WringerFetchError", source: "wringer")
+ ).result
+
+ assert_equal :error, diagnosis[:status]
+ assert_equal :wringer_failure, diagnosis[:category]
+ assert_equal "wringer", diagnosis[:source]
+ assert_equal "WringerFetchError", diagnosis[:error_type]
+ assert_equal "fetch", diagnosis[:pipeline_category]
+ assert_includes diagnosis[:message], "Failed to fetch page"
+ end
+
+ test "wringer skip diagnosis is source-aware" do
+ diagnosis = Dsl::PipelineDiagnosis.new(
+ metrics: base_metrics(error_present: true, failure_step: 2),
+ wringer: base_wringer(error_type: "WringerSkip", source: "wringer")
+ ).result
+
+ assert_equal :wringer_failure, diagnosis[:category]
+ assert_equal "wringer", diagnosis[:source]
+ assert_equal "fetch", diagnosis[:pipeline_category]
+ assert_equal "WringerSkip", diagnosis[:error_type]
+ assert_includes diagnosis[:message], "skipped by upstream policy"
+ end
+
+ test "wringer unsupported action diagnosis is source-aware" do
+ diagnosis = Dsl::PipelineDiagnosis.new(
+ metrics: base_metrics(error_present: true, failure_step: 3),
+ wringer: base_wringer(error_type: "WringerUnsupportedAction", source: "wringer")
+ ).result
+
+ assert_equal :wringer_failure, diagnosis[:category]
+ assert_equal "wringer", diagnosis[:source]
+ assert_equal "fetch", diagnosis[:pipeline_category]
+ assert_equal "WringerUnsupportedAction", diagnosis[:error_type]
+ assert_includes diagnosis[:message], "unsupported control action"
+ end
+
+ test "dsl extraction failure keeps extraction category and dsl source" do
+ diagnosis = Dsl::PipelineDiagnosis.new(
+ metrics: base_metrics(extraction_attempted: true, extraction_empty: true),
+ wringer: base_wringer
+ ).result
+
+ assert_equal :extraction_failure, diagnosis[:category]
+ assert_equal "dsl", diagnosis[:source]
+ assert_equal "extraction", diagnosis[:pipeline_category]
+ assert_nil diagnosis[:error_type]
+ assert_includes diagnosis[:message], "Extraction failed"
+ end
+
+ test "healthy message unchanged" do
+ diagnosis = Dsl::PipelineDiagnosis.new(
+ metrics: { extraction_attempted: true, extraction_empty: false },
+ steps: [
+ { primitive: :extract, output: ["ok"] }
+ ]
+ ).result
+
+ assert_equal :healthy, diagnosis[:category]
+ assert_includes diagnosis[:message], "healthy"
+ end
+
+ test "extraction failure includes wringer error_type as context" do
+ diagnosis = Dsl::PipelineDiagnosis.call(
+ steps: [],
+ metrics: { extraction: 0 },
+ wringer: { error_type: "system_cloudflare" }
+ )
+
+ assert_match(/\(error: system_cloudflare\)/, diagnosis[:message])
+ end
+
+ test "extraction failure includes slow response context" do
+ diagnosis = Dsl::PipelineDiagnosis.call(
+ steps: [],
+ metrics: { extraction: 0 },
+ wringer: { duration_ms: 2000 }
+ )
+
+ assert_match(/slow response/i, diagnosis[:message])
+ end
+
+ test "navigation failure includes redirect context" do
+ diagnosis = Dsl::PipelineDiagnosis.call(
+ steps: [],
+ metrics: { navigation: 1, extraction: 0 },
+ wringer: { redirect_chain: ["a", "b"] }
+ )
+
+ assert_match(/after redirect/i, diagnosis[:message])
+ end
+
+ test "ok pipeline but missing content yields extraction warning" do
+ diagnosis = Dsl::PipelineDiagnosis.call(
+ steps: [],
+ metrics: { extraction: 0 },
+ wringer: {},
+ statement_status: "missing"
+ )
+
+ assert_match(/no data extracted/i, diagnosis[:message])
+ end
+end
diff --git a/test/services/dsl/pipeline_evaluator_test.rb b/test/services/dsl/pipeline_evaluator_test.rb
new file mode 100644
index 00000000..a5763c50
--- /dev/null
+++ b/test/services/dsl/pipeline_evaluator_test.rb
@@ -0,0 +1,227 @@
+require "test_helper"
+
+class Dsl::PipelineEvaluatorTest < ActiveSupport::TestCase
+ test "evaluate returns metrics and diagnosis" do
+ result = Dsl::PipelineEvaluator.evaluate(event: "uri1")
+
+ assert result.is_a?(Hash)
+ assert result[:metrics].is_a?(Hash)
+ assert result[:diagnosis].is_a?(Hash)
+ assert_includes result[:metrics].keys, :steps_count
+ assert_includes result[:diagnosis].keys, :status
+ assert_includes result[:diagnosis].keys, :category
+ end
+
+ test "diagnosis mapping is preserved from metrics" do
+ result = Dsl::PipelineEvaluator.evaluate(event: "uri1")
+ expected = Dsl::PipelineDiagnosis.new(
+ metrics: result[:metrics],
+ wringer: {
+ unreachable: false,
+ received_404: false,
+ system_error: false,
+ policy_action: nil
+ }
+ ).result
+
+ assert_equal expected, result[:diagnosis]
+ end
+
+ test "does not execute dsl runner" do
+ Dsl::Core::AlgorithmRunner.expects(:new).never
+
+ Dsl::PipelineEvaluator.evaluate(event: "uri1")
+ end
+
+ test "handles missing event pipeline data gracefully" do
+ result = Dsl::PipelineEvaluator.evaluate(event: "missing-uri")
+
+ assert_equal 0, result[:metrics][:steps_count]
+ assert_equal :healthy, result[:diagnosis][:category]
+ end
+
+ test "wringer 404 forces wringer_failure diagnosis" do
+ result = Dsl::PipelineEvaluator.evaluate(
+ event: "uri1",
+ wringer: {
+ unreachable: false,
+ received_404: true,
+ system_error: false,
+ policy_action: "abort_update"
+ }
+ )
+
+ assert_equal :wringer_failure, result[:diagnosis][:category]
+ end
+
+ test "wringer unreachable forces wringer_failure diagnosis" do
+ result = Dsl::PipelineEvaluator.evaluate(
+ event: "uri1",
+ wringer: {
+ unreachable: true,
+ received_404: false,
+ system_error: false,
+ policy_action: "abort_update"
+ }
+ )
+
+ assert_equal :wringer_failure, result[:diagnosis][:category]
+ end
+
+ test "no wringer issues keeps diagnosis behavior unchanged" do
+ baseline = Dsl::PipelineEvaluator.evaluate(event: "uri1")
+ with_explicit_none = Dsl::PipelineEvaluator.evaluate(
+ event: "uri1",
+ wringer: {
+ unreachable: false,
+ received_404: false,
+ system_error: false,
+ policy_action: nil
+ }
+ )
+
+ assert_equal baseline[:diagnosis], with_explicit_none[:diagnosis]
+ end
+
+ test "end-to-end wringer failure overrides navigation and extraction symptoms" do
+ evaluator = Dsl::PipelineEvaluator.new(
+ event: "uri1",
+ wringer: {
+ unreachable: true,
+ received_404: false,
+ system_error: false,
+ policy_action: "abort_update"
+ }
+ )
+ evaluator.stubs(:pipeline_steps).returns(
+ [
+ { step: 1, type: "url", output: nil },
+ { step: 2, type: "xpath", output: [] }
+ ]
+ )
+
+ result = evaluator.evaluate
+
+ assert result[:metrics].is_a?(Hash)
+ assert_equal true, result[:metrics][:suspicious_navigation]
+ assert_equal :wringer_failure, result[:diagnosis][:category]
+ end
+
+ test "multi-step dsl is expanded into multiple pipeline steps" do
+ evaluator = Dsl::PipelineEvaluator.new(event: "uri1")
+ evaluator.stubs(:event_statements).returns([
+ statement_like("xpath=//a;url='http://example.com';xpath=//b", "[]")
+ ])
+
+ steps = evaluator.send(:pipeline_steps)
+
+ assert_equal 3, steps.size
+ assert_equal %w[xpath url xpath], steps.map { |step| step[:type] }
+ end
+
+ test "step order is preserved from dsl sequence" do
+ evaluator = Dsl::PipelineEvaluator.new(event: "uri1")
+ evaluator.stubs(:event_statements).returns([
+ statement_like("xpath=//a;ruby=$array.map(&:upcase);url='http://example.com';xpath=//b", "[]")
+ ])
+
+ steps = evaluator.send(:pipeline_steps)
+
+ assert_equal %w[xpath ruby url xpath], steps.map { |step| step[:type] }
+ assert_equal [1, 2, 3, 4], steps.map { |step| step[:step] }
+ end
+
+ test "mixed dsl operations are recognized as separate steps" do
+ evaluator = Dsl::PipelineEvaluator.new(event: "uri1")
+ evaluator.stubs(:event_statements).returns([
+ statement_like("xpath=//a;ruby=$array.reject(&:blank?);url='http://example.com'", "[]")
+ ])
+
+ steps = evaluator.send(:pipeline_steps)
+
+ assert_equal %w[xpath ruby url], steps.map { |step| step[:type] }
+ end
+
+ test "nil or empty dsl is handled safely as no steps" do
+ evaluator = Dsl::PipelineEvaluator.new(event: "uri1")
+ evaluator.stubs(:event_statements).returns([
+ statement_like(nil, "[]"),
+ statement_like("", "[]")
+ ])
+
+ assert_nothing_raised do
+ assert_equal [], evaluator.send(:pipeline_steps)
+ end
+ end
+
+ test "metrics reflect navigation for multi-step dsl with url step" do
+ evaluator = Dsl::PipelineEvaluator.new(event: "uri1")
+ evaluator.stubs(:event_statements).returns([
+ statement_like("xpath=//a;url='http://example.com';xpath=//b", "[]")
+ ])
+
+ result = evaluator.evaluate
+
+ assert_equal true, result[:metrics][:has_navigation]
+ assert_equal true, result[:metrics][:suspicious_navigation]
+ end
+
+ test "single-step dsl remains backward compatible" do
+ evaluator = Dsl::PipelineEvaluator.new(event: "uri1")
+ evaluator.stubs(:event_statements).returns([
+ statement_like("xpath=//a", "[]")
+ ])
+
+ steps = evaluator.send(:pipeline_steps)
+
+ assert_equal 1, steps.size
+ assert_equal "xpath", steps.first[:type]
+ assert_equal 1, steps.first[:step]
+ end
+
+ test "xpath step has primitive extract" do
+ steps = build_steps("xpath=//title")
+
+ assert_equal :extract, steps.first[:primitive]
+ end
+
+ test "url step has primitive navigate" do
+ steps = build_steps("url=http://example.com")
+
+ assert_equal :navigate, steps.first[:primitive]
+ end
+
+ test "ruby step has primitive transform" do
+ steps = build_steps("ruby=$array")
+
+ assert_equal :transform, steps.first[:primitive]
+ end
+
+ test "if_xpath step has primitive branch" do
+ steps = build_steps("if_xpath=//a")
+
+ assert_equal :branch, steps.first[:primitive]
+ end
+
+ test "sparql step is transform primitive" do
+ steps = build_steps("sparql={ ?s ?p ?o }")
+
+ assert_equal :transform, steps.first[:primitive]
+ end
+
+ private
+
+ def build_steps(algorithm_value, cache_value = "[]")
+ evaluator = Dsl::PipelineEvaluator.new(event: "uri1")
+ evaluator.stubs(:event_statements).returns([
+ statement_like(algorithm_value, cache_value)
+ ])
+
+ evaluator.send(:pipeline_steps)
+ end
+
+ def statement_like(algorithm_value, cache_value)
+ source = Struct.new(:algorithm_value).new(algorithm_value)
+ Struct.new(:source, :cache).new(source, cache_value)
+ end
+end
diff --git a/test/services/dsl/pipeline_interpreter_test.rb b/test/services/dsl/pipeline_interpreter_test.rb
new file mode 100644
index 00000000..a6aa2888
--- /dev/null
+++ b/test/services/dsl/pipeline_interpreter_test.rb
@@ -0,0 +1,755 @@
+require "test_helper"
+
+class Dsl::PipelineInterpreterTest < ActiveSupport::TestCase
+ test "pipeline interpreter loads without raising" do
+ assert_nothing_raised do
+ Dsl::PipelineInterpreter.new([])
+ end
+ end
+
+ test "pipeline interpreter metrics can be called safely" do
+ assert_nothing_raised do
+ Dsl::PipelineInterpreter.new([]).metrics
+ end
+ end
+
+ test "metrics exposes expected keys and base counts" do
+ steps = [
+ { step: 1, type: "xpath", output: ["a"] },
+ { step: 2, type: "ruby", output: ["b"] }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+
+ assert_equal(
+ [
+ :error_present,
+ :data_loss,
+ :extraction_empty,
+ :extraction_attempted,
+ :suspicious_navigation,
+ :failure_step,
+ :recovered_after_loss,
+ :steps_count,
+ :final_empty,
+ :has_navigation
+ ],
+ metrics.keys
+ )
+ assert_equal 2, metrics[:steps_count]
+ assert_equal false, metrics[:has_navigation]
+ end
+
+ test "error_present is true when any step contains error" do
+ steps = [
+ { step: 1, type: "xpath", output: ["a"] },
+ { step: 2, type: "ruby", output: [], error: "boom" }
+ ]
+
+ assert_equal true, Dsl::PipelineInterpreter.new(steps).metrics[:error_present]
+ end
+
+ test "data_loss is true when non-empty output becomes empty" do
+ steps = [
+ { step: 1, type: "xpath", output: ["a"] },
+ { step: 2, type: "ruby", output: [] }
+ ]
+
+ assert_equal true, Dsl::PipelineInterpreter.new(steps).metrics[:data_loss]
+ end
+
+ test "data_loss is true when output array shrinks" do
+ steps = [
+ { step: 10, type: "xpath", output: %w[a b c] },
+ { step: 42, type: "ruby", output: ["a"] }
+ ]
+
+ assert_equal true, Dsl::PipelineInterpreter.new(steps).metrics[:data_loss]
+ end
+
+ test "extraction_empty follows final output when xpath-like steps are empty but final output is present" do
+ steps = [
+ { step: 1, type: "xpath", output: [] },
+ { step: 2, type: "if_xpath", output: nil },
+ { step: 3, type: "ruby", output: ["value"] }
+ ]
+
+ assert_equal false, Dsl::PipelineInterpreter.new(steps).metrics[:extraction_empty]
+ end
+
+ test "failure_step prefers first error step over data-loss step" do
+ steps = [
+ { step: 1, type: "xpath", output: ["a"] },
+ { step: 2, type: "ruby", output: [], error: "boom" },
+ { step: 3, type: "ruby", output: [] }
+ ]
+
+ assert_equal 2, Dsl::PipelineInterpreter.new(steps).metrics[:failure_step]
+ end
+
+ test "failure_step is nil when data loss is recovered and final output is present" do
+ steps = [
+ { step: 1, type: "xpath", output: ["a"] },
+ { step: 2, type: "ruby", output: [] },
+ { step: 3, type: "ruby", output: ["recovered"] }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+
+ assert_equal true, metrics[:data_loss]
+ assert_equal false, metrics[:suspicious_navigation]
+ assert_nil metrics[:failure_step]
+ end
+
+ test "failure_step returns last non-sequential step number for unresolved final failure" do
+ steps = [
+ { step: 10, type: "xpath", output: ["a"] },
+ { step: 42, type: "ruby", output: [] },
+ { step: 90, type: "ruby", output: [] }
+ ]
+
+ assert_equal 90, Dsl::PipelineInterpreter.new(steps).metrics[:failure_step]
+ end
+
+ test "failure_step is nil when failing step has no step number" do
+ steps = [
+ { step: 10, type: "xpath", output: ["a"] },
+ { type: "ruby", output: [] }
+ ]
+
+ assert_nil Dsl::PipelineInterpreter.new(steps).metrics[:failure_step]
+ end
+
+ test "failure_step is nil when empty extraction is later recovered" do
+ steps = [
+ { step: 1, type: "xpath", output: [] },
+ { step: 2, type: "url", output: ["..."] },
+ { step: 3, type: "xpath", output: ["valid"] }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+
+ assert_nil metrics[:failure_step]
+ end
+
+ test "failure_step is last step when final output is empty" do
+ steps = [
+ { step: 1, type: "xpath", output: [] },
+ { step: 2, type: "url", output: ["..."] },
+ { step: 3, type: "xpath", output: [] }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+
+ assert_equal 3, metrics[:failure_step]
+ end
+
+ test "failure_step is last step when ruby clears output" do
+ steps = [
+ { step: 1, type: "xpath", output: ["valid"] },
+ { step: 2, type: "ruby", output: [] }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+
+ assert_equal 2, metrics[:failure_step]
+ end
+
+ test "failure_step prioritizes error over final output" do
+ steps = [
+ { step: 1, type: "xpath", output: ["valid"] },
+ { step: 2, type: "ruby", error: "boom", output: nil }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+
+ assert_equal 2, metrics[:failure_step]
+ end
+
+ test "failure_step is nil when no extraction attempted" do
+ steps = [
+ { step: 1, type: "ruby", output: ["value"] }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+
+ assert_nil metrics[:failure_step]
+ end
+
+ test "complex pipeline uses final output as truth" do
+ steps = [
+ { step: 1, type: "xpath", output: [] },
+ { step: 2, type: "ruby", output: ["transformed"] },
+ { step: 3, type: "xpath", output: [] },
+ { step: 4, type: "ruby", output: ["final"] }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+
+ assert_nil metrics[:failure_step]
+ end
+
+ test "failure_step does not alter suspicious navigation or data loss signals" do
+ steps = [
+ { step: 1, type: "url", output: nil },
+ { step: 2, type: "xpath", output: [] }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+
+ assert_equal false, metrics[:data_loss]
+ assert_equal true, metrics[:suspicious_navigation]
+ assert_equal 2, metrics[:failure_step]
+ end
+
+ test "recovered_after_loss is true when data appears after a loss" do
+ steps = [
+ { step: 1, type: "xpath", output: ["a"] },
+ { step: 2, type: "ruby", output: [] },
+ { step: 3, type: "ruby", output: ["c"] }
+ ]
+
+ assert_equal true, Dsl::PipelineInterpreter.new(steps).metrics[:recovered_after_loss]
+ end
+
+ test "recovered_after_loss is true for partial loss followed by growth" do
+ steps = [
+ { step: 1, type: "xpath", output: %w[a b c] },
+ { step: 2, type: "ruby", output: ["a"] },
+ { step: 3, type: "ruby", output: %w[a b] }
+ ]
+
+ assert_equal true, Dsl::PipelineInterpreter.new(steps).metrics[:recovered_after_loss]
+ end
+
+ test "recovered_after_loss is true for full loss followed by non-blank output" do
+ steps = [
+ { step: 1, type: "xpath", output: ["a"] },
+ { step: 2, type: "ruby", output: "" },
+ { step: 3, type: "ruby", output: ["x"] }
+ ]
+
+ assert_equal true, Dsl::PipelineInterpreter.new(steps).metrics[:recovered_after_loss]
+ end
+
+ test "final_empty tracks whether last output is blank" do
+ steps = [
+ { step: 1, type: "xpath", output: ["a"] },
+ { step: 2, type: "ruby", output: [] }
+ ]
+
+ assert_equal true, Dsl::PipelineInterpreter.new(steps).metrics[:final_empty]
+ end
+
+ test "has_navigation is true when url step exists" do
+ steps = [
+ { step: 1, type: "url", output: nil },
+ { step: 2, type: "xpath", output: ["a"] }
+ ]
+
+ assert_equal true, Dsl::PipelineInterpreter.new(steps).metrics[:has_navigation]
+ end
+
+ test "has_navigation detects variant url-like step types" do
+ steps = [
+ { step: 1, type: "pre_url_cleanup", output: nil },
+ { step: 2, type: "xpath", output: ["a"] }
+ ]
+
+ assert_equal true, Dsl::PipelineInterpreter.new(steps).metrics[:has_navigation]
+ end
+
+ test "extraction_empty ignores xpath steps that happen after navigation" do
+ steps = [
+ { step: 1, type: "xpath", output: [] },
+ { step: 2, type: "url", output: nil },
+ { step: 3, type: "xpath", output: ["late-xpath"] },
+ { step: 4, type: "ruby", output: ["done"] }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+
+ assert_equal true, metrics[:extraction_attempted]
+ assert_equal false, metrics[:extraction_empty]
+ end
+
+ test "extraction_attempted is false when no xpath-like step exists" do
+ steps = [
+ { step: 1, type: "url", output: nil },
+ { step: 2, type: "ruby", output: ["a"] }
+ ]
+
+ assert_equal false, Dsl::PipelineInterpreter.new(steps).metrics[:extraction_attempted]
+ end
+
+ test "extraction_attempted uses primitive not type string" do
+ steps = [
+ { step: 1, primitive: :extract, output: ["data"] }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+
+ assert_equal true, metrics[:extraction_attempted]
+ end
+
+ test "has_navigation uses primitive" do
+ steps = [
+ { step: 1, primitive: :navigate, output: ["url"] }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+
+ assert_equal true, metrics[:has_navigation]
+ end
+
+ test "primitive fallback works when missing" do
+ steps = [
+ { step: 1, type: "xpath", output: ["data"] }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+
+ assert_equal true, metrics[:extraction_attempted]
+ end
+
+ test "consistency keeps error_present coherent with failure_step when step number exists" do
+ steps = [
+ { step: 100, type: "ruby", output: ["a"], error: "boom" }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+
+ assert_equal true, metrics[:error_present]
+ assert_equal 100, metrics[:failure_step]
+ end
+
+ test "error_present remains true when error step has no number and failure_step is nil" do
+ steps = [
+ { step: 1, type: "xpath", output: ["a"] },
+ { type: "ruby", output: ["b"], error: "boom" }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+
+ assert_equal true, metrics[:error_present]
+ assert_nil metrics[:failure_step]
+ end
+
+ test "data_loss remains true when loss step has no number and failure_step is nil" do
+ steps = [
+ { step: 1, type: "xpath", output: ["a"] },
+ { type: "ruby", output: [] }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+
+ assert_equal true, metrics[:data_loss]
+ assert_nil metrics[:failure_step]
+ end
+
+ test "truth signals are preserved when error step number is missing and later loss exists" do
+ steps = [
+ { step: 1, type: "xpath", output: ["a"] },
+ { type: "ruby", output: ["a"], error: "boom" },
+ { step: 3, type: "ruby", output: [] }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+
+ assert_equal true, metrics[:error_present]
+ assert_equal true, metrics[:data_loss]
+ assert_nil metrics[:failure_step]
+ end
+
+ test "xpath then ruby then xpath ignores second xpath in extraction_empty" do
+ steps = [
+ { step: 1, type: "xpath", output: [] },
+ { step: 2, type: "ruby", output: ["transformed"] },
+ { step: 3, type: "xpath", output: ["late"] }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+
+ assert_equal true, metrics[:extraction_attempted]
+ assert_equal false, metrics[:extraction_empty]
+ end
+
+ test "xpath after url is considered extraction" do
+ steps = [
+ { step: 1, type: "url", output: nil },
+ { step: 2, type: "xpath", output: ["post-nav-data"] }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+
+ assert_equal true, metrics[:extraction_attempted]
+ assert_equal false, metrics[:extraction_empty]
+ end
+
+ test "xpath after url blank output sets extraction_empty true" do
+ steps = [
+ { step: 1, type: "url", output: nil },
+ { step: 2, type: "xpath", output: [] }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+
+ assert_equal true, metrics[:extraction_attempted]
+ assert_equal true, metrics[:extraction_empty]
+ end
+
+ test "mixed pre and post navigation extraction is evaluated together" do
+ steps = [
+ { step: 1, type: "xpath", output: [] },
+ { step: 2, type: "url", output: nil },
+ { step: 3, type: "xpath", output: ["post-nav-data"] }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+
+ assert_equal true, metrics[:extraction_attempted]
+ assert_equal false, metrics[:extraction_empty]
+ end
+
+ test "extraction_empty is false when xpath is empty but final ruby output is present" do
+ steps = [
+ { step: 1, type: "xpath", output: [] },
+ { step: 2, type: "ruby", output: ["valid"] }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+
+ assert_equal false, metrics[:extraction_empty]
+ end
+
+ test "extraction_empty is true when xpath steps are empty and final output is empty" do
+ steps = [
+ { step: 1, type: "xpath", output: [] },
+ { step: 2, type: "xpath", output: [] }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+
+ assert_equal true, metrics[:extraction_empty]
+ end
+
+ test "extraction_empty is true when xpath has valid output but final ruby output is empty" do
+ steps = [
+ { step: 1, type: "xpath", output: ["valid"] },
+ { step: 2, type: "ruby", output: [] }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+
+ assert_equal true, metrics[:extraction_empty]
+ end
+
+ test "extraction_empty follows final output in complex pipeline" do
+ steps = [
+ { step: 1, type: "xpath", output: [] },
+ { step: 2, type: "ruby", output: ["mid"] },
+ { step: 3, type: "xpath", output: ["x"] },
+ { step: 4, type: "ruby", output: [] }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+
+ assert_equal true, metrics[:extraction_empty]
+ end
+
+ test "no xpath means extraction not attempted and not empty" do
+ steps = [
+ { step: 1, type: "url", output: nil },
+ { step: 2, type: "ruby", output: ["a"] }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+
+ assert_equal false, metrics[:extraction_attempted]
+ assert_equal false, metrics[:extraction_empty]
+ end
+
+ test "xpath all blank with final non-blank output means extraction attempted and not empty" do
+ steps = [
+ { step: 1, type: "xpath", output: nil },
+ { step: 2, type: "if_xpath", output: [] },
+ { step: 3, type: "ruby", output: ["done"] }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+
+ assert_equal true, metrics[:extraction_attempted]
+ assert_equal false, metrics[:extraction_empty]
+ end
+
+ test "no loss when outputs change but remain non-blank with same size" do
+ steps = [
+ { step: 1, type: "xpath", output: ["a"] },
+ { step: 2, type: "ruby", output: ["b"] }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+
+ assert_equal false, metrics[:data_loss]
+ assert_equal false, metrics[:recovered_after_loss]
+ end
+
+ test "no recovery when there is full loss and no later output" do
+ steps = [
+ { step: 1, type: "xpath", output: ["a"] },
+ { step: 2, type: "ruby", output: [] }
+ ]
+
+ assert_equal false, Dsl::PipelineInterpreter.new(steps).metrics[:recovered_after_loss]
+ end
+
+ test "normalization handles nil blank array and blank string transitions safely" do
+ steps = [
+ { step: 1, type: "ruby", output: nil },
+ { step: 2, type: "ruby", output: [] },
+ { step: 3, type: "ruby", output: "" }
+ ]
+
+ metrics = nil
+ assert_nothing_raised do
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+ end
+
+ assert_equal false, metrics[:data_loss]
+ assert_equal false, metrics[:recovered_after_loss]
+ assert_equal true, metrics[:final_empty]
+ end
+
+ test "arrays with nil entries do not cause false data loss" do
+ steps = [
+ { step: 1, type: "ruby", output: [nil, "a"] },
+ { step: 2, type: "ruby", output: ["a"] }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+ assert_equal false, metrics[:data_loss]
+ end
+
+ test "has_navigation is true for post_url_transform variant" do
+ steps = [
+ { step: 1, type: "post_url_transform", output: nil },
+ { step: 2, type: "ruby", output: ["a"] }
+ ]
+
+ assert_equal true, Dsl::PipelineInterpreter.new(steps).metrics[:has_navigation]
+ end
+
+ test "metrics does not crash on malformed steps" do
+ steps = [
+ nil,
+ "broken-step",
+ 123,
+ { step: 3, type: "xpath", output: "" },
+ { step: 4 },
+ { step: 4, type: "ruby", output: ["alive"] },
+ { step: 6, type: "ruby", output: [] }
+ ]
+
+ metrics = nil
+ assert_nothing_raised do
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+ end
+
+ assert_equal 7, metrics[:steps_count]
+ assert_equal true, metrics[:data_loss]
+ assert_equal 6, metrics[:failure_step]
+ end
+
+ test "metrics is idempotent for same input" do
+ steps = [
+ { step: 1, type: "xpath", output: %w[a b] },
+ { step: 2, type: "ruby", output: ["a"] },
+ { step: 3, type: "ruby", output: %w[a c] }
+ ]
+
+ interpreter = Dsl::PipelineInterpreter.new(steps)
+ first = interpreter.metrics
+ second = interpreter.metrics
+ third = Dsl::PipelineInterpreter.new(steps).metrics
+
+ assert_equal first, second
+ assert_equal first, third
+ end
+
+ test "failure_step points to first error when multiple errors exist" do
+ steps = [
+ { step: 5, type: "ruby", output: ["a"], error: "first" },
+ { step: 7, type: "ruby", output: ["b"], error: "second" }
+ ]
+
+ assert_equal 5, Dsl::PipelineInterpreter.new(steps).metrics[:failure_step]
+ end
+
+ test "failure_step is nil when only ruby steps exist even with losses" do
+ steps = [
+ { step: 1, type: "ruby", output: %w[a b c] },
+ { step: 4, type: "ruby", output: ["a"] },
+ { step: 9, type: "ruby", output: [] }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+
+ assert_equal true, metrics[:data_loss]
+ assert_equal false, metrics[:extraction_attempted]
+ assert_nil metrics[:failure_step]
+ end
+
+ test "extraction_empty cannot be true when extraction_attempted is false" do
+ steps = [
+ { step: 1, type: "url", output: nil },
+ { step: 2, type: "ruby", output: [] }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+ assert_equal false, metrics[:extraction_attempted]
+ assert_equal false, metrics[:extraction_empty]
+ end
+
+ test "recovered_after_loss cannot be true when data_loss is false" do
+ steps = [
+ { step: 1, type: "ruby", output: ["a"] },
+ { step: 2, type: "ruby", output: ["b"] },
+ { step: 3, type: "ruby", output: ["c"] }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+ assert_equal false, metrics[:data_loss]
+ assert_equal false, metrics[:recovered_after_loss]
+ end
+
+ test "probe metadata does not affect interpreter metrics or diagnosis" do
+ base_steps = [
+ { step: 1, type: "url", output: nil },
+ { step: 2, type: "xpath", output: [] }
+ ]
+ probe_steps = [
+ { step: 1, type: "url", output: nil },
+ {
+ step: 2,
+ type: "xpath",
+ output: [],
+ probe: { status: "ok", xpath: "//title", output: ["Probe Title"] }
+ }
+ ]
+
+ metrics_without_probe = Dsl::PipelineInterpreter.new(base_steps).metrics
+ metrics_with_probe = Dsl::PipelineInterpreter.new(probe_steps).metrics
+
+ assert_equal metrics_without_probe, metrics_with_probe
+
+ wringer = { unreachable: false, received_404: false, system_error: false, policy_action: nil }
+ diagnosis_without_probe = Dsl::PipelineDiagnosis.new(metrics: metrics_without_probe, wringer: wringer).result
+ diagnosis_with_probe = Dsl::PipelineDiagnosis.new(metrics: metrics_with_probe, wringer: wringer).result
+
+ assert_equal diagnosis_without_probe, diagnosis_with_probe
+ end
+
+ test "final_empty is based on last output only" do
+ steps = [
+ { step: 1, type: "ruby", output: ["a"] },
+ { step: 2, type: "ruby", output: [] },
+ { step: 3, type: "ruby", output: ["z"] }
+ ]
+
+ assert_equal false, Dsl::PipelineInterpreter.new(steps).metrics[:final_empty]
+ end
+
+ test "suspicious_navigation is true when navigation followed by empty extraction" do
+ steps = [
+ { step: 1, type: "url", output: nil },
+ { step: 2, type: "xpath", output: [] }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+
+ assert_equal true, metrics[:suspicious_navigation]
+ end
+
+ test "suspicious_navigation is false when navigation followed by successful extraction" do
+ steps = [
+ { step: 1, type: "url", output: nil },
+ { step: 2, type: "xpath", output: ["data"] }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+
+ assert_equal false, metrics[:suspicious_navigation]
+ end
+
+ test "suspicious_navigation is false when no navigation occurs" do
+ steps = [
+ { step: 1, type: "xpath", output: [] },
+ { step: 2, type: "ruby", output: [] }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+
+ assert_equal false, metrics[:suspicious_navigation]
+ end
+
+ test "suspicious_navigation ignores xpath outside extraction phase" do
+ steps = [
+ { step: 1, type: "url", output: nil },
+ { step: 2, type: "ruby", output: ["prep"] },
+ { step: 3, type: "xpath", output: [] }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+
+ assert_equal false, metrics[:suspicious_navigation]
+ end
+
+ test "suspicious_navigation detects url-like step types" do
+ steps = [
+ { step: 1, type: "post_url_transform", output: nil },
+ { step: 2, type: "xpath", output: [] }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+
+ assert_equal true, metrics[:suspicious_navigation]
+ end
+
+ test "suspicious_navigation cannot be true when has_navigation is false" do
+ steps = [
+ { step: 1, type: "xpath", output: [] }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+
+ assert_equal false, metrics[:has_navigation]
+ assert_equal false, metrics[:suspicious_navigation]
+ end
+
+ test "suspicious_navigation true implies navigation and extraction were attempted" do
+ steps = [
+ { step: 1, type: "url", output: nil },
+ { step: 2, type: "xpath", output: [] }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+
+ assert_equal true, metrics[:suspicious_navigation]
+ assert_equal true, metrics[:has_navigation]
+ assert_equal true, metrics[:extraction_attempted]
+ end
+
+ test "suspicious_navigation does not force data_loss to true by itself" do
+ steps = [
+ { step: 1, type: "url", output: nil },
+ { step: 2, type: "xpath", output: [] }
+ ]
+
+ metrics = Dsl::PipelineInterpreter.new(steps).metrics
+
+ assert_equal true, metrics[:suspicious_navigation]
+ assert_equal false, metrics[:data_loss]
+ end
+
+
+end
diff --git a/test/services/dsl/semantic_interpreter_test.rb b/test/services/dsl/semantic_interpreter_test.rb
new file mode 100644
index 00000000..b6d26113
--- /dev/null
+++ b/test/services/dsl/semantic_interpreter_test.rb
@@ -0,0 +1,56 @@
+require "test_helper"
+
+class Dsl::SemanticInterpreterTest < ActiveSupport::TestCase
+ test "empty to non-empty is Δ added" do
+ si = Dsl::SemanticInterpreter.new
+ assert_equal "Δ added", si.semantic([], ["A"])
+ end
+
+ test "empty to empty is no result" do
+ si = Dsl::SemanticInterpreter.new
+ assert_equal "No result", si.semantic([], [])
+ end
+
+ test "same value is no change" do
+ si = Dsl::SemanticInterpreter.new
+ assert_equal "No change", si.semantic(["A"], ["A"])
+ end
+
+ test "non-empty to empty is removed" do
+ si = Dsl::SemanticInterpreter.new
+ assert_equal "Δ removed", si.semantic(["A"], [])
+ end
+
+ test "different non-empty values are changed" do
+ si = Dsl::SemanticInterpreter.new
+ assert_equal "Δ changed", si.semantic(["A"], ["B"])
+ end
+
+ test "delta only exists for changed semantics" do
+ si = Dsl::SemanticInterpreter.new
+
+ assert_nil si.delta(["A"], ["A"], "No change")
+ assert_not_nil si.delta(["A"], ["B"], "Δ changed")
+ end
+
+ test "intent classification follows type and code" do
+ si = Dsl::SemanticInterpreter.new
+
+ assert_equal "extraction", si.intent(type: "xpath")
+ assert_equal "filter", si.intent(type: "ruby", code: "reject")
+ end
+
+ test "annotate keeps summary-string delta compatibility" do
+ si = Dsl::SemanticInterpreter.new
+ steps = [
+ { step: 1, type: "ruby", output: "[1 items: seed]" },
+ { step: 2, type: "ruby", output: "[1 items: https://example.com/a]" }
+ ]
+
+ annotated = si.annotate(steps)
+
+ assert_equal "Δ changed", annotated.second[:semantic]
+ assert_match(/\+https:\/\/example.com/, annotated.second[:delta])
+ assert_match(/-seed/, annotated.second[:delta])
+ end
+end
diff --git a/test/services/dsl/trace_formatter_test.rb b/test/services/dsl/trace_formatter_test.rb
new file mode 100644
index 00000000..e99e6e6b
--- /dev/null
+++ b/test/services/dsl/trace_formatter_test.rb
@@ -0,0 +1,429 @@
+require "test_helper"
+
+class Dsl::Tracing::TraceFormatterTest < ActiveSupport::TestCase
+ test "for_ui truncates long strings" do
+ formatted = Dsl::Tracing::TraceFormatter.for_ui([
+ {
+ step: 1,
+ type: "ruby",
+ code: "x" * 400,
+ input_preview: ["in"],
+ output_preview: ["out"]
+ }
+ ])
+
+ assert_equal 1, formatted.length
+ assert_equal 203, formatted.first[:code].length
+ assert_equal("...", formatted.first[:code][-3, 3])
+ end
+
+ test "format_value summarizes arrays with size and sample" do
+ value = (1..10).to_a
+
+ formatted = Dsl::Tracing::TraceFormatter.format_value(value)
+
+ assert_match(/\A\[Array size=10, sample=/, formatted)
+ assert_includes formatted, "1"
+ assert_includes formatted, "5"
+ refute_includes formatted, "6"
+ end
+
+ test "format_value converts exceptions to message" do
+ formatted = Dsl::Tracing::TraceFormatter.format_value(StandardError.new("boom"))
+
+ assert_equal "boom", formatted
+ end
+
+ test "format_value preserves nil" do
+ assert_nil Dsl::Tracing::TraceFormatter.format_value(nil)
+ end
+
+ test "for_ui handles mixed hash and to_h structures" do
+ step_like = Struct.new(:payload) do
+ def to_h
+ payload
+ end
+ end
+
+ input = [
+ {
+ step: 1,
+ type: "xpath",
+ code: "//a",
+ input_preview: ["one"],
+ output_preview: ["two"],
+ error_class: nil,
+ error_message: nil,
+ url_before: "https://example.com/1",
+ url_after: "https://example.com/1",
+ duration_ms: 1.1
+ },
+ step_like.new(
+ {
+ step: 2,
+ type: "ruby",
+ code: "$array.map(&:upcase)",
+ input_preview: ["a"],
+ output_preview: ["A"],
+ error_class: "RuntimeError",
+ error_message: "failed",
+ url_before: "https://example.com/2",
+ url_after: "https://example.com/2",
+ duration_ms: 2.2
+ }
+ )
+ ]
+
+ formatted = Dsl::Tracing::TraceFormatter.for_ui(input)
+
+ assert_equal 2, formatted.length
+ assert_equal "xpath", formatted.first[:type]
+ assert_match(/\A\[Array size=1, sample=\[\"one\"\]\]\z/, formatted.first[:input])
+ assert_equal "RuntimeError: failed", formatted.second[:error]
+ end
+
+ test "for_ui returns empty array for nil or invalid structure" do
+ assert_equal [], Dsl::Tracing::TraceFormatter.for_ui(nil)
+ assert_equal [], Dsl::Tracing::TraceFormatter.for_ui("invalid")
+ end
+
+ test "preserves all trace steps even when large" do
+ trace = (1..10).map do |i|
+ {
+ step: i,
+ type: "ruby",
+ code: "x" * 1000,
+ input: ["y" * 1000],
+ output: ["z" * 1000]
+ }
+ end
+
+ formatted = Dsl::Tracing::TraceFormatter.for_ui(trace)
+
+ assert_equal 10, formatted.size
+ end
+
+ test "preserves last step containing error" do
+ trace = (1..5).map do |i|
+ { step: i, type: "ruby", code: "ok" }
+ end
+
+ trace << {
+ step: 6,
+ type: "ruby",
+ code: "fail",
+ error_class: "NoMethodError",
+ error_message: "boom"
+ }
+
+ formatted = Dsl::Tracing::TraceFormatter.for_ui(trace)
+
+ last = formatted.last
+
+ assert_equal 6, last[:step]
+ assert_match /NoMethodError/, last[:error]
+ end
+
+ test "logs and skips malformed events without reducing valid count incorrectly" do
+ trace = [
+ { step: 1, type: "ruby", code: "ok" },
+ "invalid_event",
+ { step: 2, type: "xpath", code: "//a" }
+ ]
+
+ formatted = Dsl::Tracing::TraceFormatter.for_ui(trace)
+
+ assert_equal 2, formatted.size
+ end
+
+ test "for_session preserves all trace steps in compact events" do
+ trace = (1..10).map do |i|
+ {
+ step: i,
+ type: "ruby",
+ code: "x" * 1000,
+ input_preview: ["y" * 1000],
+ output_preview: ["z" * 1000],
+ url_before: "http://example.com",
+ url_after: "http://example.com",
+ duration_ms: 1.0
+ }
+ end
+
+ compact = Dsl::Tracing::TraceFormatter.for_session(trace).with_indifferent_access
+
+ assert_equal 1, compact[:version]
+ assert_equal 10, compact[:events].size
+ end
+
+ test "for_session keeps full error text and longer code on error step" do
+ trace = [
+ { step: 1, type: "ruby", code: "a" * 300 },
+ {
+ step: 2,
+ type: "ruby",
+ code: "b" * 300,
+ error_class: "NoMethodError",
+ error_message: "boom boom boom"
+ }
+ ]
+
+ compact = Dsl::Tracing::TraceFormatter.for_session(trace).with_indifferent_access
+ normal = compact[:events].first.with_indifferent_access
+ errored = compact[:events].second.with_indifferent_access
+
+ assert_operator normal[:c].length, :<=, 83
+ assert_operator errored[:c].length, :<=, 203
+ assert_equal "NoMethodError: boom boom boom", errored[:e]
+ end
+
+ test "for_session deduplicates urls and stores index references" do
+ trace = [
+ { step: 1, type: "ruby", url_before: "http://example.com/a", url_after: "http://example.com/b" },
+ { step: 2, type: "ruby", url_before: "http://example.com/a", url_after: "http://example.com/b" }
+ ]
+
+ compact = Dsl::Tracing::TraceFormatter.for_session(trace).with_indifferent_access
+
+ assert_equal ["http://example.com/a", "http://example.com/b"], compact[:urls]
+ assert_equal 0, compact[:events].first.with_indifferent_access[:ub]
+ assert_equal 1, compact[:events].first.with_indifferent_access[:ua]
+ assert_equal 0, compact[:events].second.with_indifferent_access[:ub]
+ assert_equal 1, compact[:events].second.with_indifferent_access[:ua]
+ end
+
+ test "for_session_v2 preserves all steps without loss" do
+ trace = (1..20).map do |i|
+ {
+ step: i,
+ type: "ruby",
+ code: "x" * 12,
+ input_preview: ["y" * 12],
+ output_preview: ["z" * 12],
+ url_before: "http://example.com",
+ url_after: "http://example.com",
+ duration_ms: 1.0
+ }
+ end
+
+ compact = Dsl::Tracing::TraceFormatter.for_session_v2(trace).with_indifferent_access
+
+ assert_equal 2, compact[:version]
+ assert_equal 20, compact[:steps].size
+ end
+
+ test "for_session_v2 stores transition chain with initial state" do
+ trace = [
+ { step: 1, type: "ruby", input_preview: ["in-0"], output_preview: ["out-1"] },
+ { step: 2, type: "ruby", input_preview: ["out-1"], output_preview: ["out-2"] }
+ ]
+
+ compact = Dsl::Tracing::TraceFormatter.for_session_v2(trace).with_indifferent_access
+
+ assert_match(/\A\[1 items: in-0\]\z/, compact[:initial].with_indifferent_access[:state])
+ assert_match(/\A\[1 items: out-1\]\z/, compact[:steps].first.with_indifferent_access[:o])
+ assert_match(/\A\[1 items: out-2\]\z/, compact[:steps].second.with_indifferent_access[:o])
+ end
+
+ test "for_session_v2 deduplicates urls and stores only url transitions" do
+ trace = [
+ { step: 1, type: "ruby", url_before: "http://example.com/a", url_after: "http://example.com/b" },
+ { step: 2, type: "ruby", url_before: "http://example.com/b", url_after: "http://example.com/b" },
+ { step: 3, type: "ruby", url_before: "http://example.com/b", url_after: "http://example.com/c" }
+ ]
+
+ compact = Dsl::Tracing::TraceFormatter.for_session_v2(trace).with_indifferent_access
+ steps = compact[:steps].map { |s| s.with_indifferent_access }
+
+ assert_equal "http://example.com/a", compact[:initial].with_indifferent_access[:url]
+ assert_equal ["http://example.com/b", "http://example.com/c"], compact[:urls]
+ assert_equal 0, steps.first[:ua]
+ assert_nil steps.second[:ua]
+ assert_equal 1, steps.third[:ua]
+ end
+
+ test "for_session_v2 preserves error step details" do
+ trace = [
+ { step: 1, type: "ruby", code: "a" * 500 },
+ {
+ step: 2,
+ type: "ruby",
+ code: "f" * 500,
+ error_class: "NoMethodError",
+ error_message: "boom"
+ }
+ ]
+
+ compact = Dsl::Tracing::TraceFormatter.for_session_v2(trace).with_indifferent_access
+ errored = compact[:steps].second.with_indifferent_access
+
+ assert_equal 2, errored[:s]
+ assert_match(/NoMethodError: boom/, errored[:e])
+ assert errored[:c].present?
+ assert_operator compact[:steps].first.with_indifferent_access[:c].length, :<=, 43
+ assert_operator errored[:c].length, :<=, 123
+ end
+
+ test "for_session_v2 array summaries include sample content" do
+ trace = [
+ {
+ step: 1,
+ type: "ruby",
+ input_preview: ["https://example.com/alpha/very/long/path", "2026-01-01", "ignored"],
+ output_preview: ["https://example.com/beta/very/long/path", "ok", "ignored"]
+ }
+ ]
+
+ compact = Dsl::Tracing::TraceFormatter.for_session_v2(trace).with_indifferent_access
+ initial_state = compact[:initial].with_indifferent_access[:state]
+ output_state = compact[:steps].first.with_indifferent_access[:o]
+
+ assert_match(/\A\[3 items: /, initial_state)
+ assert_match(/https:\/\/example.com\/alpha/, initial_state)
+ assert_match(/\A\[3 items: /, output_state)
+ assert_match(/https:\/\/example.com\/beta/, output_state)
+ end
+
+ test "for_session_v2 preserves normalized probe payload" do
+ trace = [
+ { step: 1, type: "url", output_preview: [] },
+ {
+ step: 2,
+ type: "xpath",
+ output_preview: [],
+ probe: {
+ status: "ok",
+ xpath: "//title",
+ output: [nil, "Alpha", :beta, "Gamma", "Delta"]
+ }
+ }
+ ]
+
+ compact = Dsl::Tracing::TraceFormatter.for_session_v2(trace).with_indifferent_access
+ probe = compact[:steps].second.with_indifferent_access[:p].with_indifferent_access
+
+ assert_equal "ok", probe[:st]
+ assert_equal "//title", probe[:x]
+ assert_equal ["Alpha", "beta", "Gamma"], probe[:o]
+ end
+
+ test "for_session_v2 includes full fields for warning and error severities" do
+ trace = [
+ { step: 1, type: "ruby", code: "ok", output_preview: ["ok"] },
+ { step: 2, type: "ruby", code: "warn" * 60, output_preview: [], probe: { skipped: false, result: { status: "ok" } } },
+ { step: 3, type: "ruby", code: "boom" * 60, output_preview: ["very long output " * 20], error_class: "RuntimeError", error_message: "boom" }
+ ]
+
+ compact = Dsl::Tracing::TraceFormatter.for_session_v2(trace).with_indifferent_access
+ ok_step = compact[:steps].first.with_indifferent_access
+ warning_step = compact[:steps].second.with_indifferent_access
+ error_step = compact[:steps].third.with_indifferent_access
+
+ assert_nil ok_step[:cf]
+ assert_nil ok_step[:of]
+ assert warning_step[:cf].present?
+ assert_equal "[]", warning_step[:of]
+ assert error_step[:cf].present?
+ assert error_step[:of].present?
+ end
+
+ test "for_session_v2 warning output full falls back to output limit" do
+ trace = [
+ {
+ step: 1,
+ type: "ruby",
+ code: "warn",
+ output_preview: ["x" * 400],
+ wringer: { system_error: true }
+ }
+ ]
+
+ compact = Dsl::Tracing::TraceFormatter.for_session_v2(trace).with_indifferent_access
+ step = compact[:steps].first.with_indifferent_access
+
+ assert step[:o].present?
+ assert step[:of].present?
+ assert_equal step[:o], step[:of]
+ end
+
+ test "for_session_v2 enforces session budget after final fallback" do
+ trace = (1..250).map do |i|
+ {
+ step: i,
+ type: "ruby",
+ code: "x" * 2000,
+ input_preview: ["y" * 2000, "z" * 2000],
+ output_preview: ["w" * 2000, "q" * 2000],
+ error_class: "RuntimeError",
+ error_message: "boom " * 80,
+ wringer: {
+ signals: {
+ network_status: "failed",
+ content_type: "html",
+ primary_issue_key: "timeout",
+ final_url: "https://example.org/" + ("deep/path/" * 30)
+ },
+ hints: Array.new(20) { "hint-" + ("very-long-" * 20) }
+ }
+ }
+ end
+
+ compact = Dsl::Tracing::TraceFormatter.for_session_v2(trace)
+
+ assert_operator JSON.generate(compact).bytesize, :<=, Dsl::Tracing::TraceFormatter::MAX_SESSION_BYTES
+ assert compact[:steps].present?
+ assert_match(/Trace omitted|boom/, compact[:steps].first[:e].to_s)
+ end
+
+ test "for_session_v2 summarizes wringer diagnostics instead of copying full payloads" do
+ trace = [
+ {
+ step: 1,
+ type: "url",
+ wringer: {
+ policy_action: "abort_update",
+ final_url: "https://example.org/final",
+ redirect_chain: [
+ "https://example.org/start",
+ "https://example.org/final",
+ "https://example.org/ignored"
+ ],
+ signals: {
+ network_status: "ok",
+ content_type: "html",
+ blocking_issue_key: "redirect_to_listing",
+ primary_issue_label: "Redirect to listing",
+ fetch_backend: "phantomjs",
+ fetched_body_state: "non_empty",
+ stored_body_state: "not_stored",
+ raw_html_blob: "" + ("x" * 500) + "",
+ nested_payload: { giant: "y" * 500 }
+ },
+ hints: [
+ "redirect_to_listing",
+ { detail: "z" * 500 },
+ "ignored-third-hint"
+ ]
+ }
+ }
+ ]
+
+ compact = Dsl::Tracing::TraceFormatter.for_session_v2(trace).with_indifferent_access
+ wringer = compact[:steps].first.with_indifferent_access[:w].with_indifferent_access
+ signals = wringer[:s].with_indifferent_access
+
+ assert_equal "https://example.org/final", wringer[:fu]
+ assert_equal ["https://example.org/start", "https://example.org/final"], wringer[:rc]
+ assert_equal "ok", signals[:network_status]
+ assert_equal "html", signals[:content_type]
+ assert_equal "redirect_to_listing", signals[:blocking_issue_key]
+ assert_equal "Redirect to listing", signals[:primary_issue_label]
+ assert_equal "phantomjs", signals[:fetch_backend]
+ assert_equal "non_empty", signals[:fetched_body_state]
+ assert_equal "not_stored", signals[:stored_body_state]
+ refute signals.key?(:raw_html_blob)
+ refute signals.key?(:nested_payload)
+ assert_equal 2, wringer[:h].length
+ assert_operator wringer[:h].first.length, :<=, 60
+ end
+end
diff --git a/test/services/dsl/url_resolver_test.rb b/test/services/dsl/url_resolver_test.rb
new file mode 100644
index 00000000..19a59852
--- /dev/null
+++ b/test/services/dsl/url_resolver_test.rb
@@ -0,0 +1,37 @@
+require "test_helper"
+
+class Dsl::Support::UrlResolverTest < ActiveSupport::TestCase
+ test "extract returns normalized url for string input" do
+ value = " https://example.com/events/1#tickets "
+
+ assert_equal "https://example.com/events/1#tickets", Dsl::Support::UrlResolver.extract(value)
+ end
+
+ test "extract returns first valid url from mixed array input" do
+ value = [nil, "not-a-url", "https://first.example.com/path#fragment", "https://second.example.com"]
+
+ assert_equal "https://first.example.com/path#fragment", Dsl::Support::UrlResolver.extract(value)
+ end
+
+ test "extract returns nil for hash input" do
+ value = { "url" => "https://example.com" }
+
+ assert_nil Dsl::Support::UrlResolver.extract(value)
+ end
+
+ test "extract returns first valid url from json string input" do
+ value = "https://json.example.com/tickets#purchase".to_json
+
+ assert_equal "https://json.example.com/tickets#purchase", Dsl::Support::UrlResolver.extract(value)
+ end
+
+ test "extract returns nil for invalid url input" do
+ value = "ftp://example.com/file"
+
+ assert_nil Dsl::Support::UrlResolver.extract(value)
+ end
+
+ test "extract returns nil for nil input" do
+ assert_nil Dsl::Support::UrlResolver.extract(nil)
+ end
+end
diff --git a/test/services/dsl/wringer_client_test.rb b/test/services/dsl/wringer_client_test.rb
new file mode 100644
index 00000000..ff54b509
--- /dev/null
+++ b/test/services/dsl/wringer_client_test.rb
@@ -0,0 +1,734 @@
+require "test_helper"
+
+class Dsl::Support::WringerClientTest < ActiveSupport::TestCase
+ FakeWringerResponse = Struct.new(:code, :body, :uri, :response, keyword_init: true) do
+ def [](key)
+ response && (response[key] || response[key.to_s])
+ end
+
+ def headers
+ response
+ end
+ end
+
+ FakeHistoryEntry = Struct.new(:uri, keyword_init: true)
+
+ class FakeWringerAgent
+ attr_reader :called_urls
+
+ def initialize(response:, history: [])
+ @response = response
+ @history = history
+ @called_urls = []
+ end
+
+ def get(url)
+ @called_urls << url
+ @response
+ end
+
+ def history
+ @history
+ end
+ end
+
+ test "successful fetch returns status ok, body string, wringer diagnostics" do
+ captured = {}
+ agent = mock("agent")
+ agent.expects(:get_file).with("wringer://resolved").returns("ok")
+
+ use_wringer = lambda do |url, render_js, scrape_options|
+ captured = { url: url, render_js: render_js, scrape_options: scrape_options }
+ "wringer://resolved"
+ end
+ safe_wringer_call = ->(&blk) { blk.call }
+
+ client = Dsl::Support::WringerClient.new(
+ agent: agent,
+ render_js: false,
+ scrape_options: { force_scrape_every_hrs: 2 },
+ use_wringer: use_wringer,
+ safe_wringer_call: safe_wringer_call,
+ logger: Rails.logger
+ )
+
+ result = client.fetch(url: "https://example.com/events")
+
+ assert_equal :ok, result[:status]
+ assert_equal "ok", result[:body]
+ assert result[:wringer].is_a?(Hash)
+ assert result[:wringer].key?(:signals)
+ assert result[:wringer].key?(:hints)
+ assert_equal(
+ { url: "https://example.com/events", render_js: false, scrape_options: { force_scrape_every_hrs: 2 } },
+ captured
+ )
+ end
+
+ test "wringer returns signals and hints on successful fetch" do
+ agent = mock("agent")
+ agent.expects(:get_file).with("wringer://resolved").returns("ok")
+
+ client = Dsl::Support::WringerClient.new(
+ agent: agent,
+ render_js: false,
+ scrape_options: {},
+ use_wringer: ->(*_) { "wringer://resolved" },
+ safe_wringer_call: ->(&blk) { blk.call },
+ logger: Rails.logger
+ )
+
+ result = client.fetch(url: "https://example.com/events")
+
+ assert_equal "html", result[:wringer][:signals][:content_type]
+ assert_equal "ok", result[:wringer][:signals][:network_status]
+ assert_equal [], result[:wringer][:hints]
+ end
+
+ test "abort payload returns status abort with original payload preserved" do
+ payload = ["abort_update", { error_type: "system_cloudflare", retry: true, cache: false }]
+ safe_wringer_call = ->(&_) { payload }
+
+ client = Dsl::Support::WringerClient.new(
+ agent: mock("agent"),
+ render_js: false,
+ scrape_options: {},
+ use_wringer: ->(*_) { "wringer://resolved" },
+ safe_wringer_call: safe_wringer_call,
+ logger: Rails.logger
+ )
+
+ result = client.fetch(url: "https://example.com/events")
+
+ assert_equal :abort, result[:status]
+ assert_equal payload, result[:body]
+ assert_equal "system_cloudflare", result[:wringer][:error_type]
+ assert_equal true, result[:wringer][:retry]
+ assert_equal false, result[:wringer][:cache]
+ assert_equal({}, result[:wringer][:signals])
+ assert_equal [], result[:wringer][:hints]
+ end
+
+ test "wringer status extracts retry and cache from top-level payload" do
+ payload = [
+ "abort_update",
+ {
+ error_type: "system_cloudflare",
+ retry: false,
+ cache: true,
+ policy: { retry: true, cache: false }
+ }
+ ]
+
+ client = Dsl::Support::WringerClient.new(
+ agent: mock("agent"),
+ render_js: false,
+ scrape_options: {},
+ use_wringer: ->(*_) { "wringer://resolved" },
+ safe_wringer_call: ->(&_) { payload },
+ logger: Rails.logger
+ )
+
+ result = client.fetch(url: "https://example.com/events")
+
+ assert_equal "system_cloudflare", result[:wringer][:error_type]
+ assert_equal false, result[:wringer][:retry]
+ assert_equal true, result[:wringer][:cache]
+ assert_equal({}, result[:wringer][:signals])
+ assert_equal [], result[:wringer][:hints]
+ end
+
+ test "wringer status falls back to retry and cache from nested policy" do
+ payload = [
+ "abort_update",
+ {
+ error_type: "system_queue",
+ policy: { retry: true, cache: false }
+ }
+ ]
+
+ client = Dsl::Support::WringerClient.new(
+ agent: mock("agent"),
+ render_js: false,
+ scrape_options: {},
+ use_wringer: ->(*_) { "wringer://resolved" },
+ safe_wringer_call: ->(&_) { payload },
+ logger: Rails.logger
+ )
+
+ result = client.fetch(url: "https://example.com/events")
+
+ assert_equal "system_queue", result[:wringer][:error_type]
+ assert_equal true, result[:wringer][:retry]
+ assert_equal false, result[:wringer][:cache]
+ assert_equal({}, result[:wringer][:signals])
+ assert_equal [], result[:wringer][:hints]
+ end
+
+ test "partial abort payload includes only available wringer status fields" do
+ payload = ["abort_update", { error_type: "system_cloudflare" }]
+
+ client = Dsl::Support::WringerClient.new(
+ agent: mock("agent"),
+ render_js: false,
+ scrape_options: {},
+ use_wringer: ->(*_) { "wringer://resolved" },
+ safe_wringer_call: ->(&_) { payload },
+ logger: Rails.logger
+ )
+
+ result = client.fetch(url: "https://example.com/events")
+
+ assert_equal "system_cloudflare", result[:wringer][:error_type]
+ assert_equal({}, result[:wringer][:signals])
+ assert_equal [], result[:wringer][:hints]
+ end
+
+ test "render_js option is passed through to use_wringer" do
+ captured = {}
+ agent = mock("agent")
+ agent.expects(:get_file).with("wringer://resolved").returns("ok")
+
+ use_wringer = lambda do |url, render_js, scrape_options|
+ captured = { url: url, render_js: render_js, scrape_options: scrape_options }
+ "wringer://resolved"
+ end
+
+ client = Dsl::Support::WringerClient.new(
+ agent: agent,
+ render_js: false,
+ scrape_options: {},
+ use_wringer: use_wringer,
+ safe_wringer_call: ->(&blk) { blk.call },
+ logger: Rails.logger
+ )
+
+ client.fetch(url: "https://example.com/events", render_js: true)
+
+ assert_equal true, captured[:render_js]
+ end
+
+ test "custom scrape_options are passed through to use_wringer" do
+ captured = {}
+ agent = mock("agent")
+ agent.expects(:get_file).with("wringer://resolved").returns("ok")
+
+ use_wringer = lambda do |url, render_js, scrape_options|
+ captured = { url: url, render_js: render_js, scrape_options: scrape_options }
+ "wringer://resolved"
+ end
+
+ client = Dsl::Support::WringerClient.new(
+ agent: agent,
+ render_js: false,
+ scrape_options: {},
+ use_wringer: use_wringer,
+ safe_wringer_call: ->(&blk) { blk.call },
+ logger: Rails.logger
+ )
+
+ options = { json_post: true, absolute_src: true, force_scrape_every_hrs: 1 }
+ client.fetch(url: "https://example.com/events", scrape_options: options)
+
+ assert_equal options, captured[:scrape_options]
+ end
+
+ test "nil response remains ok and includes normalized wringer diagnostics" do
+ client = Dsl::Support::WringerClient.new(
+ agent: mock("agent"),
+ render_js: false,
+ scrape_options: {},
+ use_wringer: ->(*_) { "wringer://resolved" },
+ safe_wringer_call: ->(&_) { nil },
+ logger: Rails.logger
+ )
+
+ result = client.fetch(url: "https://example.com/events")
+ assert_equal :ok, result[:status]
+ assert_nil result[:body]
+ assert_equal({}, result[:wringer][:signals])
+ assert_equal [], result[:wringer][:hints]
+ end
+
+ test "malformed abort payload is normalized to explicit wringer abort error" do
+ malformed_payload = ["abort_update", "broken-payload"]
+ malformed_client = Dsl::Support::WringerClient.new(
+ agent: mock("agent"),
+ render_js: false,
+ scrape_options: {},
+ use_wringer: ->(*_) { "wringer://resolved" },
+ safe_wringer_call: ->(&_) { malformed_payload },
+ logger: Rails.logger
+ )
+
+ malformed = malformed_client.fetch(url: "https://example.com/events")
+ assert_equal :abort, malformed[:status]
+ assert_equal "abort_update", malformed[:body].first
+ assert_equal "WringerMalformedControlPayload", malformed[:body].last[:error_type]
+ assert_equal "wringer", malformed[:body].last[:source]
+ assert_equal({}, malformed[:wringer][:signals])
+ assert_equal [], malformed[:wringer][:hints]
+ end
+
+ test "skip control action is normalized to abort_update wringer skip" do
+ client = Dsl::Support::WringerClient.new(
+ agent: mock("agent"),
+ render_js: false,
+ scrape_options: {},
+ use_wringer: ->(*_) { "wringer://resolved" },
+ safe_wringer_call: ->(&_) { ["skip", { reason: "policy_skip" }] },
+ logger: Rails.logger
+ )
+
+ result = client.fetch(url: "https://example.com/events")
+
+ assert_equal :abort, result[:status]
+ assert_equal "abort_update", result[:body].first
+ assert_equal "WringerSkip", result[:body].last[:error_type]
+ assert_equal "wringer", result[:body].last[:source]
+ end
+
+ test "unknown wringer control action is normalized to unsupported action abort_update" do
+ client = Dsl::Support::WringerClient.new(
+ agent: mock("agent"),
+ render_js: false,
+ scrape_options: {},
+ use_wringer: ->(*_) { "wringer://resolved" },
+ safe_wringer_call: ->(&_) { ["foo", { reason: "unknown" }] },
+ logger: Rails.logger
+ )
+
+ result = client.fetch(url: "https://example.com/events")
+
+ assert_equal :abort, result[:status]
+ assert_equal "abort_update", result[:body].first
+ assert_equal "WringerUnsupportedAction", result[:body].last[:error_type]
+ assert_equal "Unsupported Wringer action: foo", result[:body].last[:error]
+ assert_equal "wringer", result[:body].last[:source]
+ end
+
+ test "single-element arrays are not treated as control tuples" do
+ payload = ["skip"]
+ client = Dsl::Support::WringerClient.new(
+ agent: mock("agent"),
+ render_js: false,
+ scrape_options: {},
+ use_wringer: ->(*_) { "wringer://resolved" },
+ safe_wringer_call: ->(&_) { payload },
+ logger: Rails.logger
+ )
+
+ result = client.fetch(url: "https://example.com/events")
+
+ assert_equal :ok, result[:status]
+ assert_equal payload, result[:body]
+ end
+
+ test "fetch failure is normalized to WringerFetchError with url step" do
+ client = Dsl::Support::WringerClient.new(
+ agent: mock("agent"),
+ render_js: false,
+ scrape_options: {},
+ use_wringer: ->(*_) { "wringer://resolved" },
+ safe_wringer_call: ->(&_) { ["abort_update", { error: "connection refused", error_type: "wringer_unreachable", source: "wringer" }] },
+ logger: Rails.logger
+ )
+
+ result = client.fetch(url: "https://example.com/events")
+
+ assert_equal :abort, result[:status]
+ assert_equal "abort_update", result[:body].first
+ assert_equal "WringerFetchError", result[:body].last[:error_type]
+ assert_equal "url", result[:body].last[:step]
+ assert_equal "wringer", result[:body].last[:source]
+ end
+
+ test "metadata normalization enforces signals hash and hints array for abort payload" do
+ payload = ["abort_update", { error_type: "system_cloudflare", signals: "bad-shape", hints: "bad-shape" }]
+ client = Dsl::Support::WringerClient.new(
+ agent: mock("agent"),
+ render_js: false,
+ scrape_options: {},
+ use_wringer: ->(*_) { "wringer://resolved" },
+ safe_wringer_call: ->(&_) { payload },
+ logger: Rails.logger
+ )
+
+ result = client.fetch(url: "https://example.com/events")
+
+ assert_equal :abort, result[:status]
+ assert_equal({}, result[:wringer][:signals])
+ assert_equal [], result[:wringer][:hints]
+ end
+
+ test "control tuples never return html body content" do
+ agent = mock("agent")
+ agent.expects(:get_file).with("wringer://resolved").returns("should_not_escape_control")
+
+ client = Dsl::Support::WringerClient.new(
+ agent: agent,
+ render_js: false,
+ scrape_options: {},
+ use_wringer: ->(*_) { "wringer://resolved" },
+ safe_wringer_call: lambda do |&blk|
+ blk.call
+ ["skip", { reason: "policy_skip" }]
+ end,
+ logger: Rails.logger
+ )
+
+ result = client.fetch(url: "https://example.com/events")
+
+ assert_equal :abort, result[:status]
+ assert_equal "abort_update", result[:body].first
+ refute_equal "should_not_escape_control", result[:body]
+ end
+
+ test "fetch detects 404 from response metadata and exposes wringer status" do
+ agent = mock("agent")
+ helper = ApplicationController.helpers
+ helper.stubs(:wringer_rules).returns(
+ {
+ "http_404" => {
+ "match" => { "http_code" => 404 },
+ "policy" => { "action" => "abort_update", "retry" => false, "cache" => false, "error_code" => "http_404" }
+ }
+ }.to_a
+ )
+ response = Struct.new(:code, :body, :uri).new(
+ 404,
+ "Not Found",
+ URI("https://example.com/missing")
+ )
+ agent.expects(:get).with("wringer://resolved").returns(response)
+
+ client = Dsl::Support::WringerClient.new(
+ agent: agent,
+ render_js: false,
+ scrape_options: {},
+ use_wringer: ->(*_) { "wringer://resolved" },
+ safe_wringer_call: helper.method(:safe_wringer_call),
+ logger: Rails.logger
+ )
+
+ result = client.fetch(url: "https://example.com/events")
+
+ assert_equal :abort, result[:status]
+ assert_equal "abort_update", result[:body].first
+ assert_equal "http_404", result[:wringer][:error_type]
+ end
+
+ test "fetch detects 500 from response metadata and exposes wringer status" do
+ agent = mock("agent")
+ helper = ApplicationController.helpers
+ helper.stubs(:wringer_rules).returns(
+ {
+ "http_5xx" => {
+ "match" => { "http_code" => [500, 502, 503, 504] },
+ "policy" => { "action" => "abort_update", "retry" => true, "cache" => false, "error_code" => "http_server_error" }
+ }
+ }.to_a
+ )
+ response = Struct.new(:code, :body, :uri).new(
+ 500,
+ "Internal Server Error",
+ URI("https://example.com/error")
+ )
+ agent.expects(:get).with("wringer://resolved").returns(response)
+
+ client = Dsl::Support::WringerClient.new(
+ agent: agent,
+ render_js: false,
+ scrape_options: {},
+ use_wringer: ->(*_) { "wringer://resolved" },
+ safe_wringer_call: helper.method(:safe_wringer_call),
+ logger: Rails.logger
+ )
+
+ result = client.fetch(url: "https://example.com/events")
+
+ assert_equal :abort, result[:status]
+ assert_equal "abort_update", result[:body].first
+ assert_equal "http_server_error", result[:wringer][:error_type]
+ end
+
+ test "fetch captures final_url for redirect responses" do
+ agent = mock("agent")
+ helper = ApplicationController.helpers
+ helper.stubs(:wringer_rules).returns(
+ {
+ "redirect_to_listing" => {
+ "match" => { "final_url_patterns" => ["/events$"] },
+ "policy" => { "action" => "abort_update", "retry" => false, "cache" => false, "error_code" => "redirect_to_listing" }
+ }
+ }.to_a
+ )
+ response = Struct.new(:code, :body, :uri).new(
+ 200,
+ "listing page",
+ URI("https://example.com/events")
+ )
+ agent.expects(:get).with("wringer://resolved").returns(response)
+
+ client = Dsl::Support::WringerClient.new(
+ agent: agent,
+ render_js: false,
+ scrape_options: {},
+ use_wringer: ->(*_) { "wringer://resolved" },
+ safe_wringer_call: helper.method(:safe_wringer_call),
+ logger: Rails.logger
+ )
+
+ result = client.fetch(url: "https://example.com/events")
+
+ assert_equal :abort, result[:status]
+ assert_equal "redirect_to_listing", result[:wringer][:error_type]
+ assert_equal "https://example.com/events", result[:wringer][:final_url]
+ end
+
+ test "canonical wringer contract marks received_404 from metadata" do
+ agent = mock("agent")
+ helper = ApplicationController.helpers
+ helper.stubs(:wringer_rules).returns(
+ {
+ "http_404" => {
+ "match" => { "http_code" => 404 },
+ "policy" => { "action" => "abort_update", "retry" => false, "cache" => false, "error_code" => "http_404" }
+ }
+ }.to_a
+ )
+ response = Struct.new(:code, :body, :uri).new(404, "Not Found", URI("https://example.com/missing"))
+ agent.expects(:get).with("wringer://resolved").returns(response)
+
+ client = Dsl::Support::WringerClient.new(
+ agent: agent,
+ render_js: false,
+ scrape_options: {},
+ use_wringer: ->(*_) { "wringer://resolved" },
+ safe_wringer_call: helper.method(:safe_wringer_call),
+ logger: Rails.logger
+ )
+
+ result = client.fetch(url: "https://example.com/events")
+
+ assert_equal true, result[:wringer][:received_404]
+ assert_equal false, result[:wringer][:unreachable]
+ assert_equal false, result[:wringer][:system_error]
+ assert_equal "abort_update", result[:wringer][:policy_action]
+ end
+
+ test "canonical wringer contract marks system_error from metadata" do
+ agent = mock("agent")
+ helper = ApplicationController.helpers
+ helper.stubs(:wringer_rules).returns(
+ {
+ "http_5xx" => {
+ "match" => { "http_code" => [500, 502, 503, 504] },
+ "policy" => { "action" => "abort_update", "retry" => true, "cache" => false, "error_code" => "http_server_error" }
+ }
+ }.to_a
+ )
+ response = Struct.new(:code, :body, :uri).new(500, "Internal Server Error", URI("https://example.com/error"))
+ agent.expects(:get).with("wringer://resolved").returns(response)
+
+ client = Dsl::Support::WringerClient.new(
+ agent: agent,
+ render_js: false,
+ scrape_options: {},
+ use_wringer: ->(*_) { "wringer://resolved" },
+ safe_wringer_call: helper.method(:safe_wringer_call),
+ logger: Rails.logger
+ )
+
+ result = client.fetch(url: "https://example.com/events")
+
+ assert_equal false, result[:wringer][:received_404]
+ assert_equal false, result[:wringer][:unreachable]
+ assert_equal true, result[:wringer][:system_error]
+ assert_equal "abort_update", result[:wringer][:policy_action]
+ end
+
+ test "canonical wringer contract marks unreachable on network abort payload" do
+ payload = [
+ "abort_update",
+ {
+ error_type: "wringer_unreachable",
+ policy: { action: "abort_update", retry: true, cache: false }
+ }
+ ]
+
+ client = Dsl::Support::WringerClient.new(
+ agent: mock("agent"),
+ render_js: false,
+ scrape_options: {},
+ use_wringer: ->(*_) { "wringer://resolved" },
+ safe_wringer_call: ->(&_) { payload },
+ logger: Rails.logger
+ )
+
+ result = client.fetch(url: "https://example.com/events")
+
+ assert_equal true, result[:wringer][:unreachable]
+ assert_equal false, result[:wringer][:received_404]
+ assert_equal false, result[:wringer][:system_error]
+ assert_equal "abort_update", result[:wringer][:policy_action]
+ end
+
+ test "matches Distillator fetch normalization for wringer response shapes" do
+ cases = [
+ {
+ name: "successful string response",
+ response: -> { "ok" },
+ safe_wringer_call: passthrough_safe_wringer_call
+ },
+ {
+ name: "Mechanize-like response with headers and final_url",
+ response: lambda {
+ FakeWringerResponse.new(
+ code: 200,
+ body: "ok",
+ uri: URI("https://example.com/final"),
+ response: { "Content-Type" => "text/html" }
+ )
+ },
+ safe_wringer_call: passthrough_safe_wringer_call
+ },
+ {
+ name: "abort_update response",
+ response: -> { "unused" },
+ safe_wringer_call: control_safe_wringer_call(
+ ["abort_update", { error_type: "system_cloudflare", retry: true, cache: false }]
+ )
+ },
+ {
+ name: "404 response",
+ response: lambda {
+ FakeWringerResponse.new(
+ code: 404,
+ body: "Not Found",
+ uri: URI("https://example.com/missing"),
+ response: { "Content-Type" => "text/html" }
+ )
+ },
+ safe_wringer_call: response_policy_safe_wringer_call(
+ http_code: 404,
+ error_type: "http_404",
+ retry_value: false
+ )
+ },
+ {
+ name: "500 response",
+ response: lambda {
+ FakeWringerResponse.new(
+ code: 500,
+ body: "Internal Server Error",
+ uri: URI("https://example.com/error"),
+ response: { "Content-Type" => "text/html" }
+ )
+ },
+ safe_wringer_call: response_policy_safe_wringer_call(
+ http_code: 500,
+ error_type: "http_server_error",
+ retry_value: true
+ )
+ },
+ {
+ name: "malformed control payload",
+ response: -> { "unused" },
+ safe_wringer_call: control_safe_wringer_call(["abort_update", "broken-payload"])
+ },
+ {
+ name: "unsupported control action",
+ response: -> { "unused" },
+ safe_wringer_call: control_safe_wringer_call(["foo", { reason: "unknown" }])
+ },
+ {
+ name: "redirect_chain extraction",
+ response: lambda {
+ FakeWringerResponse.new(
+ code: 200,
+ body: "redirected",
+ uri: URI("https://example.com/final"),
+ response: { "Content-Type" => "text/html" }
+ )
+ },
+ history: [
+ FakeHistoryEntry.new(uri: URI("https://example.com/start")),
+ FakeHistoryEntry.new(uri: URI("https://example.com/final"))
+ ],
+ safe_wringer_call: passthrough_safe_wringer_call
+ }
+ ]
+
+ cases.each do |example|
+ dsl_agent = FakeWringerAgent.new(
+ response: example[:response].call,
+ history: example[:history] || []
+ )
+ distillator_agent = FakeWringerAgent.new(
+ response: example[:response].call,
+ history: example[:history] || []
+ )
+
+ dsl_result = wringer_client(
+ agent: dsl_agent,
+ safe_wringer_call: example[:safe_wringer_call]
+ ).fetch(url: "https://example.com/events")
+ distillator_result = Distillator::FetchService.fetch_wringer_backed(
+ url: "https://example.com/events",
+ render_js: false,
+ scrape_options: {},
+ agent: distillator_agent,
+ use_wringer: wringer_url_resolver,
+ safe_wringer_call: example[:safe_wringer_call],
+ logger: Rails.logger
+ )
+
+ assert_equal distillator_result, dsl_result, example[:name]
+ end
+ end
+
+ private
+
+ def wringer_client(agent:, safe_wringer_call:)
+ Dsl::Support::WringerClient.new(
+ agent: agent,
+ render_js: false,
+ scrape_options: {},
+ use_wringer: wringer_url_resolver,
+ safe_wringer_call: safe_wringer_call,
+ logger: Rails.logger
+ )
+ end
+
+ def wringer_url_resolver
+ ->(*_) { "wringer://resolved" }
+ end
+
+ def passthrough_safe_wringer_call
+ lambda do |normalize_response: false, &blk|
+ blk.call
+ end
+ end
+
+ def control_safe_wringer_call(payload)
+ lambda do |normalize_response: false, &blk|
+ payload
+ end
+ end
+
+ def response_policy_safe_wringer_call(http_code:, error_type:, retry_value:)
+ lambda do |normalize_response: false, &blk|
+ response = blk.call
+ return response unless response.respond_to?(:code) && response.code.to_i == http_code
+
+ [
+ "abort_update",
+ {
+ error_type: error_type,
+ policy: { action: "abort_update", retry: retry_value, cache: false }
+ }
+ ]
+ end
+ end
+end
diff --git a/test/services/dsl_algorithm_runner_test.rb b/test/services/dsl_algorithm_runner_test.rb
index 84d4042e..8abb07a1 100644
--- a/test/services/dsl_algorithm_runner_test.rb
+++ b/test/services/dsl_algorithm_runner_test.rb
@@ -1,198 +1,979 @@
require "test_helper"
require "webmock/minitest"
+require "ostruct"
class DslAlgorithmRunnerTest < ActiveSupport::TestCase
+ include DslRunnerTestHelper
+
+ class CapturingLogger
+ attr_reader :infos, :warnings
+
+ def initialize
+ @infos = []
+ @warnings = []
+ end
+
+ def info(payload)
+ @infos << payload
+ end
+
+ def warn(payload)
+ @warnings << payload
+ end
+ end
- #
- # WebMock setup
- #
setup do
- # Block all real HTTP unless explicitly stubbed
- stub_request(:get, /.*/).to_return(status: 200, body: "", headers: {})
+ @previous_legacy_wringer_fallback = ENV["DISTILLATOR_LEGACY_WRINGER_FALLBACK"]
+ ENV["DISTILLATOR_LEGACY_WRINGER_FALLBACK"] = nil
+ end
+
+ teardown do
+ ENV["DISTILLATOR_LEGACY_WRINGER_FALLBACK"] = @previous_legacy_wringer_fallback
+ end
+
+ def build_runner(*args, url: nil, scrape_options: {}, tracer: Dsl::Tracing::TraceCollector.new, render_js: false)
+ start_url = url || args.first || "http://example.local"
+ super(url: start_url, scrape_options: scrape_options, tracer: tracer, render_js: render_js)
+ end
+
+ def build_compat_runner(*args, url: nil, scrape_options: {}, tracer: Dsl::Tracing::TraceCollector.new, render_js: false)
+ start_url = url || args.first || "http://example.local"
+ super(url: start_url, scrape_options: scrape_options, tracer: tracer, render_js: render_js)
+ end
+
+ test "manual prefix returns configured literal" do
+ expect_no_fetch_seams
+ runner, = build_runner_with_html
+ result = runner.run("manual=hello")
+
+ assert_equal ["hello"], result
+ assert_no_wringer_requests
+ end
+
+ test "trace uses inherited wringer context for non-fetch steps" do
+ expect_no_fetch_seams
+ runner, tracer = build_runner
+
+ runner.run("manual=hello")
+ step = tracer.to_h.first
+
+ assert_equal true, step.dig(:wringer, :inherited)
+ assert_no_wringer_requests
+ end
+
+ test "trace marks probe as skipped when not executed" do
+ expect_no_fetch_seams
+ runner, tracer = build_runner
+
+ runner.run("manual=hello")
+ step = tracer.to_h.first
+
+ assert_equal true, step.dig(:probe, :skipped)
+ assert_no_wringer_requests
+ end
+
+ test "syntax error in ruby returns abort_update payload instead of raising" do
+ expect_no_fetch_seams
+ runner, = build_runner_with_html
+ result = runner.run("ruby=$array.each {|a| a")
+
+ assert_equal "abort_update", result.first
+ assert_match(/syntax error/i, result.last[:error])
+ assert_no_wringer_requests
+ end
+
+ test "if_xpath short-circuits subsequent steps when no match" do
+ expect_no_fetch_seams
+ runner, = build_runner_with_html(html: "Title ")
+ result = runner.run("if_xpath=//missing; xpath=//h1/text()")
+
+ assert_equal [], result
+ assert_no_wringer_requests
+ end
+
+ test "unless_xpath short-circuits subsequent steps when match exists" do
+ expect_no_fetch_seams
+ runner, = build_runner_with_html(html: "Title ")
+ result = runner.run("unless_xpath=//h1; xpath=//h1/text()")
+
+ assert_equal [], result
+ assert_no_wringer_requests
+ end
+
+ test "if_xpath emits matches but later step replaces result" do
+ expect_no_fetch_seams
+ runner, = build_runner_with_html(html: "T H ")
+ result = runner.run("if_xpath=//title; xpath=//h1/text()")
+
+ assert_equal ["H"], result
+ assert_no_wringer_requests
+ end
+
+ test "ruby step uses eval result rather than stale thread local array" do
+ expect_no_fetch_seams
+ runner, = build_runner_with_html(html: "a
b
")
+ result = runner.run("xpath=//p/text(); ruby=$array.map(&:upcase)")
+
+ assert_equal %w[A B], result
+ assert_no_wringer_requests
+ end
+
+ test "ruby step preserves current array when mutating method returns nil" do
+ expect_no_fetch_seams
+ runner, = build_runner_with_html(html: "a
b
")
+ result = runner.run("xpath=//p/text(); ruby=$array.uniq!")
+
+ assert_equal %w[a b], result
+ assert_no_wringer_requests
+ end
+
+ test "ensure_page fetches through Distillator" do
+ runner, = build_runner("http://example.local/start")
+
+ fetch = distillator_fetch_response(
+ final_url: "http://example.local/start",
+ duration_ms: 7
+ )
+
+ Distillator::FetchCacheStore.expects(:fetch).with do |kwargs|
+ assert_equal "http://example.local/start", kwargs[:uri]
+ assert_equal false, kwargs[:render_js]
+ assert_equal true, kwargs[:include_fragment]
+ assert_equal :use_wringer, kwargs[:use_wringer].name
+ assert_equal :safe_wringer_call, kwargs[:safe_wringer_call].name
+ true
+ end.returns(fetch)
+
+ result = runner.run("xpath=//h1/text()")
+
+ assert_equal ["Title"], result
+ end
+
+ test "api returns parsed json payload as current result" do
+ stub_request(:get, "http://api.example.local/data").to_return(
+ status: 200,
+ body: { "name" => "Jane" }.to_json
+ )
+
+ runner, = build_runner
+ result = runner.run("api='http://api.example.local/data'")
+
+ assert_equal({ "name" => "Jane" }, result)
+ end
+
+ test "sparql fetches graph source through Distillator" do
+ runner, = build_runner("http://example.local/event")
+
+ ttl = <<~TTL
+ @prefix schema: .
+ schema:name "Event Name" .
+ TTL
+ fetch = distillator_fetch_response(
+ body: <<~TTL,
+ @prefix schema: .
+ schema:name "Event Name" .
+ TTL
+ html: ttl,
+ headers: { content_type: "text/turtle" },
+ final_url: "http://example.local/event",
+ duration_ms: 11
+ )
+
+ Distillator::FetchCacheStore.expects(:fetch).with do |kwargs|
+ assert_equal "http://example.local/event", kwargs[:uri]
+ assert_equal false, kwargs[:render_js]
+ assert_equal true, kwargs[:include_fragment]
+ true
+ end.returns(fetch)
+ rows = [OpenStruct.new(answer: OpenStruct.new(value: "Event Name"))]
+ SPARQL.expects(:execute)
+ .with do |query, graph|
+ assert_equal "PREFIX schema: select * where {?s schema:name ?answer}", query
+ assert_instance_of RDF::Graph, graph
+ true
+ end
+ .returns(rows)
+
+ result = runner.run("sparql={?s schema:name ?answer}")
+ assert_equal ["Event Name"], result
+ end
+
+ test "run_dsl internal mode routes page fetches through Distillator fetch cache store" do
+ previous_mode = ENV["DISTILLATOR_FETCH_MODE"]
+ ENV["DISTILLATOR_FETCH_MODE"] = "internal"
+ runner, = build_runner("https://example.com/start")
+
+ fetch = distillator_fetch_response(
+ final_url: "https://example.com/start",
+ duration_ms: 5
+ )
+
+ Distillator::FetchCacheStore.expects(:fetch).with do |kwargs|
+ assert_equal "https://example.com/start", kwargs[:uri]
+ assert_equal false, kwargs[:render_js]
+ assert_equal true, kwargs[:include_fragment]
+ assert_same runner.instance_variable_get(:@agent), kwargs[:agent]
+ true
+ end.returns(fetch)
+
+ result = runner.run("xpath=//h1/text()")
+
+ assert_equal ["Title"], result
+ ensure
+ ENV["DISTILLATOR_FETCH_MODE"] = previous_mode
end
- #
- # Test helper to build runner + tracer
- #
- def build_runner(start_url = "http://example.local")
- tracer = Dsl::DslTraceCollector.new
+ test "crawl cache options route eligible html fetches through Distillator fetch cache" do
+ tracer = Dsl::Tracing::TraceCollector.new
ctx = {
- url: start_url,
+ url: "https://example.com/start",
render_js: false,
- scrape_options: {},
+ scrape_options: { force_scrape_every_hrs: "1" },
+ tracer: tracer
+ }
+ runner = Dsl::Core::AlgorithmRunner.new(ctx)
+ fetch = distillator_fetch_response(
+ final_url: "https://example.com/start",
+ signals: { "network_status" => "ok" },
+ cache_reason: "stale_by_force_scrape_every_hrs",
+ )
+ Distillator::FetchCacheStore.expects(:fetch).with do |kwargs|
+ assert_equal "https://example.com/start", kwargs[:uri]
+ assert_equal false, kwargs[:render_js]
+ assert_equal true, kwargs[:include_fragment]
+ assert_equal "1", kwargs[:force_scrape_every_hrs]
+ assert_equal false, kwargs[:json_post]
+ assert_nil kwargs[:website]
+ assert_nil kwargs[:website_id]
+ assert_same runner.instance_variable_get(:@agent), kwargs[:agent]
+ true
+ end.returns(fetch)
+
+ result = runner.run("xpath=//h1/text()")
+
+ assert_equal ["Title"], result
+ end
+
+ test "native refresh forwards mode cache options and log context into Distillator fetch cache store" do
+ previous_mode = ENV["DISTILLATOR_FETCH_MODE"]
+ ENV["DISTILLATOR_FETCH_MODE"] = "internal"
+ tracer = Dsl::Tracing::TraceCollector.new
+ ctx = {
+ url: "https://example.com/native",
+ render_js: false,
+ scrape_options: {
+ force_scrape_every_hrs: "0",
+ absolute_src: true,
+ log_context: { statement_id: 41, source_id: 42, webpage_id: 43, website_id: 44 }
+ },
+ tracer: tracer
+ }
+ runner = Dsl::Core::AlgorithmRunner.new(ctx)
+ fetch = distillator_fetch_response(
+ final_url: "https://example.com/native",
+ signals: { "network_status" => "ok" },
+ cache_reason: "stale_by_force_scrape_every_hrs",
+ )
+
+ Distillator::FetchCacheStore.expects(:fetch).with do |kwargs|
+ assert_equal "https://example.com/native", kwargs[:uri]
+ assert_nil kwargs[:mode]
+ assert_equal "0", kwargs[:force_scrape_every_hrs]
+ assert_equal true, kwargs[:absolute_src]
+ assert_nil kwargs[:website]
+ assert_equal 44, kwargs[:website_id]
+ assert_equal({ statement_id: 41, source_id: 42, webpage_id: 43, website_id: 44 }, kwargs[:log_context])
+ true
+ end.returns(fetch)
+
+ result = runner.run("xpath=//h1/text()")
+
+ assert_equal ["Title"], result
+ ensure
+ ENV["DISTILLATOR_FETCH_MODE"] = previous_mode
+ end
+
+ test "explicit diagnostic mode is forwarded to Distillator fetch cache store" do
+ tracer = Dsl::Tracing::TraceCollector.new
+ runner = Dsl::Core::AlgorithmRunner.new(
+ url: "https://example.com/diagnostic",
+ render_js: false,
+ scrape_options: {
+ mode: "internal",
+ log_context: { website_id: 44 }
+ },
+ tracer: tracer
+ )
+ fetch = distillator_fetch_response(
+ final_url: "https://example.com/diagnostic",
+ signals: { "network_status" => "ok" }
+ )
+
+ Distillator::FetchCacheStore.expects(:fetch).with do |kwargs|
+ assert_equal :active, kwargs[:mode]
+ assert_equal 44, kwargs[:website_id]
+ true
+ end.returns(fetch)
+
+ assert_equal ["Title"], runner.run("xpath=//h1/text()")
+ end
+
+ test "invalid explicit diagnostic mode is ignored during statement refresh" do
+ tracer = Dsl::Tracing::TraceCollector.new
+ runner = Dsl::Core::AlgorithmRunner.new(
+ url: "https://example.com/invalid-mode",
+ render_js: false,
+ scrape_options: {
+ mode: "bogus",
+ log_context: { website_id: 55 }
+ },
+ tracer: tracer
+ )
+ fetch = distillator_fetch_response(
+ final_url: "https://example.com/invalid-mode",
+ signals: { "network_status" => "ok" }
+ )
+
+ Distillator::FetchCacheStore.expects(:fetch).with do |kwargs|
+ assert_nil kwargs[:mode]
+ assert_equal 55, kwargs[:website_id]
+ true
+ end.returns(fetch)
+
+ assert_equal ["Title"], runner.run("xpath=//h1/text()")
+ end
+
+ test "render_js and json_post options are passed through Distillator fetch cache store" do
+ tracer = Dsl::Tracing::TraceCollector.new
+ ctx = {
+ url: "https://example.com/api",
+ render_js: true,
+ scrape_options: { json_post: true, force_scrape_every_hrs: "24" },
tracer: tracer
}
- [DslAlgorithmRunner.new(ctx), tracer]
+ runner = Dsl::Core::AlgorithmRunner.new(ctx)
+ fetch = distillator_fetch_response(
+ final_url: "https://example.com/api",
+ signals: { "network_status" => "ok" },
+ cache_reason: "stale_by_force_scrape_every_hrs",
+ fetch_path: "legacy"
+ )
+
+ Distillator::FetchCacheStore.expects(:fetch).with do |kwargs|
+ assert_equal "https://example.com/api", kwargs[:uri]
+ assert_equal true, kwargs[:render_js]
+ assert_equal true, kwargs[:include_fragment]
+ assert_equal true, kwargs[:json_post]
+ assert_equal "24", kwargs[:force_scrape_every_hrs]
+ assert_nil kwargs[:website]
+ assert_nil kwargs[:website_id]
+ true
+ end.returns(fetch)
+
+ result = runner.run("xpath=//h1/text()")
+
+ assert_equal ["Title"], result
end
- #
- # Simple prefix tests
- #
+ test "env legacy mode does not override website active rollout during statement refresh" do
+ previous_mode = ENV["DISTILLATOR_FETCH_MODE"]
+ ENV["DISTILLATOR_FETCH_MODE"] = "legacy"
- test "xpath prefix extracts text from HTML" do
- html = "Hello World
"
- stub_request(:get, /wringer.*uri=http.*example.local/)
- .to_return(status: 200, body: html)
+ website = websites(:one)
+ website.update!(distillator_mode: "active")
- runner, = build_runner("http://example.local")
- result = runner.run("xpath=//p/text()")
+ tracer = Dsl::Tracing::TraceCollector.new
+ runner = Dsl::Core::AlgorithmRunner.new(
+ url: "https://example.com/active",
+ render_js: false,
+ scrape_options: {
+ website: website,
+ website_id: website.id,
+ log_context: { website_id: website.id }
+ },
+ tracer: tracer
+ )
+
+ fetch = distillator_fetch_response(
+ final_url: "https://example.com/active",
+ body: "Active ",
+ )
+
+ Distillator::FetchCacheStore.expects(:fetch).with do |kwargs|
+ assert_nil kwargs[:mode], "DSL runner must not convert ENV into explicit mode"
+ assert_equal website, kwargs[:website]
+ assert_equal website.id, kwargs[:website_id]
+ true
+ end.returns(fetch)
+
+ assert_equal ["Active"], runner.run("xpath=//h1/text()")
+ ensure
+ ENV["DISTILLATOR_FETCH_MODE"] = previous_mode
+ end
- assert_equal ["Hello World"], result
+ test "failed distillator cache fetch paths abort before extraction and preserve downstream statement state" do
+ cases = [
+ {
+ name: "normal get transport failure",
+ final_url: "https://example.com/timeout",
+ http_response_code: nil,
+ fetch_path: "native",
+ signals: {
+ "network_status" => "failed",
+ "blocking_issue_key" => "timeout",
+ "content_success" => false,
+ "transport_success" => false
+ },
+ hints: ["timeout"],
+ error_type: "timeout"
+ },
+ {
+ name: "normal get non 2xx",
+ final_url: "https://example.com/server-error",
+ http_response_code: 500,
+ fetch_path: "native",
+ signals: {
+ "network_status" => "ok",
+ "blocking_issue_key" => "http_server_error",
+ "content_success" => false,
+ "transport_success" => false
+ },
+ hints: ["http_server_error"],
+ error_type: "http_server_error"
+ },
+ {
+ name: "rendered failure",
+ final_url: "https://example.com/eventsiframe",
+ http_response_code: 500,
+ fetch_path: "native",
+ signals: {
+ "network_status" => "ok",
+ "renderer" => "legacy_phantomjs",
+ "blocking_issue_key" => "phantomjs_iframe_missing_child_content",
+ "content_success" => false,
+ "transport_success" => false
+ },
+ hints: ["legacy_phantomjs", "phantomjs_iframe_missing_child_content"],
+ error_type: "phantomjs_iframe_missing_child_content"
+ },
+ {
+ name: "post failure",
+ final_url: "https://example.com/api",
+ http_response_code: 500,
+ fetch_path: "native",
+ signals: {
+ "network_status" => "ok",
+ "request_method" => "POST",
+ "blocking_issue_key" => "http_server_error",
+ "content_success" => false,
+ "transport_success" => false
+ },
+ hints: ["json_detected", "http_server_error"],
+ error_type: "http_server_error"
+ },
+ {
+ name: "blocked url",
+ final_url: "http://127.0.0.1/events",
+ http_response_code: nil,
+ fetch_path: "blocked",
+ signals: {
+ "network_status" => "blocked",
+ "native_ineligible_reason" => "blocked_url",
+ "blocking_issue_key" => "blocked_url",
+ "content_success" => false,
+ "transport_success" => false
+ },
+ hints: ["blocked_url", "blocked"],
+ error_type: "blocked_url"
+ },
+ {
+ name: "content failure",
+ final_url: "https://www.ovation.ca/Search/Title/",
+ http_response_code: 200,
+ fetch_path: "native",
+ signals: {
+ "network_status" => "ok",
+ "blocking_issue_key" => "redirect_to_listing",
+ "primary_issue_key" => "redirect_to_listing",
+ "content_success" => false,
+ "transport_success" => true
+ },
+ hints: ["redirect_to_listing"],
+ error_type: "redirect_to_listing"
+ }
+ ]
+
+ cases.each do |test_case|
+ runner, tracer = build_runner(url: "https://example.com/start")
+ Distillator::FetchCacheStore.expects(:fetch).returns(
+ failed_cache_fetch_result(
+ final_url: test_case[:final_url],
+ http_response_code: test_case[:http_response_code],
+ fetch_path: test_case[:fetch_path],
+ signals: test_case[:signals],
+ hints: test_case[:hints]
+ )
+ )
+
+ result = runner.run("xpath=//h1/text();ruby=['SHOULD_NOT_RUN']")
+
+ assert_equal "abort_update", result.first, test_case[:name]
+ assert_equal test_case[:error_type], result.last[:error_type], test_case[:name]
+ assert_equal "distillator_fetch_cache", result.last[:source], test_case[:name]
+ assert_equal "url", result.last[:step], test_case[:name]
+ assert_equal 1, tracer.to_h.size, test_case[:name]
+ assert_equal "xpath", tracer.to_h.first[:type], test_case[:name]
+ end
end
- test "css prefix extracts text from HTML" do
- html = "Foo "
- stub_request(:get, /wringer.*uri=http.*example.local/)
- .to_return(status: 200, body: html)
+ test "thread locals are restored after aborted execution" do
+ Thread.current[:dsl_array] = ["existing"]
+ Thread.current[:dsl_url] = "existing-url"
+ Thread.current[:dsl_json] = { "existing" => true }
- runner, = build_runner("http://example.local")
- result = runner.run("css=.x")
+ runner, = build_runner
+ result = runner.run("ruby=$array.each {|a| a")
- assert_equal ["Foo"], result
+ assert_equal "abort_update", result.first
+ assert_equal ["existing"], Thread.current[:dsl_array]
+ assert_equal "existing-url", Thread.current[:dsl_url]
+ assert_equal({ "existing" => true }, Thread.current[:dsl_json])
+ ensure
+ Thread.current[:dsl_array] = nil
+ Thread.current[:dsl_url] = nil
+ Thread.current[:dsl_json] = nil
end
- #
- # URL changing behavior
- #
+ test "trace event includes wringer status when safe_wringer_call aborts" do
+ runner, tracer = build_runner
+ fetch = distillator_fetch_response(
+ status: :abort,
+ body: ["abort_update", { error_type: "system_cloudflare", error: "Cloudflare blocked", retry: true, cache: false }],
+ headers: {},
+ final_url: "http://example.local/events/1",
+ html: nil,
+ http_response_code: nil,
+ signals: { "error_type" => "system_cloudflare" },
+ duration_ms: 12,
+ fetch_path: "legacy"
+ )
+ Distillator::FetchCacheStore.stubs(:fetch).returns(fetch)
+
+ result = runner.run("url='http://example.local/events/1'")
+ event = tracer.to_h.last
- test "url prefix updates runner url and used in next xpath" do
- html_home = "Link "
- html_page = "Title "
+ assert_equal "abort_update", result.first
+ assert_equal "system_cloudflare", event[:wringer][:error_type]
+ assert_equal true, event[:wringer][:retry]
+ assert_equal false, event[:wringer][:cache]
+ assert_equal({ "error_type" => "system_cloudflare" }, event[:wringer][:signals])
+ assert_equal [], event[:wringer][:hints]
+ end
- stub_request(:get, /wringer.*uri=http.*example.local/)
- .to_return(status: 200, body: html_home)
- stub_request(:get, /wringer.*uri=http.*example.local/)
- .to_return(status: 200, body: html_page)
+ test "trace event wringer status includes diagnostics when safe_wringer_call succeeds" do
+ runner, tracer = build_runner
+ fetch = distillator_fetch_response(
+ final_url: "http://example.local/events/1",
+ signals: { "network_status" => "ok", "content_type" => "html", "redirect_type" => "none", "redirected" => false, "final_url" => "http://example.local/events/1" },
+ duration_ms: 9,
+ )
+ Distillator::FetchCacheStore.stubs(:fetch).returns(fetch)
- runner, = build_runner("http://example.local")
- algo = "xpath=//a/@href; url=$array.first; xpath=//h1/text()"
- result = runner.run(algo)
+ result = runner.run("url='http://example.local/events/1';xpath=//h1/text()")
+ url_step_event = tracer.to_h.first
assert_equal ["Title"], result
+ assert_equal fetch.signals, url_step_event[:wringer][:signals]
+ assert_equal [], url_step_event[:wringer][:hints]
end
- test "renderjs_url uses stubbed page (js rendered)" do
- js_html = "Rendered
"
- # Wringer call will contain escaped URI
- stub_request(:get, /footlight-wringer.*uri=http.*example.local/)
- .to_return(status: 200, body: js_html)
+ test "trace event safely includes partial wringer payload keys only" do
+ runner, tracer = build_runner
+ fetch = distillator_fetch_response(
+ status: :abort,
+ body: ["abort_update", { error_type: "system_cloudflare", error: "Cloudflare blocked" }],
+ headers: {},
+ final_url: "http://example.local/events/1",
+ html: nil,
+ http_response_code: nil,
+ signals: { "error_type" => "system_cloudflare" },
+ duration_ms: 12,
+ fetch_path: "legacy"
+ )
+ Distillator::FetchCacheStore.stubs(:fetch).returns(fetch)
+
+ result = runner.run("url='http://example.local/events/1'")
+ event = tracer.to_h.last
- runner, = build_runner("http://example.local")
- algo = "renderjs_url=$url; xpath=//div[@id='x']/text()"
- result = runner.run(algo)
+ assert_equal "abort_update", result.first
+ assert_equal "system_cloudflare", event[:wringer][:error_type]
+ assert_equal({ "error_type" => "system_cloudflare" }, event[:wringer][:signals])
+ assert_equal [], event[:wringer][:hints]
+ end
+
+ test "trace includes wringer duration from client" do
+ runner, tracer = build_runner
+
+ fetch = distillator_fetch_response(
+ body: "ok",
+ headers: {},
+ final_url: "http://example.local/events/1",
+ duration_ms: 123
+ )
+
+ Distillator::FetchCacheStore.stubs(:fetch).returns(fetch)
+
+ runner.run("url='http://example.local/events/1'")
- assert_equal ["Rendered"], result
+ trace = tracer.to_h
+ step = trace.find { |s| s[:wringer].is_a?(Hash) }
+
+ assert_equal 123, step[:wringer][:duration_ms]
end
- #
- # JSON prefix behavior
- #
+ test "trace includes wringer final_url and redirect_chain" do
+ runner, tracer = build_runner
+
+ fetch = distillator_fetch_response(
+ body: "ok",
+ headers: {},
+ final_url: "https://final.example.com",
+ redirect_chain: ["http://start", "https://final.example.com"],
+ duration_ms: 50
+ )
+
+ Distillator::FetchCacheStore.stubs(:fetch).returns(fetch)
- test "json prefix loads JSON and returns value" do
- json_body = { "foo" => "bar" }.to_json
- stub_request(:get, /footlight-wringer.*uri=http.*example.local/)
- .to_return(status: 200, body: json_body)
+ runner.run("url='http://example.local/events/1'")
- runner, = build_runner("http://example.local")
- result = runner.run("json=$json['foo']")
+ trace = tracer.to_h
+ step = trace.find { |s| s[:wringer].is_a?(Hash) }
- assert_equal "bar", result
+ assert_equal "https://final.example.com", step[:wringer][:final_url]
+ assert_equal ["http://start", "https://final.example.com"], step[:wringer][:redirect_chain]
end
- #
- # Ruby prefix behavior
- #
+ test "probe abort is not converted into a successful probe payload" do
+ runner, = build_runner
+ runner.stubs(:execute_xpath).with("//title").returns(
+ ["abort_update", { error: "Probe fetch failed", error_type: "ProbeAbort", step: "xpath", source: "dsl_runner" }]
+ )
- test "ruby prefix can manipulate array via lambda" do
- html = "a
b
"
- stub_request(:get, /wringer.*uri=http.*example.local/)
- .to_return(status: 200, body: html)
+ probe = runner.send(:build_xpath_probe, "url", "xpath", [], 1)
- runner, = build_runner("http://example.local")
- algo = <<~DSL
- xpath=//p/text();
- ruby=$array.map(&:upcase)
- DSL
+ assert_equal "abort_update", probe.first
+ assert_equal "ProbeAbort", probe.last[:error_type]
+ end
- result = runner.run(algo)
- assert_equal %w[A B], result
+ test "probe abort stops pipeline and prevents downstream steps" do
+ runner, tracer = build_compat_runner
+ runner.stubs(:safe_wringer_call).returns("ok ")
+ runner.stubs(:execute_xpath).with("//h1/text()").returns([])
+ runner.stubs(:execute_xpath).with("//title").returns(
+ ["abort_update", { error: "Probe fetch failed", error_type: "ProbeAbort", step: "xpath", source: "dsl_runner" }]
+ )
+
+ result = runner.run("url='http://example.local/page';xpath=//h1/text();ruby=['SHOULD_NOT_RUN']")
+
+ assert_equal "abort_update", result.first
+ assert_equal "ProbeAbort", result.last[:error_type]
+ assert_equal 2, tracer.to_h.size
+ assert_equal "xpath", tracer.to_h.last[:type]
end
- #
- # Abort and invalid URL tests
- #
+ test "url step resolving to nil aborts and traces abort step without running later steps" do
+ runner, tracer = build_compat_runner
+ runner.stubs(:safe_wringer_call).returns("Fresh ")
+ runner.expects(:execute_xpath).never
- test "abort if renderjs_url has no valid URL in $array" do
- runner, tracer = build_runner("http://example.local")
- result = runner.run("renderjs_url=$array.first")
+ result = runner.run("url='http://example.local/page';url=nil;xpath=//h1/text()")
assert_equal "abort_update", result.first
- assert_match(/Invalid URL/, result.last[:error])
+ assert_equal "InvalidURL", result.last[:error_type]
+ assert_equal "url", result.last[:step]
+ assert_match(/invalid url/i, result.last[:error].to_s)
+
+ events = tracer.to_h
+ assert_equal 2, events.size
+ assert_equal "url", events.second[:type]
+ assert_equal "Hash", events.second[:error_class]
+ assert_match(/InvalidURL/, events.second[:error_message].to_s)
+ assert_match(/step=url/, events.second[:error_message].to_s)
+ end
- found = tracer.to_h[:events].any? do |evt|
- evt.is_a?(Hash) && evt[:error].to_s.include?("Invalid URL")
- end
- assert found
+ test "non trace runner aborts on nil resolved url and does not reuse previous page state" do
+ ctx = {
+ url: "http://example.local",
+ render_js: false,
+ scrape_options: { wringer_compatibility: true },
+ tracer: Dsl::Tracing::NullTracer.new
+ }
+ runner = Dsl::Core::AlgorithmRunner.new(ctx)
+ runner.stubs(:safe_wringer_call).returns("Fresh ")
+ runner.expects(:execute_xpath).never
+
+ result = runner.run("url='http://example.local/page';url=nil;xpath=//h1/text()")
+
+ assert_equal "abort_update", result.first
+ assert_equal "InvalidURL", result.last[:error_type]
+ assert_equal "url", result.last[:step]
+ assert_equal "dsl_runner", result.last[:source]
end
- test "abort if url prefix gets invalid string" do
- runner, tracer = build_runner("http://example.local")
- result = runner.run("url=$array.first")
+ test "ensure_page abort stops pipeline without raising and prevents downstream ruby" do
+ runner, tracer = build_compat_runner
+ runner.stubs(:safe_wringer_call).returns(["abort_update", { error: "Wringer unreachable", error_type: "SocketError" }])
+
+ result = runner.run("xpath=//h1/text();ruby=$array.map(&:upcase)")
assert_equal "abort_update", result.first
- assert_match(/Invalid URL/, result.last[:error])
+ assert_equal "SocketError", result.last[:error_type]
+ assert_equal "xpath", result.last[:step]
+ assert_equal "dsl_runner", result.last[:source]
- found = tracer.to_h[:events].any? do |evt|
- evt.is_a?(Hash) && evt[:error].to_s.include?("Invalid URL")
- end
- assert found
+ events = tracer.to_h
+ assert_equal 1, events.size
+ assert_equal "xpath", events.first[:type]
end
- #
- # if_xpath / unless_xpath behavior
- #
-
- test "if_xpath returns nodes when match present" do
- html = "- OK
"
- stub_request(:get, /wringer.*uri=http.*example.local/)
- .to_return(status: 200, body: html)
+ test "wringer abort source is preserved when upstream provides it" do
+ runner, = build_compat_runner
+ runner.stubs(:safe_wringer_call).returns(
+ ["abort_update", { error: "Wringer unreachable", error_type: "SocketError", source: "wringer" }]
+ )
- runner, = build_runner("http://example.local")
- result = runner.run("if_xpath=//item; xpath=//item/text()")
+ result = runner.run("xpath=//h1/text()")
- assert_equal ["OK"], result
+ assert_equal "abort_update", result.first
+ assert_equal "wringer", result.last[:source]
+ assert_equal "xpath", result.last[:step]
end
- test "unless_xpath breaks when expression matches" do
- html = "Y "
- stub_request(:get, /wringer.*uri=http.*example.local/)
- .to_return(status: 200, body: html)
+ test "resolve_url_only nil URL aborts in api step without silent continuation" do
+ runner, tracer = build_compat_runner
- runner, = build_runner("http://example.local")
- result = runner.run("unless_xpath=//h1; xpath=//h1/text()")
+ result = runner.run("api=nil;ruby=['SHOULD_NOT_RUN']")
- assert_empty result
+ assert_equal "abort_update", result.first
+ assert_equal "InvalidURL", result.last[:error_type]
+ assert_equal "api", result.last[:step]
+ assert_equal "dsl_runner", result.last[:source]
+
+ events = tracer.to_h
+ assert_equal 1, events.size
+ assert_equal "api", events.first[:type]
end
- #
- # time_zone prefix
- #
+ test "trace and non-trace runs return identical abort payload for invalid URL" do
+ trace_runner, = build_runner
+ trace_result = trace_runner.run("api=nil")
- test "time_zone prefix returns time zone array" do
- runner, = build_runner("http://example.local")
- result = runner.run("time_zone=UTC")
+ non_trace_runner = Dsl::Core::AlgorithmRunner.new(
+ url: "http://example.local",
+ render_js: false,
+ scrape_options: {},
+ tracer: Dsl::Tracing::NullTracer.new
+ )
+ non_trace_result = non_trace_runner.run("api=nil")
- assert_equal ["time_zone: UTC"], result
+ assert_equal trace_result, non_trace_result
+ assert_equal "abort_update", trace_result.first
+ assert_equal "dsl_runner", trace_result.last[:source]
end
- #
- # Sparql prefix should abort when no RDF available
- #
+ test "trace events always include wringer signals and hints keys" do
+ runner, tracer = build_compat_runner
+ runner.run("manual=hello")
+
+ wringer = tracer.to_h.first[:wringer]
+ assert wringer.key?(:signals)
+ assert wringer.key?(:hints)
+ assert_equal({}, wringer[:signals])
+ assert_equal [], wringer[:hints]
+ end
- test "sparql prefix returns abort if graph not present" do
- runner, = build_runner("http://example.local")
- result = runner.run("sparql={?s ?p ?o}")
+ test "all abort payloads include error error_type and source" do
+ runner, = build_runner
+ result = runner.run("api=nil")
assert_equal "abort_update", result.first
+ assert result.last[:error].is_a?(String)
+ assert result.last[:error_type].is_a?(String)
+ assert result.last[:source].is_a?(String)
+ end
+
+ test "runner uses single abort_update contract without tuple wrappers" do
+ source = File.read(Rails.root.join("app/services/dsl/core/algorithm_runner.rb"))
+
+ refute_match(/def\s+ok\(/, source)
+ refute_match(/def\s+abort\(/, source)
+ refute_match(/\[:abort,/, source)
+ end
+
+ test "xpath probe runs only when xpath is empty after url step" do
+ expect_no_fetch_seams
+ runner, tracer = build_runner
+ runner.stubs(:resolve_and_fetch_url).returns("body
")
+ runner.expects(:execute_xpath).with("//h1/text()").returns([])
+ runner.expects(:execute_xpath).with("//title").returns(["Probe Title"])
+
+ result = runner.run("url='http://example.local/events/1';xpath=//h1/text()")
+
+ assert_equal [], result
+
+ url_event = tracer.to_h.first
+ xpath_event = tracer.to_h.second
+
+ assert_equal true, url_event.dig(:probe, :skipped)
+ assert_equal "//title", xpath_event.dig(:probe, :result, :xpath)
+ assert_equal ["Probe Title"], xpath_event.dig(:probe, :result, :output)
+ assert_no_wringer_requests
+ end
+
+ test "xpath probe does not run for empty xpath when previous step is not url" do
+ expect_no_fetch_seams
+ runner, tracer = build_runner
+ runner.expects(:execute_xpath).with("//h1/text()").returns([])
+ runner.expects(:execute_xpath).with("//title").never
+
+ result = runner.run("xpath=//h1/text()")
+
+ assert_equal [], result
+ assert_equal true, tracer.to_h.first.dig(:probe, :skipped)
+ assert_no_wringer_requests
+ end
+
+ test "xpath probe is attached only to first empty xpath after url" do
+ expect_no_fetch_seams
+ runner, tracer = build_runner
+ runner.stubs(:resolve_and_fetch_url).returns("body
")
+ runner.expects(:execute_xpath).with("//h1/text()").returns([])
+ runner.expects(:execute_xpath).with("//title").returns(["Probe Title"])
+ runner.expects(:execute_xpath).with("//h2/text()").returns([])
+
+ result = runner.run("url='http://example.local/events/1';xpath=//h1/text();xpath=//h2/text()")
+
+ assert_equal [], result
+
+ events = tracer.to_h
+ assert_equal true, events.first.dig(:probe, :skipped)
+ assert_equal "//title", events.second.dig(:probe, :result, :xpath)
+ assert_equal true, events.third.dig(:probe, :skipped)
+ assert_no_wringer_requests
+ end
+
+ test "xpath probe triggers for all blank outputs after url step" do
+ [[], nil, "", " "].each do |blank_output|
+ runner, = build_runner
+ runner.stubs(:execute_xpath).returns(["Probe Title"])
+
+ probe = runner.send(:build_xpath_probe, "url", "xpath", blank_output, 1)
+
+ assert_equal "//title", probe[:xpath]
+ assert_equal ["Probe Title"], probe[:output]
+ end
+ end
+
+ test "xpath probe structure is normalized" do
+ runner, = build_runner
+ runner.stubs(:execute_xpath).returns([nil, "Alpha", :beta, "Gamma", "Delta"])
+
+ probe = runner.send(:build_xpath_probe, "url", "xpath", [], 1)
+
+ assert_equal "ok", probe[:status]
+ assert_equal "//title", probe[:xpath]
+ assert_equal ["Alpha", "beta", "Gamma"], probe[:output]
+ assert_operator probe[:output].size, :<=, 3
+ assert probe[:output].all? { |entry| entry.is_a?(String) }
+ end
+
+ test "xpath probe exception is explicit error and never reported as ok" do
+ runner, = build_runner
+ runner.stubs(:execute_xpath).with("//title").raises(StandardError, "probe exploded")
+
+ probe = runner.send(:build_xpath_probe, "url", "xpath", [], 1)
+
+ assert_equal "error", probe[:status]
+ assert_equal true, probe[:exception]
+ assert_equal "//title", probe[:xpath]
+ assert_equal [], probe[:output]
+ end
+
+ test "abort_update and normalize_abort_result share the same core payload shape" do
+ runner, = build_runner
+
+ direct = runner.send(
+ :abort_update,
+ error: "Invalid URL resolved from nil",
+ error_type: "InvalidURL",
+ step: "url",
+ source: "dsl_runner"
+ )
+ normalized = runner.send(
+ :normalize_abort_result,
+ ["abort_update", { error: "Invalid URL resolved from nil", error_type: "InvalidURL", step: "url", source: "dsl_runner", retry: true }],
+ step: "url"
+ )
+
+ assert_equal "abort_update", direct.first
+ assert_equal "abort_update", normalized.first
+ assert_equal direct.last.slice(:error, :error_type, :step, :source), normalized.last.slice(:error, :error_type, :step, :source)
+ assert_equal true, normalized.last[:retry]
+ end
+
+ test "xpath probe does not run for xpath to xpath empty chain" do
+ expect_no_fetch_seams
+ runner, tracer = build_runner
+ runner.expects(:execute_xpath).with("//h1/text()").returns([])
+ runner.expects(:execute_xpath).with("//h2/text()").returns([])
+ runner.expects(:execute_xpath).with("//title").never
+
+ result = runner.run("xpath=//h1/text();xpath=//h2/text()")
+
+ assert_equal [], result
+ assert_equal true, tracer.to_h.first.dig(:probe, :skipped)
+ assert_equal true, tracer.to_h.second.dig(:probe, :skipped)
+ assert_no_wringer_requests
+ end
+
+ test "xpath probe stays trace-only and does not change pipeline output" do
+ expect_no_fetch_seams
+ runner, tracer = build_runner
+ runner.stubs(:resolve_and_fetch_url).returns("body
")
+ runner.expects(:execute_xpath).with("//h1/text()").returns([])
+ runner.expects(:execute_xpath).with("//title").returns(["SHOULD_NOT_REPLACE_OUTPUT"])
+
+ result = runner.run("url='http://example.local/events/1';xpath=//h1/text()")
+ probe_output = tracer.to_h.second.dig(:probe, :result, :output)
+
+ assert_equal [], result
+ assert_equal ["SHOULD_NOT_REPLACE_OUTPUT"], probe_output
+ refute_equal probe_output, result
+ assert_no_wringer_requests
+ end
+
+ def failed_cache_fetch_result(final_url:, http_response_code:, fetch_path:, signals:, hints:)
+ OpenStruct.new(
+ status: :ok,
+ body: "failed replacement",
+ html: nil,
+ headers: { content_type: "text/html" },
+ final_url: final_url,
+ redirect_chain: [final_url],
+ http_response_code: http_response_code,
+ signals: signals,
+ hints: hints,
+ duration_ms: 0,
+ cache_hit: false,
+ cache_write: true,
+ cache_reason: "force_scrape",
+ uri_key: CGI.escape(final_url),
+ normalized_url: final_url,
+ fetch_path: fetch_path
+ ).tap do |fetch|
+ fetch.define_singleton_method(:content_success?) { false }
+ fetch.define_singleton_method(:cache_policy) { false }
+ fetch.define_singleton_method(:retry_policy) { false }
+ end
end
-end
\ No newline at end of file
+end
diff --git a/test/services/dsl_content_parser_test.rb b/test/services/dsl_content_parser_test.rb
index e3881e76..810a8be6 100644
--- a/test/services/dsl_content_parser_test.rb
+++ b/test/services/dsl_content_parser_test.rb
@@ -4,7 +4,7 @@ class DslContentParserTest < ActiveSupport::TestCase
# === Set up a parser from raw HTML or text ===
def parser_for_html(html)
- Dsl::DslContentParser.new(html: html)
+ Dsl::Parsing::ContentParser.new(html: html)
end
def test_xpath_extracts_text
@@ -25,7 +25,7 @@ def test_json_prefix_parses_json_value
json_hash = { "name" => "value", "nested" => { "k" => "v" } }
json_str = json_hash.to_json
- parser = Dsl::DslContentParser.new(html: json_str)
+ parser = Dsl::Parsing::ContentParser.new(html: json_str)
result = parser.parse_step("json", "$json['nested']['k']")
assert_equal "v", result
@@ -82,7 +82,7 @@ def test_xpath_sanitize_removes_unwanted_tags
def test_parse_json_raises_on_invalid_json
invalid_json = "not a json string"
- parser = Dsl::DslContentParser.new(html: invalid_json)
+ parser = Dsl::Parsing::ContentParser.new(html: invalid_json)
assert_raises(JSON::ParserError) do
parser.parse_step("json", "$json['foo']")
diff --git a/test/services/dsl_contract_test.rb b/test/services/dsl_contract_test.rb
new file mode 100644
index 00000000..b4f68011
--- /dev/null
+++ b/test/services/dsl_contract_test.rb
@@ -0,0 +1,138 @@
+require "test_helper"
+require "webmock/minitest"
+
+class DslContractTest < ActiveSupport::TestCase
+ include DslRunnerTestHelper
+
+ test "if_xpath halts when no match" do
+ expect_no_fetch_seams
+ runner, = build_runner_with_html(html: "")
+
+ result = runner.run("if_xpath=//title; xpath=//h1")
+
+ assert_empty result
+ assert_no_wringer_requests
+ end
+
+ test "if_xpath continues when match exists" do
+ expect_no_fetch_seams
+ runner, = build_runner_with_html(html: "T H ")
+
+ result = runner.run("if_xpath=//title; xpath=//h1")
+
+ assert_equal ["H"], result
+ assert_no_wringer_requests
+ end
+
+ test "unless_xpath halts when match exists" do
+ expect_no_fetch_seams
+ runner, = build_runner_with_html(html: "T H ")
+
+ result = runner.run("unless_xpath=//title; xpath=//h1")
+
+ assert_empty result
+ assert_no_wringer_requests
+ end
+
+ test "unless_xpath continues when no match" do
+ expect_no_fetch_seams
+ runner, = build_runner_with_html(html: "H ")
+
+ result = runner.run("unless_xpath=//title; xpath=//h1")
+
+ assert_equal ["H"], result
+ assert_no_wringer_requests
+ end
+
+ test "xpath replaces previous results" do
+ expect_no_fetch_seams
+ runner, = build_runner_with_html(html: "A
H ")
+
+ result = runner.run("xpath=//p/text(); xpath=//h1/text()")
+
+ assert_equal ["H"], result
+ assert_no_wringer_requests
+ end
+
+ test "ruby replaces previous results" do
+ expect_no_fetch_seams
+ runner, = build_runner_with_html(html: "a
b
")
+
+ result = runner.run("xpath=//p/text(); ruby=$array.map(&:upcase)")
+
+ assert_equal %w[A B], result
+ assert_no_wringer_requests
+ end
+
+ test "json replaces previous results" do
+ expect_no_fetch_seams
+ runner, = build_runner_with_text(text: '{"name":"test"}')
+
+ result = runner.run("json=$json['name']")
+
+ assert_equal "test", result
+ assert_no_wringer_requests
+ end
+
+ test "url step does not change results" do
+ client = mock("wringer_client")
+ client.expects(:fetch).with(
+ url: "http://example.com",
+ render_js: false,
+ scrape_options: { wringer_compatibility: true }
+ ).returns(
+ status: :ok,
+ body: "",
+ headers: {},
+ final_url: "http://example.com",
+ redirect_chain: [],
+ wringer: { signals: {}, hints: [] },
+ duration_ms: 0
+ )
+ Dsl::Support::WringerClient.expects(:new).returns(client)
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ runner, = build_compat_runner(url: "http://example.com", tracer: Dsl::Tracing::NullTracer.new)
+
+ result = runner.run("url='http://example.com'; xpath=//h1")
+
+ assert_empty result
+ assert_no_wringer_requests
+ end
+
+ test "manual returns constant value" do
+ expect_no_fetch_seams
+ runner, = build_runner_with_html
+
+ result = runner.run("manual=Hello")
+
+ assert_equal ["Hello"], result
+ assert_no_wringer_requests
+ end
+
+ test "invalid ruby returns abort_update" do
+ expect_no_fetch_seams
+ runner, = build_runner_with_html
+
+ result = runner.run("ruby=invalid ruby(")
+
+ assert_equal "abort_update", result.first
+ assert result.last[:error]
+ assert_no_wringer_requests
+ end
+
+ test "no step accumulates results implicitly" do
+ expect_no_fetch_seams
+ runner, = build_runner_with_html(html: "a
b ")
+
+ result = runner.run("xpath=//p/text(); xpath=//h1/text()")
+
+ assert_not_includes result, "a"
+ assert_equal ["b"], result
+ assert_no_wringer_requests
+ end
+
+ test "real world ticket extraction pipeline preserves semantics" do
+ skip "Known issue sentinel: extraction drift (kept for regression visibility)"
+ end
+end
diff --git a/test/services/export_artsdata_integration_test.rb b/test/services/export_artsdata_integration_test.rb
new file mode 100644
index 00000000..e22c202e
--- /dev/null
+++ b/test/services/export_artsdata_integration_test.rb
@@ -0,0 +1,189 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require "json"
+require "fileutils"
+require_relative "../support/jsonld_comparator"
+
+class ExportArtsdataIntegrationTest < ActiveSupport::TestCase
+ include JsonldComparator
+
+ BASELINE_DIR = Rails.root.join("data/migration_baseline")
+ SOURCE_FILE = Rails.root.join("test/services/export_artsdata_integration_test.rb")
+
+ #
+ # --- HELPERS ---
+ #
+
+ def export_debug?
+ ENV["EXPORT_DEBUG"].present?
+ end
+
+ def debug_export(message)
+ return unless export_debug?
+
+ puts("[EXPORT_DEBUG] #{message}")
+ end
+
+ def assert_publishable_present!(seedurl:, publishable_events:, source:)
+ debug_export("source=#{source} seedurl=#{seedurl} publishable_events_count=#{publishable_events.size}")
+ assert_operator(
+ publishable_events.size,
+ :>,
+ 0,
+ "publishable_events empty before export (source=#{source}, seedurl=#{seedurl})"
+ )
+ end
+
+ def run_export(seedurl:)
+ # Calls the same logic as your rake task
+ controller = GraphsController.new
+ controller.params = ActionController::Parameters.new(seedurl: seedurl)
+ controller.website
+
+ publishable_events = controller.instance_variable_get(:@publishable) || []
+ assert_publishable_present!(seedurl: seedurl, publishable_events: publishable_events, source: "graphs_controller.website")
+
+ controller.instance_variable_get(:@dump)
+ end
+
+ def run_export_old(seedurl:)
+ publishable = EventsController.new.publishable_events(seedurl)
+ assert_publishable_present!(seedurl: seedurl, publishable_events: publishable, source: "dump_events_old")
+ JsonldGenerator.dump_events_old(publishable)
+ end
+
+ def run_export_new(seedurl:)
+ publishable = EventsController.new.publishable_events(seedurl)
+ assert_publishable_present!(seedurl: seedurl, publishable_events: publishable, source: "dump_events")
+ JsonldGenerator.dump_events(publishable)
+ end
+
+ def export_cases
+ source = File.read(SOURCE_FILE)
+ source
+ .scan(/with_replay\("([^"]+)"\)\s+do\s+output\s*=\s*run_export\(seedurl:\s*"([^"]+)"\)/m)
+ .map { |replay, seedurl| { seedurl: seedurl, replay: replay } }
+ .uniq
+ end
+
+ def write_diff(seedurl:, old_output:, new_output:)
+ output_dir = Rails.root.join("tmp")
+ FileUtils.mkdir_p(output_dir)
+
+ safe_seedurl = seedurl.downcase.gsub(/[^a-z0-9]+/, "_").gsub(/\A_+|_+\z/, "")
+ safe_seedurl = "site" if safe_seedurl.blank?
+
+ path = output_dir.join("diff_#{safe_seedurl}.json")
+ payload = {
+ seedurl: seedurl,
+ old: JSON.parse(old_output),
+ new: JSON.parse(new_output)
+ }
+ File.write(path, JSON.pretty_generate(payload))
+ path
+ end
+
+ def load_baseline(file)
+ path = BASELINE_DIR.join(file)
+ raise "Missing baseline file: #{file}" unless File.exist?(path)
+
+ JSON.parse(File.read(path))
+ end
+
+ def with_replay(site)
+ old_replay = ENV["REPLAY_FETCH"]
+ old_site = ENV["FETCH_SITE"]
+
+ ENV["REPLAY_FETCH"] = "true"
+ ENV["FETCH_SITE"] = site
+
+ yield
+ ensure
+ ENV["REPLAY_FETCH"] = old_replay
+ ENV["FETCH_SITE"] = old_site
+ end
+
+ #
+ # --- TESTS ---
+ #
+
+ # test "culture3r matches baseline" do
+ # with_replay("culture3r_com") do
+ # output = run_export(seedurl: "culture3r-com")
+ # baseline = load_baseline("culture3r_com.jsonld")
+
+ # assert_jsonld_equal baseline, output
+ # end
+ # end
+
+ # # Uncomment progressively once first test is stable
+
+ # test "co_motion matches baseline" do
+ # with_replay("co_motion_ca") do
+ # output = run_export(seedurl: "co-motion-ca")
+ # baseline = load_baseline("co_motion_ca.jsonld")
+
+ # assert_jsonld_equal baseline, output
+ # end
+ # end
+
+ # test "culturemauricie matches baseline" do
+ # with_replay("culturemauricie_lepointdevente_com") do
+ # output = run_export(seedurl: "culturemauricie_lepointdevente-com")
+ # baseline = load_baseline("culturemauricie_lepointdevente_com.jsonld")
+
+ # assert_jsonld_equal baseline, output
+ # end
+ # end
+
+ # test "gatineau matches baseline" do
+ # with_replay("gatineau_cloud") do
+ # output = run_export(seedurl: "gatineau-cloud")
+ # baseline = load_baseline("gatineau_cloud.jsonld")
+
+ # assert_jsonld_equal baseline, output
+ # end
+ # end
+
+ # test "maisondelaculture matches baseline" do
+ # with_replay("maisondelaculture_ca") do
+ # output = run_export(seedurl: "maisondelaculture-ca")
+ # baseline = load_baseline("maisondelaculture_ca.jsonld")
+
+ # assert_jsonld_equal baseline, output
+ # end
+ # end
+
+ # test "ptitbonheur matches baseline" do
+ # with_replay("ptitbonheur_org") do
+ # output = run_export(seedurl: "ptitbonheur-org")
+ # baseline = load_baseline("ptitbonheur_org.jsonld")
+
+ # assert_jsonld_equal baseline, output
+ # end
+ # end
+
+ # test "optimized dump matches legacy dump for all configured replay cases" do
+ # cases = export_cases
+ # assert_operator cases.size, :>, 0
+
+ # cases.each do |test_case|
+ # with_replay(test_case[:replay]) do
+ # old_output = run_export_old(seedurl: test_case[:seedurl])
+ # new_output = run_export_new(seedurl: test_case[:seedurl])
+
+ # begin
+ # assert_jsonld_equal JSON.parse(old_output), JSON.parse(new_output)
+ # rescue Minitest::Assertion => e
+ # diff_path = write_diff(
+ # seedurl: test_case[:seedurl],
+ # old_output: old_output,
+ # new_output: new_output
+ # )
+ # raise Minitest::Assertion, "#{e.message}\nDiff written: #{diff_path}"
+ # end
+ # end
+ # end
+ # end
+end
diff --git a/test/services/export_artsdata_service_test.rb b/test/services/export_artsdata_service_test.rb
new file mode 100644
index 00000000..136ce778
--- /dev/null
+++ b/test/services/export_artsdata_service_test.rb
@@ -0,0 +1,12 @@
+require "test_helper"
+
+class ExportArtsdataServiceTest < ActiveSupport::TestCase
+ test "matches publishable events output from events controller logic" do
+ seedurl = websites(:one).seedurl
+
+ expected = JsonldGenerator.dump_events(EventsController.new.publishable_events(seedurl))
+ actual = ExportArtsdataService.call(seedurl: seedurl)
+
+ assert_equal expected, actual
+ end
+end
diff --git a/test/services/export_artsdata_task_test.rb b/test/services/export_artsdata_task_test.rb
new file mode 100644
index 00000000..6ad2ff1f
--- /dev/null
+++ b/test/services/export_artsdata_task_test.rb
@@ -0,0 +1,44 @@
+require "test_helper"
+require "rake"
+
+# class ExportArtsdataTaskTest < ActiveSupport::TestCase
+# setup do
+# Rails.application.load_tasks unless Rake::Task.task_defined?("export:artsdata")
+# @task = Rake::Task["export:artsdata"]
+# @output_dir = Rails.root.join("data", "migration_baseline")
+# @output_path = @output_dir.join("example_org.jsonld")
+# FileUtils.rm_f(@output_path)
+# ENV["seedurl"] = nil
+# ENV["SEEDURL"] = nil
+# end
+
+# teardown do
+# @task.reenable
+# FileUtils.rm_f(@output_path)
+# ENV["seedurl"] = nil
+# ENV["SEEDURL"] = nil
+# end
+
+# test "exports raw jsonld to deterministic baseline path" do
+# seedurl = "example.org"
+# jsonld = '[{"@id":"http://kg.footlight.io/resource/event-1"}]'
+
+# ExportArtsdataService.expects(:call).with(seedurl: seedurl).twice.returns(jsonld)
+
+# ENV["seedurl"] = seedurl
+# @task.invoke
+# first = File.read(@output_path)
+
+# @task.reenable
+# @task.invoke
+# second = File.read(@output_path)
+
+# assert_equal jsonld, first
+# assert_equal first, second
+# end
+
+# test "raises when seedurl is missing" do
+# error = assert_raises(ArgumentError) { @task.invoke }
+# assert_match("Missing seedurl", error.message)
+# end
+# end
diff --git a/test/services/export_graph_to_databus_test.rb b/test/services/export_graph_to_databus_test.rb
index 3e38f314..bd73dbe5 100644
--- a/test/services/export_graph_to_databus_test.rb
+++ b/test/services/export_graph_to_databus_test.rb
@@ -3,7 +3,14 @@
class ExportGraphToDatabusTest < ActiveSupport::TestCase
test "check schedule" do
+ BatchJobsController.any_instance.stubs(:refresh_upcoming_events_jobs)
+ BatchJobsController.any_instance.stubs(:check_for_new_webpages_jobs)
+
+ job_proxy = mock("export_to_artsdata_job_proxy")
+ job_proxy.stubs(:perform_later)
+ ExportToArtsdataJob.stubs(:set).returns(job_proxy)
+
ExportGraphToDatabus.check_schedule('http://localhost:3000')
end
-end
\ No newline at end of file
+end
diff --git a/test/services/jsonld_comparator_test.rb b/test/services/jsonld_comparator_test.rb
new file mode 100644
index 00000000..2223bffc
--- /dev/null
+++ b/test/services/jsonld_comparator_test.rb
@@ -0,0 +1,138 @@
+require "test_helper"
+require_relative "../support/jsonld_comparator"
+
+class JsonldComparatorTest < ActiveSupport::TestCase
+ include JsonldComparator
+
+ test "canonical_jsonld sorts hash keys recursively" do
+ value = {
+ "z" => { "b" => 2, "a" => 1 },
+ "a" => 1
+ }
+
+ canonical = canonical_jsonld(value)
+
+ assert_equal ["a", "z"], canonical.keys
+ assert_equal ["a", "b"], canonical["z"].keys
+ end
+
+ test "canonical_jsonld strips only explicit volatile fields" do
+ value = {
+ "created_at" => "2026-04-24T00:00:00Z",
+ "generated_at" => "2026-04-24T00:00:00Z",
+ "name" => "event"
+ }
+
+ canonical = canonical_jsonld(value)
+
+ assert_equal({ "name" => "event" }, canonical)
+ end
+
+ test "only explicitly defined volatile keys are stripped" do
+ value = {
+ "generated_at" => "time",
+ "@id" => "SHOULD STAY"
+ }
+
+ canonical = canonical_jsonld(value)
+
+ assert canonical.key?("@id"), "@id must not be stripped"
+ end
+
+ test "assert_jsonld_equal reports precise mismatch path" do
+ expected = { "events" => [{ "location" => { "name" => "Old Hall" } }] }
+ actual = { "events" => [{ "location" => { "name" => "New Hall" } }] }
+
+ error = assert_raises(Minitest::Assertion) do
+ assert_jsonld_equal(expected, actual)
+ end
+
+ assert_match("events[0].location.name", error.message)
+ assert_match("\"Old Hall\"", error.message)
+ assert_match("\"New Hall\"", error.message)
+ end
+
+ test "comparator does not normalize symbol vs string" do
+ expected = { status: :ok }
+ actual = { status: "ok" }
+
+ error = assert_raises(Minitest::Assertion) do
+ assert_jsonld_equal(expected, actual)
+ end
+
+ assert_match("status", error.message)
+ assert_match(":ok", error.message)
+ assert_match("\"ok\"", error.message)
+ end
+
+ test "comparator does not hide missing keys" do
+ expected = { a: 1, b: 2 }
+ actual = { a: 1 }
+
+ error = assert_raises(Minitest::Assertion) do
+ assert_jsonld_equal(expected, actual)
+ end
+
+ assert_match("b", error.message)
+ assert_match("missing key", error.message)
+ end
+
+ test "comparator does not ignore extra keys" do
+ expected = { a: 1 }
+ actual = { a: 1, b: 2 }
+
+ error = assert_raises(Minitest::Assertion) do
+ assert_jsonld_equal(expected, actual)
+ end
+
+ assert_match("b", error.message)
+ assert_match("unexpected key", error.message)
+ end
+
+ test "comparator is strict about nil vs missing" do
+ expected = { a: nil }
+ actual = {}
+
+ error = assert_raises(Minitest::Assertion) do
+ assert_jsonld_equal(expected, actual)
+ end
+
+ assert_match("a", error.message)
+ assert_match("missing key", error.message)
+ end
+
+ test "canonical_jsonld sorts only explicitly unordered arrays" do
+ expected = [{ "@id" => "b" }, { "@id" => "a" }]
+ actual = [{ "@id" => "a" }, { "@id" => "b" }]
+
+ assert_jsonld_equal(expected, actual)
+ end
+
+ test "array sorting uses canonical structure, not raw JSON" do
+ expected = [
+ { "b" => 1, "a" => 2 },
+ { "a" => 2, "b" => 1 }
+ ]
+
+ assert_jsonld_equal(expected, expected.reverse)
+ end
+
+ test "unordered array with non-comparable elements raises error" do
+ value = [1, { "a" => 1 }]
+
+ assert_raises(ArgumentError) do
+ canonical_jsonld(value)
+ end
+ end
+
+ test "comparator does not reorder nested arrays by default" do
+ expected = { "events" => ["b", "a"] }
+ actual = { "events" => ["a", "b"] }
+
+ error = assert_raises(Minitest::Assertion) do
+ assert_jsonld_equal(expected, actual)
+ end
+
+ assert_match("events[0]", error.message)
+ end
+end
diff --git a/test/services/local_graph_generator_test.rb b/test/services/local_graph_generator_test.rb
index 8b1e02fc..ba9e5b4a 100644
--- a/test/services/local_graph_generator_test.rb
+++ b/test/services/local_graph_generator_test.rb
@@ -4,13 +4,13 @@ class LocalGraphGeneratorTest < ActiveSupport::TestCase
test "generate a single graph of Places" do
graph = LocalGraphGenerator.graph_class('Place')
# puts graph.dump(:turtle)
- expected = 6
+ expected = 8
actual = graph.count
assert_equal expected, actual
end
test "generate all local graphs" do
- expected = 12
+ expected = 14
actual = LocalGraphGenerator.graph_all.count
assert_equal expected, actual
end
diff --git a/test/services/pipeline/pipeline_status_evaluator_contract_test.rb b/test/services/pipeline/pipeline_status_evaluator_contract_test.rb
new file mode 100644
index 00000000..2eeeed0e
--- /dev/null
+++ b/test/services/pipeline/pipeline_status_evaluator_contract_test.rb
@@ -0,0 +1,270 @@
+require "test_helper"
+
+class PipelineStatusEvaluatorContractTest < ActiveSupport::TestCase
+ def call(trace, result = nil)
+ Pipeline::PipelineStatusEvaluator.call(result: result, trace: trace)
+ end
+
+ test "trace must be an Array" do
+ output = Pipeline::PipelineStatusEvaluator.call(
+ result: { data: ["ok"] },
+ trace: { service: "wringer", error: { error_type: "system_queue" } }
+ )
+
+ assert_equal :failed, output[:status]
+ assert_equal "dsl_error", output[:error_type]
+ assert_output_contract(output)
+ end
+
+ test "result must be a Hash or nil" do
+ output = Pipeline::PipelineStatusEvaluator.call(
+ result: ["abort_update", { error_type: "system_queue" }],
+ trace: []
+ )
+
+ assert_equal :failed, output[:status]
+ assert_equal "dsl_error", output[:error_type]
+ assert_output_contract(output)
+ end
+
+ test "wringer extraction only includes wringer service events with error present" do
+ output = Pipeline::PipelineStatusEvaluator.call(
+ result: { data: ["ok"] },
+ trace: [
+ { service: "other", error: { error_type: "system_cloudflare", retry: true, cache: false } },
+ { service: "wringer" },
+ { service: "wringer", error: { error_type: "system_queue", retry: true, cache: false } }
+ ]
+ )
+
+ assert_equal :partial, output[:status]
+ assert_equal "system_queue", output[:error_type]
+ assert_equal true, output[:retryable]
+ assert_equal false, output[:cacheable]
+ assert_output_contract(output)
+ end
+
+ test "error_type is normalized to lowercase string" do
+ output = Pipeline::PipelineStatusEvaluator.call(
+ result: { data: ["ok"] },
+ trace: [
+ { service: "wringer", error: { error_type: "System_Queue", retry: true, cache: false } }
+ ]
+ )
+
+ assert_equal :partial, output[:status]
+ assert_equal "system_queue", output[:error_type]
+ assert_output_contract(output)
+ end
+
+ test "abort always overrides wringer aggregation" do
+ output = Pipeline::PipelineStatusEvaluator.call(
+ result: { abort: true, error: "abort now", error_type: "Blocked_Page" },
+ trace: [
+ { service: "wringer", error: { error_type: "system_cloudflare", retry: true, cache: false } }
+ ]
+ )
+
+ assert_equal :failed, output[:status]
+ assert_equal "blocked_page", output[:error_type]
+ assert_output_contract(output)
+ end
+
+ test "abort without error_type returns dsl_error" do
+ output = Pipeline::PipelineStatusEvaluator.call(
+ result: { abort: true, error: "boom" },
+ trace: [
+ { service: "wringer", error: { error_type: "redirect_to_listing", retry: false, cache: false } }
+ ]
+ )
+
+ assert_equal :failed, output[:status]
+ assert_equal "dsl_error", output[:error_type]
+ assert_output_contract(output)
+ end
+
+ test "empty trace with valid result returns ok" do
+ output = Pipeline::PipelineStatusEvaluator.call(result: { data: ["ok"] }, trace: [])
+
+ assert_equal :ok, output[:status]
+ assert_nil output[:error_type]
+ assert_output_contract(output)
+ end
+
+ test "empty trace with abort returns failed" do
+ output = Pipeline::PipelineStatusEvaluator.call(
+ result: { abort: true, error: "stop", error_type: "redirect_to_listing" },
+ trace: []
+ )
+
+ assert_equal :failed, output[:status]
+ assert_equal "redirect_to_listing", output[:error_type]
+ assert_output_contract(output)
+ end
+
+ test "nil result returns failed" do
+ output = Pipeline::PipelineStatusEvaluator.call(result: nil, trace: [])
+
+ assert_equal :failed, output[:status]
+ assert_equal "dsl_error", output[:error_type]
+ assert_output_contract(output)
+ end
+
+ test "output contract includes required keys" do
+ output = Pipeline::PipelineStatusEvaluator.call(result: { data: ["ok"] }, trace: [])
+
+ assert_output_contract(output)
+ end
+
+ test "order of wringer events does not affect result" do
+ trace = [
+ { service: "wringer", error: true, error_type: "timeout", retry: true },
+ { service: "wringer", error: true, error_type: "network", retry: false }
+ ]
+
+ base = call(trace)
+
+ 10.times do
+ assert_equal base, call(trace.shuffle)
+ end
+
+ assert_equal base, call(trace.reverse)
+ end
+
+ test "same input always produces identical output" do
+ trace = [
+ { service: "wringer", error: true, error_type: "timeout", retry: true }
+ ]
+
+ r1 = call(trace)
+ r2 = call(trace)
+
+ assert_equal r1, r2
+ end
+
+ test "tie-breaking is deterministic for equivalent severity events" do
+ trace = [
+ { service: "wringer", error: true, error_type: "alpha", retry: true, cache: true },
+ { service: "wringer", error: true, error_type: "beta", retry: true, cache: true }
+ ]
+
+ results = []
+
+ 20.times do
+ results << call(trace.shuffle)
+ end
+
+ results.each do |r|
+ assert_equal results.first, r
+ end
+ end
+
+ test "tie-breaking handles nil and mixed types deterministically" do
+ trace = [
+ { service: "wringer", error: true, error_type: nil, retry: true },
+ { service: "wringer", error: true, error_type: "timeout", retry: true }
+ ]
+
+ results = []
+
+ 20.times do
+ results << call(trace.shuffle)
+ end
+
+ results.each do |r|
+ assert_equal results.first, r
+ end
+ end
+
+ test "ignores garbage entries safely" do
+ trace = [
+ nil,
+ "oops",
+ 123,
+ {},
+ [],
+ { foo: "bar" },
+ { service: "wringer", error: true, error_type: "timeout" }
+ ]
+
+ result = call(trace)
+
+ assert result.is_a?(Hash)
+ assert_includes [:ok, :partial, :failed], result[:status]
+ end
+
+ test "handles incomplete wringer events safely" do
+ trace = [
+ { service: "wringer" },
+ { wringer: {} }
+ ]
+
+ result = call(trace)
+
+ assert result.is_a?(Hash)
+ end
+
+ test "handles string keys the same as symbol keys" do
+ trace_symbol = [
+ { service: "wringer", error: true, error_type: "timeout" }
+ ]
+
+ trace_string = [
+ { "service" => "wringer", "error" => true, "error_type" => "timeout" }
+ ]
+
+ assert_equal call(trace_symbol), call(trace_string)
+ end
+
+ test "handles unexpected error_type values safely" do
+ trace = [
+ { service: "wringer", error: true, error_type: Object.new }
+ ]
+
+ result = call(trace)
+
+ assert result[:error_type].nil? || result[:error_type].is_a?(String)
+ end
+
+ # IMPORTANT:
+ # nil result represents a failed/invalid pipeline execution.
+ # An empty but valid result must be expressed explicitly as a Hash.
+ # This test verifies that an empty trace + valid result is considered OK.
+ test "empty trace yields ok and cacheable" do
+ result = call([], { data: [] })
+
+ assert_equal :ok, result[:status]
+ assert_equal true, result[:cacheable]
+ end
+
+ test "abort overrides all trace events" do
+ trace = [
+ { service: "wringer", error: true, error_type: "timeout", retry: true }
+ ]
+
+ result = call(trace, { abort: true, error_type: "fatal" })
+
+ assert_equal :failed, result[:status]
+ assert_equal false, result[:retryable]
+ assert_equal false, result[:cacheable]
+ assert_equal :abort, result[:reason]
+ end
+
+ test "supports mixed legacy and new wringer formats" do
+ trace = [
+ { service: "wringer", error: true, error_type: "timeout" },
+ { wringer: { error_type: "network", retry: true } }
+ ]
+
+ result = call(trace)
+
+ assert result[:status]
+ end
+
+ private
+
+ def assert_output_contract(output)
+ required = %i[status error_type retryable cacheable reason]
+ assert_equal required.sort, output.keys.sort
+ end
+end
diff --git a/test/services/pipeline/pipeline_status_evaluator_test.rb b/test/services/pipeline/pipeline_status_evaluator_test.rb
new file mode 100644
index 00000000..e35fb40c
--- /dev/null
+++ b/test/services/pipeline/pipeline_status_evaluator_test.rb
@@ -0,0 +1,101 @@
+require "test_helper"
+
+class PipelineStatusEvaluatorTest < ActiveSupport::TestCase
+ test "multiple wringer events partial plus failed resolves to failed" do
+ result = { data: ["ok"] }
+ trace = [
+ { wringer: { error_type: "system_queue", retry: true, cache: true } },
+ { wringer: { error_type: "redirect_to_listing", retry: false, cache: false } }
+ ]
+
+ output = Pipeline::PipelineStatusEvaluator.call(result: result, trace: trace)
+
+ assert_equal :failed, output[:status]
+ assert_equal "redirect_to_listing", output[:error_type]
+ assert_equal true, output[:retryable]
+ assert_equal false, output[:cacheable]
+ assert_equal :invalid_event_page, output[:reason]
+ end
+
+ test "multiple wringer events aggregate retryable with any true" do
+ result = { data: ["ok"] }
+ trace = [
+ { wringer: { error_type: "system_cloudflare", retry: false, cache: true } },
+ { wringer: { error_type: "system_queue", retry: true, cache: true } }
+ ]
+
+ output = Pipeline::PipelineStatusEvaluator.call(result: result, trace: trace)
+
+ assert_equal :partial, output[:status]
+ assert_equal true, output[:retryable]
+ end
+
+ test "multiple wringer events aggregate cacheable with all true" do
+ result = { data: ["ok"] }
+ trace = [
+ { wringer: { error_type: "system_cloudflare", retry: true, cache: true } },
+ { wringer: { error_type: "system_queue", retry: true, cache: false } }
+ ]
+
+ output = Pipeline::PipelineStatusEvaluator.call(result: result, trace: trace)
+
+ assert_equal :partial, output[:status]
+ assert_equal false, output[:cacheable]
+ end
+
+ test "fatal error overrides partial status" do
+ result = { data: ["ok"] }
+ trace = [
+ { wringer: { error_type: "system_cloudflare", retry: true, cache: true } },
+ { wringer: { error_type: "blocked_page", retry: false, cache: false } }
+ ]
+
+ output = Pipeline::PipelineStatusEvaluator.call(result: result, trace: trace)
+
+ assert_equal :failed, output[:status]
+ assert_equal "blocked_page", output[:error_type]
+ end
+
+ test "empty trace with valid result returns ok" do
+ output = Pipeline::PipelineStatusEvaluator.call(result: { data: ["ok"] }, trace: [])
+
+ assert_equal :ok, output[:status]
+ assert_nil output[:error_type]
+ assert_equal false, output[:retryable]
+ assert_equal true, output[:cacheable]
+ assert_nil output[:reason]
+ end
+
+ test "nil result returns failed dsl_error" do
+ output = Pipeline::PipelineStatusEvaluator.call(result: nil, trace: [])
+
+ assert_equal :failed, output[:status]
+ assert_equal "dsl_error", output[:error_type]
+ assert_equal false, output[:retryable]
+ assert_equal false, output[:cacheable]
+ end
+
+ test "abort without error_type returns failed dsl_error" do
+ result = { abort: true, error: "boom" }
+
+ output = Pipeline::PipelineStatusEvaluator.call(result: result, trace: [])
+
+ assert_equal :failed, output[:status]
+ assert_equal "dsl_error", output[:error_type]
+ assert_equal false, output[:retryable]
+ assert_equal false, output[:cacheable]
+ end
+
+ test "last error wins when severity is equal" do
+ result = { data: ["ok"] }
+ trace = [
+ { wringer: { error_type: "system_cloudflare", retry: false, cache: true } },
+ { wringer: { error_type: "system_queue", retry: false, cache: true } }
+ ]
+
+ output = Pipeline::PipelineStatusEvaluator.call(result: result, trace: trace)
+
+ assert_equal :partial, output[:status]
+ assert_equal "system_queue", output[:error_type]
+ end
+end
diff --git a/test/services/statements/extracted_parity_comparison_service_test.rb b/test/services/statements/extracted_parity_comparison_service_test.rb
new file mode 100644
index 00000000..3eb01cde
--- /dev/null
+++ b/test/services/statements/extracted_parity_comparison_service_test.rb
@@ -0,0 +1,188 @@
+require "test_helper"
+
+class Statements::ExtractedParityComparisonServiceTest < ActiveSupport::TestCase
+ test "defaults to essential properties only and ignores non essential differences" do
+ website, webpage = build_statement_compare_fixture
+ essential_same = create_statement_compare_source(
+ website: website,
+ property: essential_property(1, "Essential title"),
+ algorithm: "xpath=//title/text()"
+ )
+ essential_changed = create_statement_compare_source(
+ website: website,
+ property: essential_property(3, "Essential description"),
+ algorithm: "xpath=//meta[@name='description']/@content"
+ )
+ essential_removed = create_statement_compare_source(
+ website: website,
+ property: essential_property(5, "Essential legacy note"),
+ algorithm: "xpath=//div[@class='legacy-only']/text()"
+ )
+ essential_error = create_statement_compare_source(
+ website: website,
+ property: essential_property(13, "Essential fetched title"),
+ algorithm: "url=$url + '?detail'; xpath=//title/text()"
+ )
+ non_essential = create_statement_compare_source(
+ website: website,
+ property: non_essential_property(21, "Non essential photo"),
+ algorithm: "xpath=//img/@src"
+ )
+
+ [essential_same, essential_changed, essential_removed, essential_error, non_essential].each do |source|
+ create_statement_compare_record(webpage: webpage, source: source)
+ end
+
+ result = nil
+ Distillator::FetchCacheStore.expects(:fetch).never
+
+ assert_no_difference("Statement.count") do
+ assert_no_difference("Source.count") do
+ assert_no_difference("Webpage.count") do
+ assert_no_difference("Distillator::FetchCache.count") do
+ assert_no_difference("Distillator::TransitionEvidence.count") do
+ assert_no_difference("Distillator::RolloutEvent.count") do
+ result = Statements::ExtractedParityComparisonService.call(
+ webpage: webpage,
+ default_language: website.default_language,
+ legacy_html: legacy_html,
+ condenser_html: condenser_html,
+ refresh_helper: StatementsHelper.build_refresh_proxy(cookies: {})
+ )
+ end
+ end
+ end
+ end
+ end
+ end
+
+ assert_equal [1, 3, 5, 13], result.property_ids
+ assert_equal 4, result.sources_count
+ assert_equal 1, result.counts[:same]
+ assert_equal 0, result.counts[:added]
+ assert_equal 1, result.counts[:removed]
+ assert_equal 1, result.counts[:changed]
+ assert_equal 1, result.counts[:extraction_errors]
+ assert_equal ["Essential description"], result.groups[:changed].map { |row| row[:property_label] }
+ assert_equal ["Essential legacy note"], result.groups[:removed].map { |row| row[:property_label] }
+ assert_equal ["Essential fetched title"], result.groups[:extraction_errors].map { |row| row[:property_label] }
+ assert_equal ["Essential title"], result.groups[:same].map { |row| row[:property_label] }
+ assert_not_includes result.groups.values.flatten.map { |row| row[:property_label] }, "Non essential photo"
+ end
+
+ test "essential property difference is still reported" do
+ website, webpage = build_statement_compare_fixture(suffix: "essential-reported")
+ source = create_statement_compare_source(
+ website: website,
+ property: essential_property(3, "Essential description"),
+ algorithm: "xpath=//meta[@name='description']/@content"
+ )
+ create_statement_compare_record(webpage: webpage, source: source)
+
+ result = Statements::ExtractedParityComparisonService.call(
+ webpage: webpage,
+ default_language: website.default_language,
+ legacy_html: legacy_html,
+ condenser_html: condenser_html,
+ refresh_helper: StatementsHelper.build_refresh_proxy(cookies: {})
+ )
+
+ assert_equal 1, result.counts[:changed]
+ assert_equal "Essential description", result.groups[:changed].first[:property_label]
+ assert_equal "Legacy description", result.groups[:changed].first[:legacy]
+ assert_equal "Condenser description", result.groups[:changed].first[:condenser]
+ end
+
+ private
+
+ def build_statement_compare_fixture(suffix: "parity-compare-site")
+ website = Website.create!(
+ name: "Parity compare site #{suffix}",
+ seedurl: suffix,
+ graph_name: "https://example.org/#{suffix}",
+ default_language: "en"
+ )
+ webpage = Webpage.create!(
+ url: "https://example.org/#{suffix}/event",
+ language: "en",
+ rdf_uri: "rdf:#{suffix}:event",
+ rdfs_class: rdfs_classes(:one),
+ website: website
+ )
+
+ [website, webpage]
+ end
+
+ def create_statement_compare_source(website:, property:, algorithm:)
+ Source.create!(
+ algorithm_value: algorithm,
+ selected: true,
+ selected_by: "test",
+ language: "en",
+ render_js: false,
+ property: property,
+ website: website
+ )
+ end
+
+ def create_statement_compare_record(webpage:, source:)
+ Statement.create!(
+ cache: "seeded cache",
+ status: "ok",
+ status_origin: "test",
+ cache_refreshed: 1.hour.ago,
+ cache_changed: 1.hour.ago,
+ source: source,
+ webpage: webpage,
+ selected_individual: true
+ )
+ end
+
+ def essential_property(id, label)
+ Property.create!(
+ id: id,
+ label: label,
+ value_datatype: "MyString",
+ uri: "https://example.org/properties/#{id}",
+ rdfs_class: rdfs_classes(:one)
+ )
+ end
+
+ def non_essential_property(id, label)
+ Property.create!(
+ id: id,
+ label: label,
+ value_datatype: "MyString",
+ uri: "https://example.org/properties/#{id}",
+ rdfs_class: rdfs_classes(:one)
+ )
+ end
+
+ def legacy_html
+ <<~HTML
+
+
+ Shared Title
+
+
+
+ Legacy note
+
+
+ HTML
+ end
+
+ def condenser_html
+ <<~HTML
+
+
+ Shared Title
+
+
+
+
+
+
+ HTML
+ end
+end
diff --git a/test/services/statements/refresh_webpage_statements_service_test.rb b/test/services/statements/refresh_webpage_statements_service_test.rb
new file mode 100644
index 00000000..8de45ae4
--- /dev/null
+++ b/test/services/statements/refresh_webpage_statements_service_test.rb
@@ -0,0 +1,124 @@
+require "test_helper"
+
+module Statements
+ class RefreshWebpageStatementsServiceTest < ActiveSupport::TestCase
+ class DummyRefreshHelper
+ attr_reader :calls
+
+ def initialize
+ @calls = []
+ end
+
+ def refresh_statement_helper(stat, scrape_options)
+ @calls << { statement_id: stat.id, scrape_options: scrape_options }
+ end
+ end
+
+ test "call delegates statement refreshes to provided helper and returns error list" do
+ webpage = webpages(:six)
+ helper = DummyRefreshHelper.new
+ service = Statements::RefreshWebpageStatementsService.new(refresh_helper: helper)
+
+ result = service.call(webpage: webpage, default_language: "en", scrape_options: { force_scrape_every_hrs: 1 })
+
+ assert_kind_of Array, result
+ assert_operator helper.calls.size, :>, 0
+ assert_equal({ force_scrape_every_hrs: 1 }, helper.calls.first[:scrape_options])
+ end
+
+ test "call preserves string force_scrape_every_hrs through helper refreshes" do
+ webpage = webpages(:six)
+ helper = DummyRefreshHelper.new
+ service = Statements::RefreshWebpageStatementsService.new(refresh_helper: helper)
+
+ service.call(webpage: webpage, default_language: "en", scrape_options: { force_scrape_every_hrs: "1" })
+
+ assert_operator helper.calls.size, :>, 0
+ assert_equal({ force_scrape_every_hrs: "1" }, helper.calls.first[:scrape_options])
+ end
+
+ test "call refreshes the existing statement for matching webpage and source" do
+ webpage = webpages(:culture3r_refresh_fixture)
+ statement = statements(:culture3r_refresh_statement)
+ helper = DummyRefreshHelper.new
+ service = Statements::RefreshWebpageStatementsService.new(refresh_helper: helper)
+
+ service.call(webpage: webpage, default_language: "en", scrape_options: { force_scrape_every_hrs: "1" })
+
+ matching_call = helper.calls.find { |call| call[:statement_id] == statement.id }
+
+ assert matching_call, "expected existing statement #{statement.id} to be refreshed"
+ assert_equal({ force_scrape_every_hrs: "1" }, matching_call[:scrape_options])
+ assert_equal statement.id, Statement.find_by!(webpage_id: webpage.id, source_id: statement.source_id).id
+ end
+
+ test "call updates existing statement cache through the real refresh helper" do
+ previous_mode = ENV["DISTILLATOR_FETCH_MODE"]
+ ENV["DISTILLATOR_FETCH_MODE"] = "internal"
+ Distillator::FetchCache.delete_all
+
+ website = Website.create!(
+ name: "Service Refresh Fixture",
+ seedurl: "service-refresh-fixture",
+ graph_name: "https://fixtures.example/service-refresh",
+ default_language: "en",
+ distillator_mode: "active"
+ )
+ webpage = Webpage.create!(
+ website: website,
+ rdfs_class: rdfs_classes(:one),
+ url: "https://fixtures.example/service-refresh/event",
+ rdf_uri: "footlight:service-refresh-event",
+ language: "en"
+ )
+ source = Source.create!(
+ algorithm_value: "xpath=//h1/text()",
+ selected: true,
+ selected_by: "test",
+ language: "en",
+ render_js: false,
+ property: properties(:four),
+ website: website
+ )
+ statement = Statement.create!(
+ cache: "Old Service Title",
+ status: "initial",
+ status_origin: "test",
+ cache_refreshed: 1.day.ago,
+ cache_changed: 1.day.ago,
+ source: source,
+ webpage: webpage,
+ selected_individual: true
+ )
+
+ Distillator::FetchGuard.stubs(:check_url).returns(Distillator::FetchGuard::Result.new(allowed: true))
+ Distillator::FetchGuard.stubs(:check_response).returns(Distillator::FetchGuard::Result.new(allowed: true))
+ Distillator::NativeFetch.expects(:call).once.returns(
+ status: :ok,
+ body: "Service Refreshed Title ",
+ raw_body: "Service Refreshed Title ",
+ headers: { content_type: "text/html" },
+ final_url: webpage.url,
+ redirect_chain: [webpage.url],
+ wringer: { signals: {}, hints: [] },
+ http_code: 200
+ )
+
+ helper = Object.new
+ helper.extend(StatementsHelper)
+ helper.instance_variable_set(:@_statement_refresh_cookies, {})
+ helper.define_singleton_method(:cookies) { @_statement_refresh_cookies }
+
+ result = Statements::RefreshWebpageStatementsService.new(refresh_helper: helper).call(
+ webpage: webpage,
+ default_language: "en",
+ scrape_options: { force_scrape_every_hrs: "0" }
+ )
+
+ assert_equal [], result
+ assert_equal "Service Refreshed Title", statement.reload.cache
+ ensure
+ ENV["DISTILLATOR_FETCH_MODE"] = previous_mode
+ end
+ end
+end
diff --git a/test/services/wringer_client_test.rb b/test/services/wringer_client_test.rb
new file mode 100644
index 00000000..357cf634
--- /dev/null
+++ b/test/services/wringer_client_test.rb
@@ -0,0 +1,27 @@
+require "test_helper"
+require "minitest/mock"
+
+class WringerClientTest < ActiveSupport::TestCase
+ test "preserves DSL wringer metadata in signals" do
+ fake_dsl_result = {
+ body: "ok",
+ wringer: {
+ error_type: "system_cloudflare",
+ retry: true,
+ cache: false,
+ signals: { network_status: "ok" }
+ }
+ }
+
+ client = WringerClient.new("http://example.com")
+
+ client.stub(:call_wringer, fake_dsl_result) do
+ result = client.fetch
+
+ assert_equal "system_cloudflare", result[:signals][:error_type]
+ assert_equal true, result[:signals][:retry]
+ assert_equal false, result[:signals][:cache]
+ assert_equal "ok", result[:signals][:signals][:network_status]
+ end
+ end
+end
diff --git a/test/shared/source_identity_test.rb b/test/shared/source_identity_test.rb
new file mode 100644
index 00000000..158646c6
--- /dev/null
+++ b/test/shared/source_identity_test.rb
@@ -0,0 +1,48 @@
+require "test_helper"
+
+class SourceIdentityTest < ActiveSupport::TestCase
+ test "to_seedurl matches expected normalization output" do
+ input = " https://Example.com/Foo/Bar?x=1#frag "
+
+ expected = input.downcase.strip
+ expected = expected.gsub(%r{https?://}, "")
+ expected = expected.gsub("/", "")
+ expected = expected.gsub(".", "-")
+
+ assert_equal expected, SourceIdentity.from_url(input).to_seedurl
+ end
+
+ test "to_canonical_url keeps scheme differences for http and https" do
+ http_identity = SourceIdentity.from_url("http://Example.com/path")
+ https_identity = SourceIdentity.from_url("https://Example.com/path")
+
+ assert_equal "http://example.com/path", http_identity.to_canonical_url
+ assert_equal "https://example.com/path", https_identity.to_canonical_url
+ end
+
+ test "to_canonical_url normalizes trailing slash" do
+ source_identity = SourceIdentity.from_url("https://Example.com")
+
+ assert_equal "https://example.com/", source_identity.to_canonical_url
+ end
+
+ test "to_canonical_url drops fragments" do
+ source_identity = SourceIdentity.from_url("https://Example.com/path#section")
+
+ assert_equal "https://example.com/path", source_identity.to_canonical_url
+ end
+
+ test "to_canonical_url drops query params" do
+ source_identity = SourceIdentity.from_url("https://Example.com/path?x=1&y=2")
+
+ assert_equal "https://example.com/path", source_identity.to_canonical_url
+ end
+
+ test "to_wringer_key matches expected escaped canonical url" do
+ url = "https://Example.com/path?x=1"
+
+ expected = CGI.escape("https://example.com/path?x=1")
+
+ assert_equal expected, SourceIdentity.from_url(url).to_wringer_key
+ end
+end
diff --git a/test/support/distillator/export_fetch_mode_harness.rb b/test/support/distillator/export_fetch_mode_harness.rb
new file mode 100644
index 00000000..cad43ba7
--- /dev/null
+++ b/test/support/distillator/export_fetch_mode_harness.rb
@@ -0,0 +1,170 @@
+require "digest/sha1"
+require "json"
+
+module Distillator
+ module ExportFetchModeHarness
+ FIXTURE_STATEMENT_TIMESTAMP = Time.zone.parse("2026-04-27T00:00:00Z")
+
+ FetchModeResponse = Struct.new(:code, :body, :uri, :response, keyword_init: true) do
+ def [](key)
+ response && (response[key] || response[key.to_s])
+ end
+
+ def headers
+ response
+ end
+ end
+
+ FetchModeHistoryEntry = Struct.new(:uri, keyword_init: true)
+
+ class ReplayBackedWringerAgent
+ attr_reader :fetched_urls
+
+ def initialize(site:)
+ @site = site
+ @fetched_urls = []
+ @history = []
+ end
+
+ def get(url)
+ @fetched_urls << url
+ payload = replay_payload(url)
+ response = payload.fetch("response")
+ @history = Array(response.fetch("redirect_chain")).map do |history_url|
+ FetchModeHistoryEntry.new(uri: URI(history_url))
+ end
+
+ FetchModeResponse.new(
+ code: http_code(response),
+ body: response.fetch("body"),
+ uri: URI(response.fetch("final_url") || url),
+ response: response.fetch("headers")
+ )
+ end
+
+ def history
+ @history
+ end
+
+ private
+
+ def replay_payload(url)
+ path = Rails.root.join(
+ "data",
+ "migration_fixtures",
+ @site,
+ "fetch",
+ "#{Digest::SHA1.hexdigest(url)}.json"
+ )
+ JSON.parse(File.read(path))
+ end
+
+ def http_code(response)
+ wringer_code = response.dig("wringer", "http_code")
+ return wringer_code.to_i if wringer_code
+ return 404 if response.fetch("status") == "abort"
+
+ 200
+ end
+ end
+
+ def run_export_under_fetch_mode(mode)
+ reset_export_state!
+ responses = fetch_curated_pages(mode)
+
+ {
+ responses: responses,
+ export: ExportArtsdataService.call(seedurl: @fixture.fetch("seedurl"))
+ }
+ end
+
+ def fetch_curated_pages(mode)
+ @fixture.fetch("pages").index_by { |page| page.fetch("case") }.transform_values do |page|
+ fetch_fixture_page(mode: mode, url: page.fetch("url"))
+ end
+ end
+
+ def fetch_fixture_page(mode:, url:)
+ case mode
+ when :replay
+ with_replay_fetch_enabled do
+ Distillator::FetchService.fetch(url: url)
+ end
+ when :legacy
+ with_fetch_replay_disabled do
+ Distillator::FetchService.stubs(:use_internal_fetch?).returns(false)
+ Distillator::FetchService.fetch(**wringer_fetch_kwargs(url))
+ end
+ when :internal
+ with_fetch_replay_disabled do
+ Distillator::FetchService.stubs(:use_internal_fetch?).returns(true)
+ Distillator::FetchService.fetch(**wringer_fetch_kwargs(url))
+ end
+ else
+ raise ArgumentError, "Unsupported fetch mode: #{mode.inspect}"
+ end
+ end
+
+ def wringer_fetch_kwargs(url)
+ {
+ url: url,
+ render_js: false,
+ scrape_options: {},
+ agent: ReplayBackedWringerAgent.new(site: self.class::FIXTURE_SITE),
+ use_wringer: ->(resolved_url, _render_js, _scrape_options) { resolved_url },
+ safe_wringer_call: method(:replay_wringer_policy),
+ logger: Rails.logger
+ }
+ end
+
+ def replay_wringer_policy(normalize_response: false)
+ response = yield
+ return response unless response.respond_to?(:code)
+
+ case response.code.to_i
+ when 404
+ [
+ "abort_update",
+ {
+ error: "Not Found",
+ error_type: "http_404",
+ source: "wringer",
+ policy: { action: "abort_update", retry: false, cache: false }
+ }
+ ]
+ else
+ response
+ end
+ end
+
+ def with_fetch_replay_disabled
+ old_replay_fetch = ENV["REPLAY_FETCH"]
+ old_fetch_site = ENV["FETCH_SITE"]
+ ENV["REPLAY_FETCH"] = nil
+ ENV["FETCH_SITE"] = nil
+ yield
+ ensure
+ ENV["REPLAY_FETCH"] = old_replay_fetch
+ ENV["FETCH_SITE"] = old_fetch_site
+ end
+
+ def with_replay_fetch_enabled
+ old_replay_fetch = ENV["REPLAY_FETCH"]
+ old_fetch_site = ENV["FETCH_SITE"]
+ ENV["REPLAY_FETCH"] = "true"
+ ENV["FETCH_SITE"] = self.class::FIXTURE_SITE
+ yield
+ ensure
+ ENV["REPLAY_FETCH"] = old_replay_fetch
+ ENV["FETCH_SITE"] = old_fetch_site
+ end
+
+ def reset_export_state!
+ Distillator::FetchCache.delete_all
+ Statement.where(status_origin: "distillator_export_invariance").update_all(
+ cache_refreshed: FIXTURE_STATEMENT_TIMESTAMP,
+ cache_changed: FIXTURE_STATEMENT_TIMESTAMP
+ )
+ end
+ end
+end
diff --git a/test/support/distillator_fixture_helpers.rb b/test/support/distillator_fixture_helpers.rb
new file mode 100644
index 00000000..b6c270fa
--- /dev/null
+++ b/test/support/distillator_fixture_helpers.rb
@@ -0,0 +1,22 @@
+module DistillatorFixtureHelpers
+ CATEGORY_FILES = {
+ simple_static_html_title: "simple_static_title.html",
+ xpath_extraction: "xpath_extraction.html",
+ xpath_sanitize: "xpath_sanitize.html",
+ jsonld_script_extraction: "jsonld_script_event.html",
+ relative_src_href: "relative_links.html",
+ redirected_url: "redirected_event_final.html",
+ failed_fetch_404: "failed_404.html",
+ json_post_legacy_only: "json_post_payload.json",
+ render_js_legacy_only: "render_js_iframe.html",
+ timezone_date_extraction: "timezone_dates.html"
+ }.freeze
+
+ def distillator_fixture_path(name)
+ Rails.root.join("test", "fixtures", "files", "distillator", CATEGORY_FILES.fetch(name))
+ end
+
+ def distillator_fixture_categories
+ CATEGORY_FILES.keys
+ end
+end
diff --git a/test/support/distillator_integration_event_factory.rb b/test/support/distillator_integration_event_factory.rb
new file mode 100644
index 00000000..72e07a55
--- /dev/null
+++ b/test/support/distillator_integration_event_factory.rb
@@ -0,0 +1,99 @@
+module DistillatorIntegrationEventFactory
+ def build_publishable_event(seedurl:, rdf_uri:, algorithm_value:, render_js:, initial_title:, json_post: false)
+ website = Website.create!(
+ name: seedurl.humanize,
+ seedurl: seedurl,
+ graph_name: "https://fixtures.example/#{seedurl}",
+ default_language: "en"
+ )
+ event_page = Webpage.create!(
+ website: website,
+ rdfs_class: rdfs_classes(:one),
+ url: "https://fixtures.example/#{seedurl}/event",
+ rdf_uri: rdf_uri,
+ language: "en",
+ archive_date: Time.zone.parse("2026-06-01T00:00:00Z")
+ )
+ place_page = Webpage.create!(
+ website: website,
+ rdfs_class: rdfs_classes(:place),
+ url: "https://fixtures.example/#{seedurl}/place",
+ rdf_uri: "#{rdf_uri}-place",
+ language: "en",
+ archive_date: Time.zone.parse("2026-06-01T00:00:00Z")
+ )
+
+ Source.create!(
+ website: website,
+ property: properties(:two),
+ algorithm_value: "manual=Main Hall",
+ selected: true,
+ selected_by: "test",
+ render_js: false
+ ).tap do |source|
+ Statement.create!(
+ webpage: place_page,
+ source: source,
+ cache: "Main Hall",
+ status: "ok",
+ status_origin: "test",
+ cache_refreshed: 1.day.ago,
+ cache_changed: 1.day.ago,
+ selected_individual: true
+ )
+ end
+
+ title_source = Source.create!(
+ website: website,
+ property: properties(:four),
+ algorithm_value: algorithm_value,
+ selected: true,
+ selected_by: "test",
+ language: "en",
+ render_js: render_js
+ )
+ title_statement = Statement.create!(
+ webpage: event_page,
+ source: title_source,
+ cache: initial_title,
+ status: "initial",
+ status_origin: "test",
+ cache_refreshed: 1.day.ago,
+ cache_changed: 1.day.ago,
+ selected_individual: true
+ )
+ if json_post
+ allow_scrape_option_json_post!(title_statement)
+ end
+
+ create_support_statement!(website:, webpage: event_page, property: properties(:ten), cache: "[\"2026-05-10T19:30:00-04:00\"]")
+ create_support_statement!(website:, webpage: event_page, property: properties(:location), cache: "[\"Main Hall\",\"Place\",[\"Main Hall\",\"#{rdf_uri}-place\"]]")
+
+ [website, event_page, title_statement]
+ end
+
+ def allow_scrape_option_json_post!(statement)
+ statement
+ end
+
+ def create_support_statement!(website:, webpage:, property:, cache:)
+ source = Source.create!(
+ website: website,
+ property: property,
+ algorithm_value: "manual=#{property.label}",
+ selected: true,
+ selected_by: "test",
+ render_js: false
+ )
+ Statement.create!(
+ webpage: webpage,
+ source: source,
+ cache: cache,
+ status: "ok",
+ status_origin: "test",
+ cache_refreshed: 1.day.ago,
+ cache_changed: 1.day.ago,
+ selected_individual: true
+ )
+ end
+end
diff --git a/test/support/dsl_runner_test_helper.rb b/test/support/dsl_runner_test_helper.rb
new file mode 100644
index 00000000..679f6549
--- /dev/null
+++ b/test/support/dsl_runner_test_helper.rb
@@ -0,0 +1,119 @@
+module DslRunnerTestHelper
+ FETCH_RESPONSE_CLASS = Struct.new(
+ :status,
+ :body,
+ :html,
+ :headers,
+ :final_url,
+ :redirect_chain,
+ :http_response_code,
+ :signals,
+ :hints,
+ :duration_ms,
+ :cache_hit,
+ :cache_write,
+ :cache_reason,
+ :uri_key,
+ :normalized_url,
+ :fetch_path,
+ keyword_init: true
+ )
+
+ def build_runner(url: "http://example.local", tracer: Dsl::Tracing::TraceCollector.new, scrape_options: {}, render_js: false)
+ runner = Dsl::Core::AlgorithmRunner.new(
+ url: url,
+ render_js: render_js,
+ scrape_options: scrape_options,
+ tracer: tracer
+ )
+
+ [runner, tracer]
+ end
+
+ def build_runner_with_html(html: "", url: "http://example.local", tracer: Dsl::Tracing::TraceCollector.new, scrape_options: {}, render_js: false)
+ runner, tracer = build_runner(
+ url: url,
+ tracer: tracer,
+ scrape_options: scrape_options,
+ render_js: render_js
+ )
+
+ runner.instance_variable_set(:@html, html)
+ runner.instance_variable_set(:@page, Nokogiri::HTML(html, nil, Encoding::UTF_8.to_s))
+
+ [runner, tracer]
+ end
+
+ def build_runner_with_text(text:, url: "http://example.local", tracer: Dsl::Tracing::TraceCollector.new, scrape_options: {}, render_js: false)
+ runner, tracer = build_runner(
+ url: url,
+ tracer: tracer,
+ scrape_options: scrape_options,
+ render_js: render_js
+ )
+
+ runner.instance_variable_set(:@html, text)
+ runner.instance_variable_set(:@page, Struct.new(:text).new(text))
+
+ [runner, tracer]
+ end
+
+ def build_compat_runner(url: "http://example.local", tracer: Dsl::Tracing::TraceCollector.new, scrape_options: {}, render_js: false)
+ build_runner(
+ url: url,
+ tracer: tracer,
+ scrape_options: scrape_options.merge(wringer_compatibility: true),
+ render_js: render_js
+ )
+ end
+
+ def expect_no_fetch_seams
+ Distillator::FetchCacheStore.expects(:fetch).never
+ Dsl::Support::WringerClient.any_instance.expects(:fetch).never
+ end
+
+ def assert_no_wringer_requests
+ wring_path = %r{https?://(?:localhost|127\.0\.0\.1|0\.0\.0\.0):(3000|3009)/websites/wring}
+ assert_not_requested :any, wring_path
+ end
+
+ def distillator_fetch_response(
+ status: :ok,
+ body: "Title ",
+ html: nil,
+ headers: { content_type: "text/html" },
+ final_url: "http://example.local/start",
+ redirect_chain: nil,
+ http_response_code: 200,
+ signals: {},
+ hints: [],
+ duration_ms: 0,
+ cache_hit: false,
+ cache_write: true,
+ cache_reason: "missing_cache",
+ uri_key: nil,
+ normalized_url: nil,
+ fetch_path: "native"
+ )
+ url = final_url
+
+ FETCH_RESPONSE_CLASS.new(
+ status: status,
+ body: body,
+ html: html || body,
+ headers: headers,
+ final_url: final_url,
+ redirect_chain: redirect_chain || [final_url],
+ http_response_code: http_response_code,
+ signals: signals,
+ hints: hints,
+ duration_ms: duration_ms,
+ cache_hit: cache_hit,
+ cache_write: cache_write,
+ cache_reason: cache_reason,
+ uri_key: uri_key || CGI.escape(url),
+ normalized_url: normalized_url || url,
+ fetch_path: fetch_path
+ )
+ end
+end
diff --git a/test/support/harmonized_card_assertions.rb b/test/support/harmonized_card_assertions.rb
new file mode 100644
index 00000000..1ce971d3
--- /dev/null
+++ b/test/support/harmonized_card_assertions.rb
@@ -0,0 +1,18 @@
+module HarmonizedCardAssertions
+ def assert_harmonized_record_card(section_selector: "section.cache-detail-cards")
+ assert_select section_selector, 1
+ assert_select "#{section_selector} article.cache-detail-card", minimum: 1
+ end
+
+ def assert_harmonized_card_action_bar(action_selector: ".cache-actions")
+ assert_select action_selector, minimum: 1
+ assert_select "#{action_selector} .cache-action-group strong", text: "View"
+ assert_select "#{action_selector} a", text: "Show"
+ assert_select "#{action_selector} a", text: "Raw"
+ assert_select "#{action_selector} a", text: ".json"
+ assert_select "#{action_selector} a", text: "Pretty JSON"
+ assert_select "#{action_selector} a", text: "Compare"
+ end
+end
+
+ActiveSupport::TestCase.include(HarmonizedCardAssertions)
diff --git a/test/support/harmonized_table_assertions.rb b/test/support/harmonized_table_assertions.rb
new file mode 100644
index 00000000..740a4c39
--- /dev/null
+++ b/test/support/harmonized_table_assertions.rb
@@ -0,0 +1,80 @@
+module HarmonizedTableAssertions
+ def assert_harmonized_table_shell
+ assert_select ".harmonized-table-shell", 1
+ end
+
+ def assert_harmonized_filter_form(action:)
+ assert_select %(form[action="#{action}"][method="get"]), 1
+ end
+
+ def assert_harmonized_filter_shell(form_action: "/distillator/cache", details_selector: "details.cache-advanced-filters")
+ assert_harmonized_filter_form(action: form_action)
+ assert_select %(form[action="#{form_action}"][method="get"]), 1
+ assert_select details_selector, 1
+ end
+
+ def assert_harmonized_advanced_filters(details_selector: "details.cache-advanced-filters")
+ assert_select "#{details_selector} summary", text: "Advanced filters"
+ assert_select "#{details_selector} input[name='term']", 1
+ assert_select "#{details_selector} select[name='health']", 1
+ assert_select "#{details_selector} input[name='http_response_code']", 1
+ assert_select "#{details_selector} select[name='status_group']", 1
+ assert_select "#{details_selector} select[name='has_html']", 1
+ assert_select "#{details_selector} input[name='network_status']", 1
+ assert_select "#{details_selector} select[name='content_type']", 1
+ assert_select "#{details_selector} select[name='redirected']", 1
+ assert_select "#{details_selector} input[name='hint']", 1
+ assert_select "#{details_selector} select[name='last_attempt']", 1
+ assert_select "#{details_selector} select[name='last_success']", 1
+ end
+
+ def assert_harmonized_sortable_header(label:, sort_key:)
+ assert_select %(th a[href*="sort=#{sort_key}"]), text: /#{Regexp.escape(label)}/
+ end
+
+ def assert_harmonized_reset_filters_link(path: "/distillator/cache", params: {})
+ href = build_expected_href(path, params)
+ assert_select %(a[href="#{href}"]), text: "Reset filters"
+ end
+
+ def assert_harmonized_apply_filters_button
+ assert_select %(input[type="submit"][value="Apply filters"]), 1
+ end
+
+ def assert_harmonized_summary_cards_before_filters(summary_text: "Healthy", filter_text: "Advanced filters")
+ assert_operator @response.body.index(summary_text), :<, @response.body.index(filter_text)
+ end
+
+ def assert_sort_link_preserves_filters(label:, sort_key:, params:)
+ href = sortable_header_href(label: label, sort_key: sort_key)
+ params.each do |key, value|
+ assert_includes href, "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}"
+ end
+ assert_no_match(/(?:\?|&)page=/, href)
+ end
+
+ def assert_sort_link_preserves_params(label:, sort_key:, params:)
+ href = sortable_header_href(label: label, sort_key: sort_key)
+ params.each do |key, value|
+ assert_includes href, "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}"
+ end
+ end
+
+ private
+
+ def sortable_header_href(label:, sort_key:)
+ document = Nokogiri::HTML(@response.body)
+ link = document.css("th a").find do |node|
+ node.text.include?(label) && node["href"].to_s.include?("sort=#{sort_key}")
+ end
+ assert link, "Expected sortable header #{label.inspect} for #{sort_key.inspect}"
+ link["href"]
+ end
+
+ def build_expected_href(path, params)
+ query = params.compact_blank.to_query
+ query.present? ? "#{path}?#{query}" : path
+ end
+end
+
+ActiveSupport::TestCase.include(HarmonizedTableAssertions)
diff --git a/test/support/jsonld_comparator.rb b/test/support/jsonld_comparator.rb
new file mode 100644
index 00000000..0ce4745c
--- /dev/null
+++ b/test/support/jsonld_comparator.rb
@@ -0,0 +1,178 @@
+require "json"
+
+module JsonldComparator
+ VOLATILE_KEYS = %w[
+ created_at
+ updated_at
+ generated_at
+ debug
+ debug_info
+ trace
+ ].freeze
+
+ # Arrays are canonicalized only at explicitly declared unordered paths.
+ # Root JSON-LD export is a set of events and can be compared order-independently.
+ UNORDERED_ARRAY_PATHS = [
+ []
+ ].freeze
+
+ def canonical_jsonld(json)
+ value = parse_jsonld_if_string(json)
+ canonicalize_jsonld(value, [])
+ end
+
+ def assert_jsonld_equal(expected, actual)
+ expected_canonical = canonical_jsonld(expected)
+ actual_canonical = canonical_jsonld(actual)
+
+ diff = first_jsonld_difference(expected_canonical, actual_canonical, [])
+ return unless diff
+
+ flunk <<~MSG
+ JSON-LD mismatch at #{format_jsonld_path(diff[:path])}
+ Reason: #{diff[:reason]}
+ Expected: #{diff[:expected].inspect}
+ Actual: #{diff[:actual].inspect}
+ MSG
+ end
+
+ private
+
+ def parse_jsonld_if_string(value)
+ return value unless value.is_a?(String)
+
+ JSON.parse(value)
+ rescue JSON::ParserError
+ value
+ end
+
+ def canonicalize_jsonld(value, path)
+ case value
+ when Hash
+ canonical_pairs = value.each_with_object([]) do |(key, child), out|
+ next if volatile_key?(key)
+
+ out << [key, canonicalize_jsonld(child, path + [key])]
+ end
+
+ canonical_pairs
+ .sort_by { |(key, _)| [key.class.name, key.to_s] }
+ .each_with_object({}) { |(key, child), out| out[key] = child }
+ when Array
+ canonical_items = value.each_with_index.map do |child, index|
+ canonicalize_jsonld(child, path + [index])
+ end
+
+ return canonical_items unless unordered_array_path?(path)
+
+ ensure_sortable_unordered_array!(canonical_items, path)
+ canonical_items.sort_by { |child| canonical_sort_key(child) }
+ else
+ value
+ end
+ end
+
+ def volatile_key?(key)
+ VOLATILE_KEYS.include?(key.to_s)
+ end
+
+ def unordered_array_path?(path)
+ UNORDERED_ARRAY_PATHS.include?(path)
+ end
+
+ def ensure_sortable_unordered_array!(items, path)
+ return if items.empty?
+
+ unless items.all? { |item| item.is_a?(Hash) }
+ raise ArgumentError, "Cannot canonicalize unordered array at #{format_jsonld_path(path)} with non-hash elements"
+ end
+
+ items.each do |item|
+ canonical_sort_key(item)
+ rescue JSON::GeneratorError => e
+ raise ArgumentError, "Cannot canonicalize unordered array at #{format_jsonld_path(path)}: #{e.message}"
+ end
+ end
+
+ def canonical_sort_key(value)
+ JSON.generate(canonical_jsonld(value))
+ end
+
+ def first_jsonld_difference(expected, actual, path)
+ if expected.class != actual.class
+ return {
+ path: path,
+ reason: "type mismatch (#{expected.class} != #{actual.class})",
+ expected: expected,
+ actual: actual
+ }
+ end
+
+ case expected
+ when Hash
+ missing_key = (expected.keys - actual.keys).sort_by { |key| [key.class.name, key.to_s] }.first
+ if missing_key
+ return {
+ path: path + [missing_key],
+ reason: "missing key",
+ expected: expected[missing_key],
+ actual: :__missing__
+ }
+ end
+
+ extra_key = (actual.keys - expected.keys).sort_by { |key| [key.class.name, key.to_s] }.first
+ if extra_key
+ return {
+ path: path + [extra_key],
+ reason: "unexpected key",
+ expected: :__missing__,
+ actual: actual[extra_key]
+ }
+ end
+
+ expected.keys.sort_by { |key| [key.class.name, key.to_s] }.each do |key|
+ diff = first_jsonld_difference(expected[key], actual[key], path + [key])
+ return diff if diff
+ end
+ when Array
+ if expected.length != actual.length
+ return {
+ path: path,
+ reason: "array length mismatch",
+ expected: expected.length,
+ actual: actual.length
+ }
+ end
+
+ expected.each_index do |index|
+ diff = first_jsonld_difference(expected[index], actual[index], path + [index])
+ return diff if diff
+ end
+ else
+ return if expected == actual
+
+ return {
+ path: path,
+ reason: "value mismatch",
+ expected: expected,
+ actual: actual
+ }
+ end
+
+ nil
+ end
+
+ def format_jsonld_path(path)
+ return "root" if path.empty?
+
+ path.each_with_index.map do |segment, index|
+ if segment.is_a?(Integer)
+ "[#{segment}]"
+ elsif index.zero?
+ segment.to_s
+ else
+ ".#{segment}"
+ end
+ end.join
+ end
+end
diff --git a/test/system/properties_test.rb b/test/system/properties_test.rb
deleted file mode 100644
index ded50462..00000000
--- a/test/system/properties_test.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-require "application_system_test_case"
-
-class PropertiesTest < ApplicationSystemTestCase
- # test "visiting the index" do
- # visit properties_url
- #
- # assert_selector "h1", text: "Property"
- # end
-end
diff --git a/test/system/rdfs_classes_test.rb b/test/system/rdfs_classes_test.rb
deleted file mode 100644
index a19fac67..00000000
--- a/test/system/rdfs_classes_test.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-require "application_system_test_case"
-
-class RdfsClassesTest < ApplicationSystemTestCase
- # test "visiting the index" do
- # visit rdfs_classes_url
- #
- # assert_selector "h1", text: "RdfsClass"
- # end
-end
diff --git a/test/system/search_exceptions_test.rb b/test/system/search_exceptions_test.rb
deleted file mode 100644
index 664f1d5b..00000000
--- a/test/system/search_exceptions_test.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-require "application_system_test_case"
-
-class SearchExceptionsTest < ApplicationSystemTestCase
- # test "visiting the index" do
- # visit search_exceptions_url
- #
- # assert_selector "h1", text: "SearchException"
- # end
-end
diff --git a/test/system/sources_test.rb b/test/system/sources_test.rb
deleted file mode 100644
index c94cc80b..00000000
--- a/test/system/sources_test.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-require "application_system_test_case"
-
-class SourcesTest < ApplicationSystemTestCase
- # test "visiting the index" do
- # visit sources_url
- #
- # assert_selector "h1", text: "Source"
- # end
-end
diff --git a/test/system/statements_test.rb b/test/system/statements_test.rb
deleted file mode 100644
index 8022a358..00000000
--- a/test/system/statements_test.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-require "application_system_test_case"
-
-class StatementsTest < ApplicationSystemTestCase
- # test "visiting the index" do
- # visit statements_url
- #
- # assert_selector "h1", text: "Statement"
- # end
-end
diff --git a/test/system/webpages_test.rb b/test/system/webpages_test.rb
deleted file mode 100644
index 276c9f75..00000000
--- a/test/system/webpages_test.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-require "application_system_test_case"
-
-class WebpagesTest < ApplicationSystemTestCase
- # test "visiting the index" do
- # visit webpages_url
- #
- # assert_selector "h1", text: "Webpage"
- # end
-end
diff --git a/test/system/websites_test.rb b/test/system/websites_test.rb
deleted file mode 100644
index 27e66077..00000000
--- a/test/system/websites_test.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-require "application_system_test_case"
-
-class WebsitesTest < ApplicationSystemTestCase
- # test "visiting the index" do
- # visit websites_url
- #
- # assert_selector "h1", text: "Website"
- # end
-end
diff --git a/test/tasks/distillator_cache_task_test.rb b/test/tasks/distillator_cache_task_test.rb
new file mode 100644
index 00000000..ccf42e02
--- /dev/null
+++ b/test/tasks/distillator_cache_task_test.rb
@@ -0,0 +1,46 @@
+require "test_helper"
+require "rake"
+
+class DistillatorCacheTaskTest < ActiveSupport::TestCase
+ setup do
+ Distillator::FetchCache.delete_all
+ Rails.application.load_tasks unless Rake::Task.task_defined?("distillator:cache:backfill_health")
+ @task = Rake::Task["distillator:cache:backfill_health"]
+ @task.reenable
+ end
+
+ test "backfill task populates materialized health fields for old rows" do
+ cache = Distillator::FetchCache.create!(
+ uri_key: CGI.escape("http://example.org/backfill"),
+ normalized_url: "http://example.org/backfill",
+ html: "cached",
+ body: "cached",
+ scrape_date: 2.days.ago,
+ successful_refresh: 2.days.ago,
+ http_response_code: 200,
+ headers: {},
+ signals: { "network_status" => "ok", "content_type" => "html" },
+ hints: []
+ )
+ cache.update_columns(
+ health_status: nil,
+ health_severity: nil,
+ health_reasons: [],
+ html_bytes: 0,
+ body_bytes: 0,
+ redirected: false,
+ network_status: nil,
+ content_type: nil,
+ hint_keys: []
+ )
+
+ @task.invoke
+ cache.reload
+
+ assert_equal "healthy", cache.health_status
+ assert_equal "ok", cache.health_severity
+ assert_equal "cached".bytesize, cache.html_bytes
+ assert_equal "ok", cache.network_status
+ assert_equal "html", cache.content_type
+ end
+end
diff --git a/test/tasks/distillator_transition_task_test.rb b/test/tasks/distillator_transition_task_test.rb
new file mode 100644
index 00000000..c0736542
--- /dev/null
+++ b/test/tasks/distillator_transition_task_test.rb
@@ -0,0 +1,123 @@
+require "test_helper"
+require "rake"
+
+class DistillatorTransitionTaskTest < ActiveSupport::TestCase
+ setup do
+ Distillator::TransitionEvidence.delete_all
+ Distillator::FetchCache.delete_all
+ Website.where(seedurl: "task-runner-site").find_each(&:destroy)
+
+ Rails.application.load_tasks unless Rake::Task.task_defined?("distillator:transition:check")
+ @check_task = Rake::Task["distillator:transition:check"]
+ @preflight_task = Rake::Task["distillator:transition:preflight"]
+ @check_task.reenable
+ @preflight_task.reenable
+ end
+
+ test "task prints operator facing check summary and detail path" do
+ website = Website.create!(
+ name: "Task runner site",
+ seedurl: "task-runner-site",
+ graph_name: "https://example.org/task-runner-site",
+ default_language: "en",
+ distillator_mode: "shadow"
+ )
+ url = "https://task-runner-site.example/event"
+ website.webpages.create!(url: url, language: "en", rdf_uri: "rdf:task-runner-site", rdfs_class: rdfs_classes(:one))
+ Distillator::FetchCache.create!(
+ uri_key: CGI.escape(url),
+ normalized_url: url,
+ html: "ok",
+ body: "ok",
+ scrape_date: 1.hour.ago,
+ successful_refresh: 1.hour.ago,
+ headers: {},
+ signals: { "transport_success" => true, "content_success" => true },
+ final_url: url,
+ health_status: "healthy"
+ )
+ Distillator::TransitionCheckRunner.expects(:call).with(website: website.id).returns(
+ OpenStruct.new(website: website)
+ )
+ Distillator::TransitionCheck.expects(:call).with(website: website).returns(
+ OpenStruct.new(fetch: :passed, statements: :missing, export: :missing, status: :review)
+ )
+
+ stdout, = capture_io do
+ @check_task.invoke(website.id)
+ end
+
+ assert_includes stdout, "Fetch: Passed"
+ assert_includes stdout, "Statements: Missing"
+ assert_includes stdout, "Export: Missing"
+ assert_includes stdout, "Overall: Needs review"
+ assert_includes stdout, "Open: /distillator/shadow_report/#{website.id}"
+ end
+
+ test "preflight task prints second production readiness checks without fetching" do
+ Distillator::FetchCacheStore.expects(:fetch).never
+ Distillator::FetchService.expects(:fetch).never
+ Distillator::NativeFetch.expects(:call).never
+ Distillator::FetchShadowComparator.expects(:call).never
+ HTTParty.expects(:get).never
+ ApplicationController.helpers.stubs(:get_wringer_url_per_environment).returns("http://wringer.example")
+
+ stdout, = capture_io do
+ @preflight_task.invoke
+ end
+
+ assert_includes stdout, "Database: OK"
+ assert_includes stdout, "Queue adapter:"
+ assert_includes stdout, "Rollout modes: legacy/shadow/active available"
+ assert_includes stdout, "Default mode: legacy"
+ assert_includes stdout, "Cache table: OK"
+ assert_includes stdout, "Transition Report route: OK"
+ assert_includes stdout, "Compare route: OK"
+ assert_includes stdout, "Wringer inspection base: configured"
+ end
+
+ test "preflight task prints staging rollout mode failure when staging has invalid websites" do
+ ENV["DISTILLATOR_RUNTIME"] = "staging"
+ ApplicationController.helpers.stubs(:get_wringer_url_per_environment).returns("http://wringer.example")
+ Website.create!(
+ name: "Task preflight legacy",
+ seedurl: "task-preflight-legacy",
+ graph_name: "https://example.org/task-preflight-legacy",
+ default_language: "en",
+ distillator_mode: "legacy"
+ )
+
+ error = nil
+ stdout, = capture_io do
+ error = assert_raises(Distillator::ProductionPreflight::Failure) do
+ @preflight_task.invoke
+ end
+ end
+
+ assert_includes stdout, "Staging rollout modes: Staging requires every website to be Shadow or Active."
+ assert_includes stdout, "Preflight: FAILED"
+ assert_equal "Second-production preflight failed", error.message
+ ensure
+ ENV["DISTILLATOR_RUNTIME"] = nil
+ end
+
+ test "preflight task raises with clear failure output" do
+ failing_result = Distillator::ProductionPreflight::Result.new(
+ entries: [
+ Distillator::ProductionPreflight::Entry.new(label: "Database", value: "FAILED (boom)", ok: false)
+ ]
+ )
+ Distillator::ProductionPreflight.stubs(:call).returns(failing_result)
+
+ error = nil
+ stdout, = capture_io do
+ error = assert_raises(Distillator::ProductionPreflight::Failure) do
+ @preflight_task.invoke
+ end
+ end
+
+ assert_includes stdout, "Database: FAILED (boom)"
+ assert_includes stdout, "Preflight: FAILED"
+ assert_equal "Second-production preflight failed", error.message
+ end
+end
diff --git a/test/test_helper.rb b/test/test_helper.rb
index 7699b3c0..d681aae0 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -52,11 +52,34 @@ class ActiveSupport::TestCase
require 'mocha/minitest'
require 'webmock'
require 'vcr'
+ Dir[Rails.root.join("test/support/**/*.rb")].sort.each { |file| require file }
VCR.configure do |config|
config.cassette_library_dir = "test/vcr_cassettes"
config.hook_into :webmock
- config.allow_http_connections_when_no_cassette = true
+ # Unit tests should never hit live HTTP. Set ALLOW_TEST_HTTP=1 only for deliberate
+ # cassette recording / manual integration debugging.
+ config.allow_http_connections_when_no_cassette = ENV["ALLOW_TEST_HTTP"].present?
+ end
+
+ WebMock.disable_net_connect!(allow_localhost: false)
+
+ if ENV["REPORT_SLOW_TESTS"].present?
+ mod = Module.new do
+ def before_setup
+ @__test_started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
+ super
+ end
+
+ def after_teardown
+ elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - @__test_started_at
+ threshold = ENV.fetch("SLOW_TEST_THRESHOLD", "1.0").to_f
+ STDERR.puts "[SLOW TEST] #{self.class}##{name}: #{elapsed.round(2)}s" if elapsed > threshold
+ super
+ end
+ end
+
+ prepend mod
end
# Use transactional tests (recommended)
diff --git a/test/test_hygiene/unit_boundary_test.rb b/test/test_hygiene/unit_boundary_test.rb
new file mode 100644
index 00000000..5a96a2e9
--- /dev/null
+++ b/test/test_hygiene/unit_boundary_test.rb
@@ -0,0 +1,23 @@
+require "test_helper"
+
+class UnitBoundaryTest < ActiveSupport::TestCase
+ UNIT_FAST_FILES = %w[
+ test/helpers/statements_helper_test.rb
+ test/services/dsl_contract_test.rb
+ test/services/dsl_algorithm_runner_test.rb
+ ].freeze
+
+ test "unit-fast files do not use VCR" do
+ UNIT_FAST_FILES.each do |path|
+ source = File.read(Rails.root.join(path))
+ refute_match(/\bVCR\b/, source, "#{path} must remain unit-fast; move recorded HTTP tests to integration")
+ end
+ end
+
+ test "unit-fast files do not stub localhost wring requests directly" do
+ UNIT_FAST_FILES.each do |path|
+ source = File.read(Rails.root.join(path))
+ refute_match(/localhost:(3000|3009).*websites\/wring/, source, "#{path} should use in-memory/mocked seams, not localhost wring stubs")
+ end
+ end
+end
diff --git a/test/vcr_cassettes/StatementsHelper_complexeculturelfelixleclerc.yml b/test/vcr_cassettes/StatementsHelper_complexeculturelfelixleclerc.yml
index f291928d..3fd69dc1 100644
--- a/test/vcr_cassettes/StatementsHelper_complexeculturelfelixleclerc.yml
+++ b/test/vcr_cassettes/StatementsHelper_complexeculturelfelixleclerc.yml
@@ -2,7 +2,7 @@
http_interactions:
- request:
method: get
- uri: http://localhost:3009/websites/wring?format=raw&include_fragment=true&uri=https://www.complexeculturelfelixleclerc.com/event-details/dominic-paquet-laisse-moi-partir
+ uri: http://localhost:3000/websites/wring?format=raw&include_fragment=true&uri=https://www.complexeculturelfelixleclerc.com/event-details/dominic-paquet-laisse-moi-partir
body:
encoding: US-ASCII
string: ''
diff --git a/test/vcr_cassettes/StatementsHelper_culturecreates_com.yml b/test/vcr_cassettes/StatementsHelper_culturecreates_com.yml
index 4781d127..e3bb69bb 100644
--- a/test/vcr_cassettes/StatementsHelper_culturecreates_com.yml
+++ b/test/vcr_cassettes/StatementsHelper_culturecreates_com.yml
@@ -2,7 +2,7 @@
http_interactions:
- request:
method: get
- uri: http://localhost:3009/websites/wring?format=raw&include_fragment=true&uri=http://culturecreates.com
+ uri: http://localhost:3000/websites/wring?format=raw&include_fragment=true&uri=http://culturecreates.com
body:
encoding: US-ASCII
string: ''
@@ -19,7 +19,7 @@ http_interactions:
Accept-Language:
- en-us,en;q=0.5
Host:
- - localhost:3009
+ - localhost:3000
Connection:
- keep-alive
Keep-Alive:
diff --git a/test/vcr_cassettes/StatementsHelper_process_algorithm_url_and_json_and_ruby.yml b/test/vcr_cassettes/StatementsHelper_process_algorithm_url_and_json_and_ruby.yml
index 97fd1b91..375d4088 100644
--- a/test/vcr_cassettes/StatementsHelper_process_algorithm_url_and_json_and_ruby.yml
+++ b/test/vcr_cassettes/StatementsHelper_process_algorithm_url_and_json_and_ruby.yml
@@ -2,7 +2,7 @@
http_interactions:
- request:
method: get
- uri: http://localhost:3009/websites/wring?format=raw&include_fragment=true&uri=https://signelaval.com/fr/evenements/14650/du-fond-de-mon-garde-robe.json
+ uri: http://localhost:3000/websites/wring?format=raw&include_fragment=true&uri=https://signelaval.com/fr/evenements/14650/du-fond-de-mon-garde-robe.json
body:
encoding: US-ASCII
string: ''
@@ -19,7 +19,7 @@ http_interactions:
Accept-Language:
- en-us,en;q=0.5
Host:
- - localhost:3009
+ - localhost:3000
Connection:
- keep-alive
Keep-Alive:
diff --git a/test/vcr_cassettes/StatementsHelper_process_algorithm_url_and_xpath.yml b/test/vcr_cassettes/StatementsHelper_process_algorithm_url_and_xpath.yml
index ce775310..1aba908a 100644
--- a/test/vcr_cassettes/StatementsHelper_process_algorithm_url_and_xpath.yml
+++ b/test/vcr_cassettes/StatementsHelper_process_algorithm_url_and_xpath.yml
@@ -2,7 +2,7 @@
http_interactions:
- request:
method: get
- uri: http://localhost:3009/websites/wring?format=raw&include_fragment=true&uri=http://api.artsdata.ca
+ uri: http://localhost:3000/websites/wring?format=raw&include_fragment=true&uri=http://api.artsdata.ca
body:
encoding: US-ASCII
string: ''
@@ -19,7 +19,7 @@ http_interactions:
Accept-Language:
- en-us,en;q=0.5
Host:
- - localhost:3009
+ - localhost:3000
Connection:
- keep-alive
Keep-Alive:
diff --git a/test/vcr_cassettes/StatementsHelper_should_concatenate_2_items_from_html.yml b/test/vcr_cassettes/StatementsHelper_should_concatenate_2_items_from_html.yml
index 127be5bc..94e83e22 100644
--- a/test/vcr_cassettes/StatementsHelper_should_concatenate_2_items_from_html.yml
+++ b/test/vcr_cassettes/StatementsHelper_should_concatenate_2_items_from_html.yml
@@ -2,7 +2,7 @@
http_interactions:
- request:
method: get
- uri: http://localhost:3009/websites/wring?format=raw&include_fragment=true&uri=http://culturecreates.com
+ uri: http://localhost:3000/websites/wring?format=raw&include_fragment=true&uri=http://culturecreates.com
body:
encoding: US-ASCII
string: ''
@@ -19,7 +19,7 @@ http_interactions:
Accept-Language:
- en-us,en;q=0.5
Host:
- - localhost:3009
+ - localhost:3000
Connection:
- keep-alive
Keep-Alive:
diff --git a/test/vcr_cassettes/StatementsHelper_should_scrape_2_items_from_html.yml b/test/vcr_cassettes/StatementsHelper_should_scrape_2_items_from_html.yml
index 8336a8db..c7a84264 100644
--- a/test/vcr_cassettes/StatementsHelper_should_scrape_2_items_from_html.yml
+++ b/test/vcr_cassettes/StatementsHelper_should_scrape_2_items_from_html.yml
@@ -2,7 +2,7 @@
http_interactions:
- request:
method: get
- uri: http://localhost:3009/websites/wring?format=raw&include_fragment=true&uri=http://culturecreates.com
+ uri: http://localhost:3000/websites/wring?format=raw&include_fragment=true&uri=http://culturecreates.com
body:
encoding: US-ASCII
string: ''
@@ -19,7 +19,7 @@ http_interactions:
Accept-Language:
- en-us,en;q=0.5
Host:
- - localhost:3009
+ - localhost:3000
Connection:
- keep-alive
Keep-Alive:
diff --git a/test/vcr_cassettes/StatementsHelper_should_scrape_title_from_html.yml b/test/vcr_cassettes/StatementsHelper_should_scrape_title_from_html.yml
index 91e19b27..922ac0b3 100644
--- a/test/vcr_cassettes/StatementsHelper_should_scrape_title_from_html.yml
+++ b/test/vcr_cassettes/StatementsHelper_should_scrape_title_from_html.yml
@@ -2,7 +2,7 @@
http_interactions:
- request:
method: get
- uri: http://localhost:3009/websites/wring?format=raw&include_fragment=true&uri=http://culturecreates.com
+ uri: http://localhost:3000/websites/wring?format=raw&include_fragment=true&uri=http://culturecreates.com
body:
encoding: US-ASCII
string: ''
@@ -19,7 +19,7 @@ http_interactions:
Accept-Language:
- en-us,en;q=0.5
Host:
- - localhost:3009
+ - localhost:3000
Connection:
- keep-alive
Keep-Alive: