diff --git a/app/(iframes)/iframes/quiz/page.tsx b/app/(iframes)/iframes/quiz/page.tsx new file mode 100644 index 000000000..f1eff8978 --- /dev/null +++ b/app/(iframes)/iframes/quiz/page.tsx @@ -0,0 +1,8 @@ +import React from 'react' +import Quiz from 'components/outils/quiz/Quiz' + +const page = () => { + return +} + +export default page diff --git a/app/(public)/outils/quiz/page.tsx b/app/(public)/outils/quiz/page.tsx new file mode 100644 index 000000000..1333cc993 --- /dev/null +++ b/app/(public)/outils/quiz/page.tsx @@ -0,0 +1,32 @@ +import { Metadata } from 'next' +import React from 'react' +import QuizPage from 'components/outils/quiz/QuizPage' +import { metaDescriptions, metaTitles } from 'utils/meta' +import Suggestion from 'components/layout/Suggestion' + +export async function generateMetadata({ + searchParams, +}: { + searchParams: { [key: string]: string | string[] | undefined } +}): Promise { + const language = (searchParams.language as string) || 'fr' + return { + title: `${metaTitles.quiz[language]} | Impact CO₂`, + description: metaDescriptions.quiz[language], + openGraph: { + creators: 'ADEME', + images: 'meta/quiz.png', + }, + } +} + +const page = () => { + return ( + <> + + + + ) +} + +export default page diff --git a/npm/react/README.md b/npm/react/README.md index 31a9b3f4c..b17d748c5 100644 --- a/npm/react/README.md +++ b/npm/react/README.md @@ -262,12 +262,12 @@ Liste des équivalents à comparer parmis la liste suivante : - visioconference : Heure de visioconférence - telechargement : go de donnée - Cas pratique - - avionpny : A/R paris - New-York en avion - - tgvparis-berlin" : A/R Paris - Berlin en tgv - - tgvparis-marseille" : A/R Paris - Marseille en tgv - - voiturelille-nimes" : A/R Lille - Nîmes en voiture + - avion-pny : A/R paris - New-York en avion + - tgv-paris-berlin" : A/R Paris - Berlin en tgv + - tgv-paris-marseille" : A/R Paris - Marseille en tgv + - voiture-lille-nimes" : A/R Lille - Nîmes en voiture - francais : % de l'empreinte carbone d'un citoyen français - - gameof-thrones : épisode de game of thrones en streaming + - game-of-thrones : épisode de game of thrones en streaming - friends : intégrale de friends en streaming diff --git a/public/images/banner-quiz-cards.jpg b/public/images/banner-quiz-cards.jpg new file mode 100644 index 000000000..5837a753f Binary files /dev/null and b/public/images/banner-quiz-cards.jpg differ diff --git a/public/images/fiches.png b/public/images/fiches.png new file mode 100644 index 000000000..20b8c8c62 Binary files /dev/null and b/public/images/fiches.png differ diff --git a/public/images/ico2.svg b/public/images/ico2.svg new file mode 100644 index 000000000..16ccbca2e --- /dev/null +++ b/public/images/ico2.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/public/images/tools-quiz-end.svg b/public/images/tools-quiz-end.svg new file mode 100644 index 000000000..7393e7430 --- /dev/null +++ b/public/images/tools-quiz-end.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/images/tools-quiz.svg b/public/images/tools-quiz.svg new file mode 100644 index 000000000..245c26f83 --- /dev/null +++ b/public/images/tools-quiz.svg @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/meta/quiz.png b/public/meta/quiz.png new file mode 100644 index 000000000..c58b51744 Binary files /dev/null and b/public/meta/quiz.png differ diff --git a/src/components/base/buttons/Button.tsx b/src/components/base/buttons/Button.tsx index e1b7c4ff8..66c931c2b 100644 --- a/src/components/base/buttons/Button.tsx +++ b/src/components/base/buttons/Button.tsx @@ -1,19 +1,22 @@ import classNames from 'classnames' -import React, { ButtonHTMLAttributes, ReactNode } from 'react' +import React, { ButtonHTMLAttributes, ForwardedRef, ReactNode, forwardRef } from 'react' import buttonStyles from './Button.module.css' import linkStyles from './Link.module.css' -const Button = ({ - asLink, - className, - icon, - children, - priority, - size, - ...rest -}: { size?: 'sm' | 'lg'; priority?: 'outline'; icon?: ReactNode } & ButtonHTMLAttributes & { - asLink?: boolean - }) => { +const Button = ( + { + asLink, + className, + icon, + children, + priority, + size, + ...rest + }: { size?: 'sm' | 'lg'; priority?: 'outline'; icon?: ReactNode } & ButtonHTMLAttributes & { + asLink?: boolean + }, + ref: ForwardedRef +) => { return ( + ) +} + +export default Answer diff --git a/src/components/outils/quiz/Question.module.css b/src/components/outils/quiz/Question.module.css new file mode 100644 index 000000000..04eb2ec3a --- /dev/null +++ b/src/components/outils/quiz/Question.module.css @@ -0,0 +1,50 @@ +.container { + padding: 2rem 1.5rem; + background-color: var(--primary-10); + border-top: solid 2px var(--primary-30); + + @media screen and (max-width: 29.875rem) { + padding: 2rem 0.5rem; + } +} + +.question { + display: flex; + gap: 1rem; + align-items: stretch; + justify-content: center; + + @media screen and (max-width: 29.875rem) { + gap: 0.5rem; + } +} + +.orContainer { + display: flex; + align-items: center; + + @media screen and (max-width: 29.875rem) { + display: none; + } +} + +.or { + color: var(--primary-40); + font-size: 0.875rem; + font-weight: 700; + line-height: 1.25rem; + padding: 0.5rem 0; + border-top: 2px solid var(--primary-30); + border-bottom: 2px solid var(--primary-30); + height: fit-content; +} + +.nextButton { + margin: 1.5rem auto; +} + +.moreInfo { + border-radius: 0.75rem; + background: var(--primary-20, #C4EAE7); + padding: 1.25rem 1.5rem; +} \ No newline at end of file diff --git a/src/components/outils/quiz/Question.tsx b/src/components/outils/quiz/Question.tsx new file mode 100644 index 000000000..10547aa84 --- /dev/null +++ b/src/components/outils/quiz/Question.tsx @@ -0,0 +1,123 @@ +'use client' + +import { useTranslations } from 'next-intl' +import { SetStateAction } from 'preact/compat' +import React, { Dispatch, ForwardedRef, forwardRef, useCallback, useEffect, useMemo } from 'react' +import { computedEquivalents } from 'src/providers/equivalents' +import { Question as QuestionType } from 'types/question' +import Button from 'components/base/buttons/Button' +import FullArrowRightIcon from 'components/base/icons/full-arrow-right' +import Answer from './Answer' +import styles from './Question.module.css' + +const Question = ( + { + question, + answer, + setAnswer, + nextQuestion, + displayMore, + }: { + question: QuestionType + nextQuestion: () => void + answer: 'A' | 'B' | undefined + setAnswer: Dispatch> + displayMore: boolean + }, + nextRef: ForwardedRef +) => { + const t = useTranslations('quiz') + const equivalentA = useMemo( + () => computedEquivalents.find((equivalent) => equivalent.slug === question.slugA), + [question.slugA] + ) + const equivalentB = useMemo( + () => computedEquivalents.find((equivalent) => equivalent.slug === question.slugB), + [question.slugB] + ) + + const onKeyDown = useCallback( + (e: KeyboardEvent) => { + if (!answer) { + if (e.key === 'a') { + e.preventDefault() + setAnswer('A') + } + if (e.key === 'b') { + e.preventDefault() + setAnswer('B') + } + } + }, + [answer] + ) + + useEffect(() => { + window.addEventListener('keydown', onKeyDown) + + return () => { + window.removeEventListener('keydown', onKeyDown) + } + }, [onKeyDown]) + + return ( + equivalentA && + equivalentB && ( + <> +
+
+ setAnswer('A')} + answer={answer} + correctAnswer={question.answer} + proportion={ + question.answer === 'B' + ? 1 + : ((question.valueA || 1) * equivalentA.value) / ((question.valueB || 1) * equivalentB.value) + } + /> +
+
{t('ou')}
+
+ setAnswer('B')} + answer={answer} + correctAnswer={question.answer} + proportion={ + question.answer === 'A' + ? 1 + : ((question.valueB || 1) * equivalentB.value) / ((question.valueA || 1) * equivalentA.value) + } + /> +
+ {answer && ( +
+ +
+ {question.moreInfo} +
+
+ )} +
+ + ) + ) +} + +export default forwardRef(Question) diff --git a/src/components/outils/quiz/Quiz.tsx b/src/components/outils/quiz/Quiz.tsx new file mode 100644 index 000000000..92374d544 --- /dev/null +++ b/src/components/outils/quiz/Quiz.tsx @@ -0,0 +1,16 @@ +import React, { useMemo } from 'react' +import Shareable from 'components/shareable/Shareable' +import { overScreenQuizValues } from 'components/shareable/overScreens/Values' +import QuizSimulator from './QuizSimulator' + +const Quiz = () => { + const overScreens = useMemo(() => overScreenQuizValues(), []) + + return ( + + + + ) +} + +export default Quiz diff --git a/src/components/outils/quiz/QuizCards.tsx b/src/components/outils/quiz/QuizCards.tsx new file mode 100644 index 000000000..d7f815a2f --- /dev/null +++ b/src/components/outils/quiz/QuizCards.tsx @@ -0,0 +1,28 @@ +import Image from 'next/image' +import React from 'react' +import Card from 'components/cards/Card' +import Download from 'components/kit/Download' +import outilStyles from '../Outil.module.css' + +const QuizCards = () => { + return ( + +
+ +
+
+

Fiches du quiz

+ + Si vous souhaitez proposer le Quiz Carbone dans un autre contexte, nous mettons à disposition les fiches de + chacun des équivalents utilisés dans les questions dans un format imprimable.{' '} + + Pour obtenir les fiches, cliquez sur le bouton ci-dessous : +
+
+ +
+
+ ) +} + +export default QuizCards diff --git a/src/components/outils/quiz/QuizPage.tsx b/src/components/outils/quiz/QuizPage.tsx new file mode 100644 index 000000000..847544bce --- /dev/null +++ b/src/components/outils/quiz/QuizPage.tsx @@ -0,0 +1,56 @@ +import React, { Suspense } from 'react' +import Sources from 'components/base/Sources' +import Breadcrumbs from 'components/breadcrumbs/Breadcrumbs' +import Examples from 'components/examples/Examples' +import FAQs from 'components/faq/FAQs' +import Block from 'components/layout/Block' +import styles from '../CategoryPage.module.css' +import Quiz from './Quiz' +import QuizCards from './QuizCards' + +const QuizPage = () => { + return ( + <> + + + + + + + + + + + + + + + + ) +} + +export default QuizPage diff --git a/src/components/outils/quiz/QuizSimulator.module.css b/src/components/outils/quiz/QuizSimulator.module.css new file mode 100644 index 000000000..9bd444449 --- /dev/null +++ b/src/components/outils/quiz/QuizSimulator.module.css @@ -0,0 +1,120 @@ +.header { + padding: 1.5rem; + text-align: center; + outline: none; +} + +.question { + text-transform: uppercase; + color: var(--neutral-50); + font-size: 0.875rem; + font-weight: 500; + line-height: 1.5rem; +} + +.previousButton { + cursor: pointer; + position: absolute; + top: 1.5rem; + left: 1.5rem; + border-radius: 0.25rem; + background: var(--neutral-10); + display: flex; + padding: 0.25rem 0.5rem; + justify-content: center; + align-items: center; + gap: 0.5rem; + color: var(--neutral-50); + font-size: 0.75rem; + font-weight: 500; + line-height: 1rem; + + &:hover { + background-color: var(--neutral-20); + color: var(--neutral-60); + } + + @media screen and (max-width: 29.875rem) { + position: relative; + top: 0; + left: 0; + } +} + +.title { + font-size: 1.25rem; + line-height: 2rem; + margin: 0.5rem 0 0.75rem 0; + + b { + color: var(--neutral-80); + } +} + +.tag { + padding: 0.5rem 1.25rem; + border-radius: 0.5rem; + background-color: var(--neutral-10); + color: var(--neutral-60); + width: fit-content; + margin: auto; +} + +.correct { + color: var(--primary-60); + background-color: var(--primary-10); +} + +.missed { + color: var(--critical-60); + background-color: var(--critical-10); +} + + +.noBottom { + padding-bottom: 0; +} + +.reduced { + max-height: 36.5rem; + overflow: hidden; +} + +.moreButton { + cursor: pointer; + padding: 1rem; + color: var(--primary-60); + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + background-color: var(--neutral-00); + width: 100%; + border-top: 2px solid var(--primary-30); + font-weight: 500; + + &:hover { + background-color: var(--primary-10); + } + + &:active { + color: var(--primary-70); + } +} + +.result { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.75rem; + margin-bottom: 1.5rem; +} + +.ressources { + padding: 2rem 1.5rem; + display: flex; + flex-direction: column; + text-align: center; + gap: 1rem; + justify-content: center; +} \ No newline at end of file diff --git a/src/components/outils/quiz/QuizSimulator.tsx b/src/components/outils/quiz/QuizSimulator.tsx new file mode 100644 index 000000000..a21121df0 --- /dev/null +++ b/src/components/outils/quiz/QuizSimulator.tsx @@ -0,0 +1,187 @@ +'use client' + +import classNames from 'classnames' +import { useTranslations } from 'next-intl' +import Image from 'next/image' +import React, { useEffect, useMemo, useRef, useState } from 'react' +import Resource from 'components/base/Resource' +import Button from 'components/base/buttons/Button' +import DropdownArrowDownIcon from 'components/base/icons/dropdown-arrow-down' +import DropdownArrowUpIcon from 'components/base/icons/dropdown-arrow-up' +import FullArrowLeftIcon from 'components/base/icons/full-arrow-left' +import shareableStyles from '../../shareable/Shareable.module.css' +import Question from './Question' +import styles from './QuizSimulator.module.css' +import { questions } from './question.config' + +const QuizSimulator = () => { + const ref = useRef(null) + const nextRef = useRef(null) + + const [navigated, setNavigated] = useState(false) + const [question, setQuestion] = useState(0) + const score = useRef([]) + const [answer, setAnswer] = useState<'A' | 'B' | undefined>() + const [displayMore, setDisplayMore] = useState(false) + + const config = useMemo(() => questions[question], [question]) + const t = useTranslations('quiz') + + useEffect(() => { + if (answer && nextRef.current) { + nextRef.current.focus() + } + }, [answer, nextRef]) + + useEffect(() => { + if (displayMore && nextRef.current) { + nextRef.current.focus() + } + }, [nextRef, displayMore]) + + useEffect(() => { + if (navigated && ref.current) { + ref.current.focus() + } + }, [question, navigated]) + + return ( + <> +
+ {question > 0 && config && ( + + )} +
+ {config ? ( + <> + {t('question')} {question + 1} / 10 + + ) : ( + t('finished') + )} +
+
+ {config + ? t.rich('title') + : t.rich('score', { score: score.current.reduce((acc, current) => acc + current, 0) })} +
+ {config && ( +
+ {answer ? ( + answer === config.answer ? ( + t.rich('correct') + ) : ( + <> + {t.rich('missed')} {config.answer} ! + + ) + ) : ( + t('tag') + )} +
+ )} +
+ {config ? ( +
+ { + score.current[question] = answer === config.answer ? 1 : 0 + setNavigated(true) + setAnswer(undefined) + setQuestion(question + 1) + setDisplayMore(false) + }} + answer={answer} + setAnswer={setAnswer} + ref={nextRef} + displayMore={displayMore} + /> +
+ ) : ( + <> +
+ + +
+
+
+ {t('read-more')} + + + + +
+
+ + )} + {answer && ( + + )} + + ) +} + +export default QuizSimulator diff --git a/src/components/outils/quiz/infos/AvocatPoisson.tsx b/src/components/outils/quiz/infos/AvocatPoisson.tsx new file mode 100644 index 000000000..f67faa0f8 --- /dev/null +++ b/src/components/outils/quiz/infos/AvocatPoisson.tsx @@ -0,0 +1,33 @@ +'use client' + +import { useTranslations } from 'next-intl' +import React from 'react' +import InfographySimulator from 'components/outils/equivalents/infographies/InfographySimulator' +import Icon from './Icon' +import styles from './Infos.module.css' + +const AvocatPoisson = () => { + const t = useTranslations('quiz.avocat-poisson') + return ( + <> +
+
+ +
+ {t.rich('line-1')} +
+
+ {t.rich('line-2')} +
+
+
+
+ + + ) +} + +export default AvocatPoisson diff --git a/src/components/outils/quiz/infos/BoeufTGV.tsx b/src/components/outils/quiz/infos/BoeufTGV.tsx new file mode 100644 index 000000000..9fea8d53d --- /dev/null +++ b/src/components/outils/quiz/infos/BoeufTGV.tsx @@ -0,0 +1,33 @@ +'use client' + +import { useTranslations } from 'next-intl' +import React from 'react' +import InfographySimulator from 'components/outils/equivalents/infographies/InfographySimulator' +import Icon from './Icon' +import styles from './Infos.module.css' + +const BoeufTGV = () => { + const t = useTranslations('quiz.boeuf-tgv') + return ( + <> +
+
+ +
+ {t.rich('line-1')} +
+
+ {t('line-2')} +
+
+
+
+ + + ) +} + +export default BoeufTGV diff --git a/src/components/outils/quiz/infos/EauThe.tsx b/src/components/outils/quiz/infos/EauThe.tsx new file mode 100644 index 000000000..90d39fff0 --- /dev/null +++ b/src/components/outils/quiz/infos/EauThe.tsx @@ -0,0 +1,32 @@ +'use client' + +import { useTranslations } from 'next-intl' +import React from 'react' +import { computedEquivalents } from 'src/providers/equivalents' +import { Category } from 'types/category' +import { ComputedEquivalent } from 'types/equivalent' +import { categories } from 'data/categories' +import EquivalentSimulator from 'components/outils/equivalents/simulators/EquivalentSimulator' +import Icon from './Icon' +import styles from './Infos.module.css' + +const boisson = categories.find((category) => category.slug === 'boisson') as Category +const eau = computedEquivalents.find((equivalent) => equivalent.slug === 'eauenbouteille') as ComputedEquivalent + +const EauThe = () => { + const t = useTranslations('quiz.eau-the') + return ( + <> +
+
+ +
{t.rich('line-1')}
+
+
+
+ + + ) +} + +export default EauThe diff --git a/src/components/outils/quiz/infos/EmailSmartphone.tsx b/src/components/outils/quiz/infos/EmailSmartphone.tsx new file mode 100644 index 000000000..1dbc82f93 --- /dev/null +++ b/src/components/outils/quiz/infos/EmailSmartphone.tsx @@ -0,0 +1,37 @@ +'use client' + +import { useTranslations } from 'next-intl' +import React from 'react' +import { computedEquivalents } from 'src/providers/equivalents' +import { Category } from 'types/category' +import { ComputedEquivalent } from 'types/equivalent' +import { categories } from 'data/categories' +import EquivalentSimulator from 'components/outils/equivalents/simulators/EquivalentSimulator' +import Icon from './Icon' +import styles from './Infos.module.css' + +const numerique = categories.find((category) => category.slug === 'numerique') as Category +const smartphone = computedEquivalents.find((equivalent) => equivalent.slug === 'smartphone') as ComputedEquivalent + +const EmailSmartphone = () => { + const t = useTranslations('quiz.email-smartphone') + return ( + <> +
+
+ +
+ {t.rich('line-1')} +
+
+ {t.rich('line-2')} +
+
+
+
+ + + ) +} + +export default EmailSmartphone diff --git a/src/components/outils/quiz/infos/FriendsVoiture.tsx b/src/components/outils/quiz/infos/FriendsVoiture.tsx new file mode 100644 index 000000000..9d33582cb --- /dev/null +++ b/src/components/outils/quiz/infos/FriendsVoiture.tsx @@ -0,0 +1,37 @@ +'use client' + +import { useTranslations } from 'next-intl' +import React from 'react' +import useParamContext from 'src/providers/ParamProvider' +import Etiquette from 'components/comparateur/Etiquette' +import Icon from './Icon' +import styles from './Infos.module.css' + +const FriendsVoiture = () => { + const t = useTranslations('quiz.friends-voiture') + const { language } = useParamContext() + return ( + <> +
+
+ +
+ {t.rich('line-1')} +
+
+ {t.rich('line-2')} +
+
+
+
+ + + ) +} + +export default FriendsVoiture diff --git a/src/components/outils/quiz/infos/Icon.module.css b/src/components/outils/quiz/infos/Icon.module.css new file mode 100644 index 000000000..69d8ccdcd --- /dev/null +++ b/src/components/outils/quiz/infos/Icon.module.css @@ -0,0 +1,8 @@ +.icon { + display: flex; + padding: 0.5rem; + justify-content: center; + align-items: center; + border-radius: 50%; + background: var(--primary-10); +} \ No newline at end of file diff --git a/src/components/outils/quiz/infos/Icon.tsx b/src/components/outils/quiz/infos/Icon.tsx new file mode 100644 index 000000000..16ee8260c --- /dev/null +++ b/src/components/outils/quiz/infos/Icon.tsx @@ -0,0 +1,15 @@ +import Image from 'next/image' +import React from 'react' +import styles from './Icon.module.css' + +const Icon = () => { + return ( +
+
+ +
+
+ ) +} + +export default Icon diff --git a/src/components/outils/quiz/infos/Infos.module.css b/src/components/outils/quiz/infos/Infos.module.css new file mode 100644 index 000000000..213b2f5ad --- /dev/null +++ b/src/components/outils/quiz/infos/Infos.module.css @@ -0,0 +1,20 @@ +.container { + color: var(--primary-70); +} + +.withIcon { + display: flex; + gap: 0.75rem; +} + +.borders { + background-color: var(--neutral-00); + border-radius: 1rem; + border: 2px solid var(--primary-30); + box-shadow: 0px 2px 8px 0px rgba(0, 0, 0, 0.06); +} + +.center { + display: flex; + justify-content: center; +} \ No newline at end of file diff --git a/src/components/outils/quiz/infos/ManteauBouilloire.tsx b/src/components/outils/quiz/infos/ManteauBouilloire.tsx new file mode 100644 index 000000000..a5737f6e7 --- /dev/null +++ b/src/components/outils/quiz/infos/ManteauBouilloire.tsx @@ -0,0 +1,32 @@ +'use client' + +import { useTranslations } from 'next-intl' +import React from 'react' +import { computedEquivalents } from 'src/providers/equivalents' +import { Category } from 'types/category' +import { ComputedEquivalent } from 'types/equivalent' +import { categories } from 'data/categories' +import EquivalentSimulator from 'components/outils/equivalents/simulators/EquivalentSimulator' +import Icon from './Icon' +import styles from './Infos.module.css' + +const habillement = categories.find((category) => category.slug === 'habillement') as Category +const manteau = computedEquivalents.find((equivalent) => equivalent.slug === 'manteau') as ComputedEquivalent + +const ManteauBouilloire = () => { + const t = useTranslations('quiz.manteau-bouilloire') + return ( + <> +
+
+ +
{t.rich('line-1')}
+
+
+
+ + + ) +} + +export default ManteauBouilloire diff --git a/src/components/outils/quiz/infos/RefrigirateurOrdinateur.tsx b/src/components/outils/quiz/infos/RefrigirateurOrdinateur.tsx new file mode 100644 index 000000000..4871a1fbf --- /dev/null +++ b/src/components/outils/quiz/infos/RefrigirateurOrdinateur.tsx @@ -0,0 +1,34 @@ +'use client' + +import { useTranslations } from 'next-intl' +import React from 'react' +import { computedEquivalents } from 'src/providers/equivalents' +import { Category } from 'types/category' +import { ComputedEquivalent } from 'types/equivalent' +import { categories } from 'data/categories' +import EquivalentSimulator from 'components/outils/equivalents/simulators/EquivalentSimulator' +import Icon from './Icon' +import styles from './Infos.module.css' + +const electromenager = categories.find((category) => category.slug === 'electromenager') as Category +const refrigirateur = computedEquivalents.find( + (equivalent) => equivalent.slug === 'refrigirateur' +) as ComputedEquivalent + +const RefrigirateurOrdinateur = () => { + const t = useTranslations('quiz.refrigirateur-ordinateur') + return ( + <> +
+
+ +
{t.rich('line-1')}
+
+
+
+ + + ) +} + +export default RefrigirateurOrdinateur diff --git a/src/components/outils/quiz/infos/TabletteChaussure.tsx b/src/components/outils/quiz/infos/TabletteChaussure.tsx new file mode 100644 index 000000000..4e149a205 --- /dev/null +++ b/src/components/outils/quiz/infos/TabletteChaussure.tsx @@ -0,0 +1,37 @@ +'use client' + +import { useTranslations } from 'next-intl' +import React from 'react' +import { computedEquivalents } from 'src/providers/equivalents' +import { Category } from 'types/category' +import { ComputedEquivalent } from 'types/equivalent' +import { categories } from 'data/categories' +import EquivalentSimulator from 'components/outils/equivalents/simulators/EquivalentSimulator' +import Icon from './Icon' +import styles from './Infos.module.css' + +const numerique = categories.find((category) => category.slug === 'numerique') as Category +const tablette = computedEquivalents.find((equivalent) => equivalent.slug === 'tabletteclassique') as ComputedEquivalent + +const TabletteChaussure = () => { + const t = useTranslations('quiz.tablette-chaussures') + return ( + <> +
+
+ +
+ {t.rich('line-1')} +
+
+ {t.rich('line-2')} +
+
+
+
+ + + ) +} + +export default TabletteChaussure diff --git a/src/components/outils/quiz/infos/TelevisionPNY.tsx b/src/components/outils/quiz/infos/TelevisionPNY.tsx new file mode 100644 index 000000000..05ad87bbe --- /dev/null +++ b/src/components/outils/quiz/infos/TelevisionPNY.tsx @@ -0,0 +1,37 @@ +'use client' + +import { useTranslations } from 'next-intl' +import React from 'react' +import useParamContext from 'src/providers/ParamProvider' +import Etiquette from 'components/comparateur/Etiquette' +import Icon from './Icon' +import styles from './Infos.module.css' + +const TelevisionPNY = () => { + const t = useTranslations('quiz.television-pny') + const { language } = useParamContext() + return ( + <> +
+
+ +
+ {t.rich('line-1')} +
+
+ {t.rich('line-2')} +
+
+
+
+ + + ) +} + +export default TelevisionPNY diff --git a/src/components/outils/quiz/infos/VeloMangue.tsx b/src/components/outils/quiz/infos/VeloMangue.tsx new file mode 100644 index 000000000..5248e67d3 --- /dev/null +++ b/src/components/outils/quiz/infos/VeloMangue.tsx @@ -0,0 +1,37 @@ +'use client' + +import { useTranslations } from 'next-intl' +import React from 'react' +import { computedEquivalents } from 'src/providers/equivalents' +import { Category } from 'types/category' +import { ComputedEquivalent } from 'types/equivalent' +import { categories } from 'data/categories' +import EquivalentSimulator from 'components/outils/equivalents/simulators/EquivalentSimulator' +import Icon from './Icon' +import styles from './Infos.module.css' + +const repas = categories.find((category) => category.slug === 'repas') as Category +const mangue = computedEquivalents.find((equivalent) => equivalent.slug === 'mangue') as ComputedEquivalent + +const VeloMangue = () => { + const t = useTranslations('quiz.velo-mangue') + return ( + <> +
+
+ +
+ {t.rich('line-1')} +
+
+ {t.rich('line-2')} +
+
+
+
+ + + ) +} + +export default VeloMangue diff --git a/src/components/outils/quiz/question.config.tsx b/src/components/outils/quiz/question.config.tsx new file mode 100644 index 000000000..a06625c2a --- /dev/null +++ b/src/components/outils/quiz/question.config.tsx @@ -0,0 +1,24 @@ +import { Question } from 'types/question' +import AvocatPoisson from './infos/AvocatPoisson' +import BoeufTGV from './infos/BoeufTGV' +import EauThe from './infos/EauThe' +import EmailSmartphone from './infos/EmailSmartphone' +import FriendsVoiture from './infos/FriendsVoiture' +import ManteauBouilloire from './infos/ManteauBouilloire' +import RefrigirateurOrdinateur from './infos/RefrigirateurOrdinateur' +import TabletteChaussure from './infos/TabletteChaussure' +import TelevisionPNY from './infos/TelevisionPNY' +import VeloMangue from './infos/VeloMangue' + +export const questions: Question[] = [ + { answer: 'B', slugA: 'repasavecduboeuf', slugB: 'tgv-paris-marseille', moreInfo: }, + { answer: 'B', slugA: 'tabletteclassique', slugB: 'chaussuresencuir', valueB: 3, moreInfo: }, + { answer: 'A', slugA: 'avocat', slugB: 'repasavecdupoissonblanc', moreInfo: }, + { answer: 'B', slugA: 'refrigirateur', slugB: 'ordinateurportable', moreInfo: }, + { answer: 'B', slugA: 'eauenbouteille', slugB: 'the', valueB: 4, moreInfo: }, + { answer: 'A', slugA: 'friends', slugB: 'voiture-lille-nimes', moreInfo: }, + { answer: 'B', slugA: 'manteau', slugB: 'bouilloire', moreInfo: }, + { answer: 'A', slugA: 'television', slugB: 'avion-pny', moreInfo: }, + { answer: 'A', slugA: 'email', valueA: 1000, slugB: 'smartphone', moreInfo: }, + { answer: 'A', slugA: 'veloelectrique', valueA: 10, slugB: 'mangue', moreInfo: , last: true }, +] diff --git a/src/components/shareable/overScreens/Values.tsx b/src/components/shareable/overScreens/Values.tsx index 5831595eb..742983174 100644 --- a/src/components/shareable/overScreens/Values.tsx +++ b/src/components/shareable/overScreens/Values.tsx @@ -40,6 +40,18 @@ export const overScreenEquivalentEtiquetteValues: (equivalent: ComputedEquivalen }, } } +export const overScreenQuizValues: () => Record = () => { + return { + partager: { + title: 'share', + children: , + }, + integrer: { + title: 'integrate', + children: , + }, + } +} export const overScreenEquivalentInfographyValues: ( equivalent: ComputedEquivalent, @@ -264,7 +276,7 @@ export const overScreenCategoryValues: (category: Category) => Record { return ( {chunks}, + }} locale={language === 'fr' ? 'fr-FR' : language === 'es' ? 'es-ES' : 'en-EN'} messages={language === 'fr' ? french : language === 'es' ? spanish : english} timeZone='Europe/Paris' diff --git a/src/providers/locales/en.json b/src/providers/locales/en.json index e1194b725..15c9e7d2a 100644 --- a/src/providers/locales/en.json +++ b/src/providers/locales/en.json @@ -1,4 +1,58 @@ { + "quiz": { + "question": "Question", + "previous": "Prev.", + "title": "In your opinion, what has the least impact on the climate?", + "tag": "Choose your answer below:", + "ou": "or", + "correct": "Well done! That’s exactly right!", + "missed": "Missed... The correct answer was", + "more": "Read more", + "less": "Collapse", + "next": "Next question", + "result": "View score", + "finished": "It's finished!", + "score": "You scored {score} / 10 {score, plural, =1 {correct answer} other {correct answers}}!", + "restart": "Restart", + "read-more": "LEARN MORE", + "boeuf-tgv": { + "line-1": "The TGV is currently one of the modes of transport with the least carbon impact on the planet. Per person and per kilometer, trains pollute 8 times less than cars and 14 times less than airplanes.", + "line-2": "In contrast, beef consumption has a strong environmental impact. In fact, a meal with beef is equivalent to 14 vegetarian meals!" + }, + "tablette-chaussures": { + "line-1": "A tablet has 4x more impact than a pair of leather shoes!", + "line-2": "The carbon footprint of digital devices in general is mainly due to their manufacturing, rather than their usage, which is why it’s important to keep your tablet as long as possible." + }, + "avocat-poisson": { + "line-1": "Today, avocados are often criticized for their significant environmental impact. However, despite being an exotic fruit, the impact of 1 kg of avocados is slightly lower than that of a meal with white fish.", + "line-2": "The point is not to say ‘it’s better to eat avocado than fish’, but rather to put into perspective the relative scale of climate impact to make informed decisions." + }, + "refrigirateur-ordinateur": { + "line-1": "Household appliances also have a strong environmental impact. Like digital devices, their impact mainly lies in their manufacturing." + }, + "eau-the": { + "line-1": "A liter of bottled water has, on average, 6x more impact than a liter of tea! Its carbon footprint is primarily due to the impact of the plastic packaging and its distribution to different points of sale." + }, + "friends-voiture": { + "line-1": "Watching all 236 episodes of Friends via streaming has 50 times less environmental impact than a nearly 1000 km trip in a gasoline car.", + "line-2": "Indeed, today the transportation sector accounts for 30% of greenhouse gas emissions in France, while the digital sector only accounts for 2.5%." + }, + "manteau-bouilloire": { + "line-1": "Textiles have a significant carbon footprint on the environment, largely linked to the manufacturing phase and the extraction of raw materials. This is why it is important to take care of your clothes and make them last." + }, + "television-pny": { + "line-1": "Short, medium, and long-haul flights emit a substantial amount of CO2e.", + "line-2": "A round trip from Paris to New York emits nearly 2 tons of CO2, which is almost 20% of the annual carbon footprint of a French citizen today." + }, + "email-smartphone": { + "line-1": "Multiplied by billions of sends, the impact of emails at a global scale is necessarily significant.", + "line-2": "However, this information can divert attention from bigger issues like keeping your smartphone as long as possible. Indeed, the carbon impact of manufacturing a smartphone will always be higher than its usage." + }, + "velo-mangue": { + "line-1": "Electric bikes are part of soft mobility, which have a lower impact compared to other modes of transportation on the planet.", + "line-2": "On the contrary, the mango is one of the fruits with the highest environmental impact in France today, largely due to its air transport." + } + }, "clipboard": { "copier": "Copy", "copie": "Copied", @@ -404,10 +458,13 @@ "chauffage-bois": "Adopt wood heating", "wattris": "Simulate the electrical consumption of your home", "ngc": "Estimate your consumption carbon footprint", - "agir": "What does telecommuting change for the planet?", + "agir-tt": "What does telecommuting change for the planet?", + "agir": "Reduce your ecological footprint", "calendar": "Seasonal fruits and vegetables calendar", "livraison": "Download the guide 'E-consumer & responsible'", "leger": "How to work with a low impact ?", - "ecoresponsable": "Eco-responsible at work" + "ecoresponsable": "Eco-responsible at work", + "ico2": "Fun and educational resources on CO2 impact", + "fiches": "Download the printable quiz sheets" } } \ No newline at end of file diff --git a/src/providers/locales/es.json b/src/providers/locales/es.json index 84e1561e9..2b117ad3c 100644 --- a/src/providers/locales/es.json +++ b/src/providers/locales/es.json @@ -1,4 +1,58 @@ { + "quiz": { + "question": "Pregunta", + "previous": "Ant.", + "title": "Según tú, ¿qué tiene menos impacto en el clima?", + "tag": "Elige tu respuesta a continuación:", + "ou": "o", + "correct": "¡Bien hecho! ¡Eso es exactamente correcto!", + "missed": "Fallaste... La respuesta correcta era", + "more": "Leer más", + "less": "Reducir", + "next": "Siguiente pregunta", + "result": "Ver puntuación", + "finished": "¡Ha terminado!", + "score": "Has obtenido {score} / 10 {score, plural, =1 {respuesta correcta} other {respuestas correctas}}!", + "restart": "Reiniciar", + "read-more": "APRENDE MÁS", + "boeuf-tgv": { + "line-1": "El TGV es actualmente uno de los modos de transporte con menor impacto de carbono en el planeta. Por persona y por kilómetro, el tren contamina 8 veces menos que el coche y 14 veces menos que el avión.", + "line-2": "En cambio, el consumo de carne de res tiene un gran impacto ambiental. De hecho, ¡una comida con carne de res equivale a 14 comidas vegetarianas!" + }, + "tablette-chaussures": { + "line-1": "Una tableta tiene 4 veces más impacto que un par de zapatos de cuero.", + "line-2": "La huella de carbono de los dispositivos digitales en general se debe principalmente a su fabricación, más que a su uso, por lo que es importante conservar tu tableta el mayor tiempo posible." + }, + "avocat-poisson": { + "line-1": "Hoy en día, el aguacate a menudo es criticado por su significativo impacto ambiental. Sin embargo, a pesar de ser una fruta exótica, el impacto de 1 kg de aguacates es ligeramente menor que el de una comida con pescado blanco.", + "line-2": "No se trata de decir 'es mejor comer aguacate que pescado', sino simplemente de poner en perspectiva las magnitudes relativas del impacto climático para tomar decisiones informadas." + }, + "refrigirateur-ordinateur": { + "line-1": "Los electrodomésticos también tienen un fuerte impacto ambiental. Al igual que los dispositivos digitales, su impacto se encuentra principalmente en su fabricación." + }, + "eau-the": { + "line-1": "Un litro de agua embotellada tiene, en promedio, 6 veces más impacto que un litro de té. Su huella de carbono se debe principalmente al impacto del embalaje de plástico y a su distribución en los diferentes puntos de venta." + }, + "friends-voiture": { + "line-1": "Ver los 236 episodios de Friends en streaming tiene 50 veces menos impacto ambiental que un viaje de casi 1000 km en un coche a gasolina.", + "line-2": "De hecho, hoy el sector del transporte representa el 30% de las emisiones de gases de efecto invernadero en Francia, mientras que el sector digital solo representa el 2,5%." + }, + "manteau-bouilloire": { + "line-1": "El sector textil tiene una importante huella de carbono en el medio ambiente, en gran parte vinculada a la fase de fabricación y a la explotación de materias primas. Por ello, es importante cuidar la ropa y hacer que dure." + }, + "television-pny": { + "line-1": "Los vuelos de corto, medio y largo recorrido emiten una cantidad significativa de CO2e.", + "line-2": "Un viaje de ida y vuelta París/Nueva York emite casi 2 toneladas de CO2, lo que equivale a casi el 20% de la huella de carbono anual de un ciudadano francés hoy en día." + }, + "email-smartphone": { + "line-1": "Multiplicado por miles de millones de envíos, el impacto de los correos electrónicos a escala global es necesariamente significativo.", + "line-2": "Sin embargo, esta información puede desviar la atención de temas más importantes como mantener tu smartphone el mayor tiempo posible. De hecho, el impacto de carbono de la fabricación de un smartphone siempre será mayor que su uso." + }, + "velo-mangue": { + "line-1": "Las bicicletas eléctricas forman parte de las movilidades suaves, que tienen un impacto más bajo que otros modos de transporte en el planeta.", + "line-2": "Por el contrario, el mango es una de las frutas con mayor impacto ambiental en Francia hoy en día, debido en gran parte a su importación por avión." + } + }, "clipboard": { "copier": "Copiar", "copie": "Copiado", @@ -408,10 +462,13 @@ "chauffage-bois": "Adoptar calefacción de madera", "wattris": "Simular el consumo eléctrico de tu hogar", "ngc": "Estimar tu huella de carbono de consumo", - "agir": "¿El teletrabajo, qué cambia para el planeta?", + "agir-tt": "¿El teletrabajo, qué cambia para el planeta?", + "agir": "Reduce tu huella ecológica", "calendar": "Calendario de frutas y verduras de temporada", "livraison": "Descargar la guía “E-consumidor & responsable”", "leger": "¿Cómo trabajar con bajo impacto?", - "ecoresponsable": "Eco-responsabilidad en el trabajo" + "ecoresponsable": "Eco-responsabilidad en el trabajo", + "ico2": "Recursos divertidos y educativos sobre el impacto del CO2", + "fiches": "Descargue las hojas del cuestionario para imprimir" } } \ No newline at end of file diff --git a/src/providers/locales/fr.json b/src/providers/locales/fr.json index 401936682..d8d5b88e4 100644 --- a/src/providers/locales/fr.json +++ b/src/providers/locales/fr.json @@ -1,4 +1,58 @@ { + "quiz": { + "question": "Question", + "previous": "Prec.", + "title": "D’après vous, qu’est ce qui a le moins d’impact sur le climat ?", + "tag": "Choisissez votre réponse ci dessous :", + "ou": "ou", + "correct": "Bien joué ! C’est exactement ça !", + "missed": "Raté... C’était la réponse", + "more": "Lire la suite", + "less": "Réduire", + "next": "Question suivante", + "result": "Voir le score", + "finished": "C'est terminé !", + "score": "Vous avez obtenu {score} / 10 {score, plural, =1 {bonne réponse} other {bonnes réponses}} !", + "restart": "Recommencer", + "read-more": "POUR ALLER PLUS LOIN", + "boeuf-tgv": { + "line-1": "Le TGV est aujourd’hui est un des modes de déplacement qui a le moins d’impact carbone sur la planète. Par personne et par kilomètre, le train pollue 8 fois moins que la voiture et 14 fois moins que l’avion.", + "line-2": "A contrario, la consommation de boeuf a un fort impact sur l’environnement. En effet, un repas avec du boeuf correspond à 14 repas végétariens !" + }, + "tablette-chaussures": { + "line-1": "Une tablette a 4x plus d’impact qu’une paire de chaussures en cuir !", + "line-2": "L’impact carbone des appareils numériques en général, réside principalement dans leur fabrication, plus que dans leur usage, c’est pourquoi il est important de conserver sa tablette le plus longtemps possible." + }, + "avocat-poisson": { + "line-1": "Aujourd’hui, l’avocat est souvent montré du doigt pour son impact environnemental important. Pourtant, bien que ce soit un fruit exotique, l’impact lié à 1 kg d’avocat est légèrement inférieur à celui d’un repas avec poisson blanc.", + "line-2": "Il ne s’agit pas de dire “il vaut mieux manger de l’avocat que du poisson”, mais simplement de mettre en perspective les ordres de grandeur d’impact sur le climat afin de pouvoir prendre des décisions avisées." + }, + "refrigirateur-ordinateur": { + "line-1": "Les appareils électroménagers ont aussi un fort impact sur l’environnement. Tout comme les appareils numériques, leur impact réside principalement dans leur fabrication." + }, + "eau-the": { + "line-1": "Un litre d’eau en bouteille a en moyenne 6x plus d’impact qu’un litre de thé ! Son empreinte carbone est en majorité dû à l’impact de l’emballage plastique de la bouteille et de sa distribution dans les différents points de ventes." + }, + "friends-voiture": { + "line-1": "Regarder les 236 épisodes de Friends en streaming a 50 fois moins d’impact sur l’environnement qu’un déplacement de presque 1000 km en voiture thermique.", + "line-2": "En effet, aujourd’hui le secteur du transport représente 30% des émission de gaz à effet de serre en France, alors que le secteur du numérique seulement 2,5%." + }, + "manteau-bouilloire": { + "line-1": "Le textile a une forte empreinte carbone sur l’environnement, en grande partie liée à la phase de fabrication et l’exploitation des matières premières. C’est pourquoi il est important de prendre soin de ses vêtements et de les faire durer. " + }, + "television-pny": { + "line-1": "Les déplacements en avion, court moyen et long courrier, émettent une quantité très importante de CO2e.", + "line-2": "Un aller-retour Paris/New York émet près de 2 tonnes de CO2, soit presque 20% de l’empreinte carbone annuelle d’un citoyen français aujourd’hui." + }, + "email-smartphone": { + "line-1": "Multiplié par des milliards d’envois, l’impact des e-mails à l’échelle planétaire est nécessairement important.", + "line-2": "Pour autant, cette information peut détourner des enjeux de taille comme garder son smartphone le plus longtemps possible. En effet l’impact carbone de la fabrication d’un smartphone restera toujours plus élevé que son usage." + }, + "velo-mangue": { + "line-1": "Le vélo électrique fait partie des mobilités douces, qui ont un impact plus faible que les autres modes de déplacement sur la planète.", + "line-2": "Au contraire, la mangue est un des fruits ayant le plus d’impact sur l’environnement aujourd’hui en France, en grande partie liée à son importation en avion." + } + }, "clipboard": { "copier": "Copier", "copie": "Copié", @@ -407,10 +461,13 @@ "chauffage-bois": "Adopter le chauffage au bois", "wattris": "Simuler la consommation électrique de son logement", "ngc": "Estimer son empreinte carbone de consommation", - "agir": "Le télétravail, ça change quoi pour la planète ?", + "agir-tt": "Le télétravail, ça change quoi pour la planète ?", + "agir": "Réduire votre empreinte écologique", "calendar": "Calendrier des fruits et légumes de saison", "livraison": "Télécharger le guide “E-consommateur & responsable”", "leger": "Comment télétravailler léger ?", - "ecoresponsable": "Écoresponsable au bureau" + "ecoresponsable": "Écoresponsable au bureau", + "ico2": "Les ressources ludiques et pédagogiques sur l’impact CO2", + "fiches": "Télécharger les fiches du quizz à imprimer" } } \ No newline at end of file diff --git a/src/utils/meta.ts b/src/utils/meta.ts index 7e18f0197..3e24ad20a 100644 --- a/src/utils/meta.ts +++ b/src/utils/meta.ts @@ -4,6 +4,11 @@ export const metaDescriptions: Record> = { en: 'Compare and visualize a carbon quantity using the Impact CO2 simulator and equivalents to get a sense of the correct orders of magnitude.', es: 'Compare y visualice las cantidades de carbono utilizando el simulador Impact CO2 y sus equivalentes, para obtener los órdenes de magnitud correctos.', }, + quiz: { + fr: 'Sensibiliser et jouer avec les équivalences pour mieux se représenter les ordres de grandeur.', + en: 'Raise awareness and play with equivalences to better represent orders of magnitude.', + es: 'Sensibilizar y jugar con equivalencias para representar mejor órdenes de magnitud.', + }, transport: { fr: "Comparer l'impact carbone des déplacements en fonction de son itinéraire ou d'une distance donnée, grâce au simulateur d’Impact CO2.", en: 'Compare the carbon impact of travel based on your itinerary or a given distance using the Impact CO2 simulator.', @@ -77,6 +82,11 @@ export const metaTitles: Record> = { en: 'Carbon comparator', es: 'Comparador de carbono', }, + quiz: { + fr: 'Quiz carbone', + en: 'carbon quiz', + es: 'cuestionario de carbono', + }, transport: { fr: 'Transport', en: 'Transport', diff --git a/types/question.d.ts b/types/question.d.ts new file mode 100644 index 000000000..4f313c040 --- /dev/null +++ b/types/question.d.ts @@ -0,0 +1,11 @@ +import { ReactNode } from 'react' + +export type Question = { + answer: 'A' | 'B' + slugA: string + valueA?: number + slugB: string + valueB?: number + moreInfo: ReactNode + last?: boolean +}