diff --git a/README.md b/README.md
index b01b9ab0..ff891aac 100644
--- a/README.md
+++ b/README.md
@@ -241,3 +241,49 @@ In a platform where the number of votes determines how much attention something
Social media can be thought of as a protocol for collaboratively determining what content receives attention. Upvotes and downvotes are how the community expresses their intention. And so it's critical that the outcome of the vote be informed and fair.
-->
+
+
+# Prerequesites
+Last tested with:
+
+```shell
+$ direnv version
+2.35.0
+```
+
+```shell
+$ nix --version
+nix (Nix) 2.24.10
+```
+
+```shell
+$ devbox version
+0.13.6
+```
+
+
+# Getting started
+
+Get the global brain algorithm repo and build the global brain as shared library:
+```shell
+git clone git@github.com:social-protocols/GlobalBrain.jl.git
+cd GlobalBrain.jl
+direnv allow
+just build-shared-library
+```
+
+Checkout the actual jabble code and allow it:
+```shell
+git clone git@github.com:social-protocols/jabble.git
+cd jabble
+direnv allow
+```
+
+Afterwards you can start developing with:
+```shell
+just reset-all # creates social protocols directory
+just dev
+```
+
+This is how it can look like:
+
\ No newline at end of file
diff --git a/annotate0.html b/annotate0.html
new file mode 100644
index 00000000..bb7a0d05
--- /dev/null
+++ b/annotate0.html
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+ Annotate
+
+
+
+ Hello World
+
+ arbitrary text selection works on simple text
+
+ posted text
+
+ This
+ is
+ a
+ simple
+ text
+ to
+ experiment
+ with
+ text
+ range
+ selection
+ and
+ highlighting.
+
+ Highlight Selection
+
+
+
+
\ No newline at end of file
diff --git a/annotate1.html b/annotate1.html
new file mode 100644
index 00000000..8b867751
--- /dev/null
+++ b/annotate1.html
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+ Annotate
+
+
+
+ Hello World
+
+ arbitrary text selection breaks on text with spans, but still works for word selections but not cross word selections
+
+ posted text
+
+ This
+ is
+ a
+ simple
+ text
+ to
+ experiment
+ with
+ text
+ range
+ selection
+ and
+ highlighting.
+
+ Highlight Selection
+
+
+
+
+
\ No newline at end of file
diff --git a/annotate2.html b/annotate2.html
new file mode 100644
index 00000000..a25f36fa
--- /dev/null
+++ b/annotate2.html
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+ Annotate
+
+
+
+ Hello World
+
+ arbitrary text selection breaks on text with spans, but can be restored by using the cloneContents method
+
+ posted text
+
+ This
+ is
+ a
+ simple
+ text
+ to
+ experiment
+ with
+ text
+ range
+ selection
+ and
+ highlighting.
+
+ Highlight Selection
+
+
+
+
\ No newline at end of file
diff --git a/app/components/ui/badge.tsx b/app/components/ui/badge.tsx
new file mode 100644
index 00000000..11a49f3e
--- /dev/null
+++ b/app/components/ui/badge.tsx
@@ -0,0 +1,36 @@
+import { cva, type VariantProps } from 'class-variance-authority'
+import * as React from 'react'
+
+import { cn } from '#app/utils/misc.tsx'
+
+const badgeVariants = cva(
+ 'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
+ {
+ variants: {
+ variant: {
+ default:
+ 'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',
+ secondary:
+ 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
+ destructive:
+ 'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',
+ outline: 'text-foreground',
+ },
+ },
+ defaultVariants: {
+ variant: 'default',
+ },
+ },
+)
+
+export interface BadgeProps
+ extends React.HTMLAttributes,
+ VariantProps {}
+
+function Badge({ className, variant, ...props }: BadgeProps) {
+ return (
+
+ )
+}
+
+export { Badge, badgeVariants }
diff --git a/app/components/ui/card.tsx b/app/components/ui/card.tsx
new file mode 100644
index 00000000..f4d3c9e9
--- /dev/null
+++ b/app/components/ui/card.tsx
@@ -0,0 +1,79 @@
+import * as React from 'react'
+
+import { cn } from '#app/utils/misc.tsx'
+
+const Card = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+Card.displayName = 'Card'
+
+const CardHeader = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardHeader.displayName = 'CardHeader'
+
+const CardTitle = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardTitle.displayName = 'CardTitle'
+
+const CardDescription = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardDescription.displayName = 'CardDescription'
+
+const CardContent = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardContent.displayName = 'CardContent'
+
+const CardFooter = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardFooter.displayName = 'CardFooter'
+
+export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
diff --git a/app/components/ui/collapsible.tsx b/app/components/ui/collapsible.tsx
new file mode 100644
index 00000000..7cee61ef
--- /dev/null
+++ b/app/components/ui/collapsible.tsx
@@ -0,0 +1,9 @@
+import * as CollapsiblePrimitive from '@radix-ui/react-collapsible'
+
+const Collapsible = CollapsiblePrimitive.Root
+
+const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
+
+const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
+
+export { Collapsible, CollapsibleTrigger, CollapsibleContent }
diff --git a/app/components/ui/radio-group.tsx b/app/components/ui/radio-group.tsx
new file mode 100644
index 00000000..f640b2cd
--- /dev/null
+++ b/app/components/ui/radio-group.tsx
@@ -0,0 +1,48 @@
+import * as RadioGroupPrimitive from '@radix-ui/react-radio-group'
+import * as React from 'react'
+// import { Circle } from "lucide-react"
+
+import { cn } from '#app/utils/misc.tsx'
+import { Icon } from './icon.tsx'
+
+const RadioGroup = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => {
+ return (
+
+ )
+})
+RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
+
+const RadioGroupItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => {
+ return (
+
+
+
+ {/* */}
+
+
+ )
+})
+RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
+
+export { RadioGroup, RadioGroupItem }
diff --git a/app/components/ui/select.tsx b/app/components/ui/select.tsx
new file mode 100644
index 00000000..8e2e2811
--- /dev/null
+++ b/app/components/ui/select.tsx
@@ -0,0 +1,158 @@
+import * as SelectPrimitive from '@radix-ui/react-select'
+import * as React from 'react'
+// import { Check, ChevronDown, ChevronUp } from "lucide-react"
+
+import { cn } from '#app/utils/misc.tsx'
+
+const Select = SelectPrimitive.Root
+
+const SelectGroup = SelectPrimitive.Group
+
+const SelectValue = SelectPrimitive.Value
+
+const SelectTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+ span]:line-clamp-1',
+ className,
+ )}
+ {...props}
+ >
+ {children}
+
+ {/* */}
+
+
+))
+SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
+
+const SelectScrollUpButton = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+ {/* */}
+
+))
+SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
+
+const SelectScrollDownButton = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+ {/* */}
+
+))
+SelectScrollDownButton.displayName =
+ SelectPrimitive.ScrollDownButton.displayName
+
+const SelectContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, position = 'popper', ...props }, ref) => (
+
+
+
+
+ {children}
+
+
+
+
+))
+SelectContent.displayName = SelectPrimitive.Content.displayName
+
+const SelectLabel = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+SelectLabel.displayName = SelectPrimitive.Label.displayName
+
+const SelectItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+
+ {/* */}
+
+
+
+ {children}
+
+))
+SelectItem.displayName = SelectPrimitive.Item.displayName
+
+const SelectSeparator = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+SelectSeparator.displayName = SelectPrimitive.Separator.displayName
+
+export {
+ Select,
+ SelectGroup,
+ SelectValue,
+ SelectTrigger,
+ SelectContent,
+ SelectLabel,
+ SelectItem,
+ SelectSeparator,
+ SelectScrollUpButton,
+ SelectScrollDownButton,
+}
diff --git a/app/components/ui/toggle.tsx b/app/components/ui/toggle.tsx
new file mode 100644
index 00000000..ade200c9
--- /dev/null
+++ b/app/components/ui/toggle.tsx
@@ -0,0 +1,43 @@
+import * as TogglePrimitive from '@radix-ui/react-toggle'
+import { cva, type VariantProps } from 'class-variance-authority'
+import * as React from 'react'
+
+import { cn } from '#app/utils/misc.tsx'
+
+const toggleVariants = cva(
+ 'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 gap-2',
+ {
+ variants: {
+ variant: {
+ default: 'bg-transparent',
+ outline:
+ 'border border-input bg-transparent hover:bg-accent hover:text-accent-foreground',
+ },
+ size: {
+ default: 'h-10 px-3 min-w-10',
+ sm: 'h-9 px-2.5 min-w-9',
+ lg: 'h-11 px-5 min-w-11',
+ },
+ },
+ defaultVariants: {
+ variant: 'default',
+ size: 'default',
+ },
+ },
+)
+
+const Toggle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef &
+ VariantProps
+>(({ className, variant, size, ...props }, ref) => (
+
+))
+
+Toggle.displayName = TogglePrimitive.Root.displayName
+
+export { Toggle, toggleVariants }
diff --git a/app/root.tsx b/app/root.tsx
index 45c0b5bd..f5b1f272 100644
--- a/app/root.tsx
+++ b/app/root.tsx
@@ -298,6 +298,12 @@ function NavigationMenu() {
>
Open Discussions
+
+ Rhetoric
+
+
+ Annotate
+
{/* = {
+ anger: {
+ emoji: '😠',
+ highlightColor: 'bg-red-900 bg-opacity-80',
+ outlineColor: 'outline outline-red-500',
+ },
+ disgust: {
+ emoji: '🤢',
+ highlightColor: 'bg-green-900 bg-opacity-80',
+ outlineColor: 'outline outline-green-500',
+ },
+ fear: {
+ emoji: '😨',
+ highlightColor: 'bg-purple-900 bg-opacity-80',
+ outlineColor: 'outline outline-purple-500',
+ },
+ joy: {
+ emoji: '😊',
+ highlightColor: 'bg-yellow-900 bg-opacity-80',
+ outlineColor: 'outline outline-yellow-500',
+ },
+ sadness: {
+ emoji: '😢',
+ highlightColor: 'bg-blue-900 bg-opacity-80',
+ outlineColor: 'outline outline-blue-500',
+ },
+ surprise: {
+ emoji: '😲',
+ highlightColor: 'bg-pink-900 bg-opacity-80',
+ outlineColor: 'outline outline-pink-500',
+ },
+}
+
+export interface Range {
+ start: number
+ end: number
+}
+export interface AnnotatedRange {
+ emotions: Emotion[]
+ range: Range
+}
+
+export interface PublicAnnotatedRange {
+ emotions: Map
+ range: Range
+}
+
+export interface AnnotatedText {
+ text: string
+ annotations: AnnotatedRange[]
+}
+
+const initialPublicAnnotations: Map = new Map<
+ string,
+ PublicAnnotatedRange
+>([
+ [
+ '0-641',
+ {
+ emotions: new Map([
+ ['surprise', 101],
+ ['fear', 35],
+ ]),
+ range: { start: 0, end: 641 },
+ },
+ ],
+ [
+ '529-641',
+ {
+ emotions: new Map([
+ ['disgust', 11],
+ ['fear', 58],
+ ['sadness', 71],
+ ]),
+ range: { start: 529, end: 641 },
+ },
+ ],
+ [
+ '119-210',
+ { emotions: new Map([['sadness', 20]]), range: { start: 119, end: 210 } },
+ ],
+])
+
+export async function loadAnnotations() {
+ const filePrefix = path.join(process.cwd(), 'public', '/speech/merz/')
+ const annotationsFilePath = filePrefix + '/left-wing.json'
+ const annotationsFileContents = await fs.readFile(
+ annotationsFilePath,
+ 'utf-8',
+ )
+ const annotations = JSON.parse(annotationsFileContents) as AnnotatedRange[]
+
+ const textFilePath = filePrefix + '/speech.txt'
+ const textFileContents = await fs.readFile(textFilePath, 'utf-8')
+
+ return { annotations, text: textFileContents }
+}
+
+export const loader: LoaderFunction = async () => {
+ const { annotations, text } = await loadAnnotations()
+ return json({ annotations, text })
+}
+
+const getHighlightColor = (emotion: Emotion) => {
+ return emotionDetails[emotion].highlightColor
+}
+
+const getOutlineColor = (emotion: Emotion) => {
+ return emotionDetails[emotion].outlineColor
+}
+
+export default function OverviewPage() {
+ const data = useLoaderData<{ annotations: AnnotatedRange[]; text: string }>()
+ const [annotations, setAnnotations] = useState>(
+ initializeAnnotations(data.annotations),
+ )
+ function initializeAnnotations(
+ data: AnnotatedRange[],
+ ): Map {
+ return new Map(
+ data.map(annotation => [
+ `${annotation.range.start}-${annotation.range.end}`,
+ annotation,
+ ]),
+ )
+ }
+ const [publicAnnotations, setPublicAnnotations] = useState<
+ Map
+ >(initialPublicAnnotations)
+ const [defaultEmotion, setDefaultEmotion] = useState(
+ initialDefaultEmotion,
+ )
+ const [highlightedEmotion, setHighlightedEmotion] = useState(
+ initialDefaultEmotion,
+ )
+ const [text, setText] = useState(data.text)
+ const [isPlaying, setIsPlaying] = useState(true)
+ const [isHighlighting, setIsHighlighting] = useState(true)
+
+ useEffect(() => {
+ let interval: NodeJS.Timeout | undefined
+ if (isPlaying && isHighlighting) {
+ interval = setInterval(() => {
+ setHighlightedEmotion(prevEmotion => {
+ const currentIndex = potentialEmotions.indexOf(prevEmotion)
+ const nextIndex = (currentIndex + 1) % potentialEmotions.length
+ return potentialEmotions[nextIndex] || initialDefaultEmotion
+ })
+ }, 200)
+ } else if (!isPlaying || !isHighlighting) {
+ clearInterval(interval)
+ }
+ return () => clearInterval(interval)
+ }, [isPlaying, isHighlighting])
+
+ const handleMouseUp = () => {
+ const selection = window.getSelection()
+ if (selection && selection.rangeCount > 0) {
+ const range = selection.getRangeAt(0)
+ const start = range.startOffset
+ const end = range.endOffset
+ if (start !== end) {
+ const rangeKey = `${start}-${end}`
+ const newAnnotation: AnnotatedRange = {
+ emotions: [defaultEmotion],
+ range: { start, end },
+ }
+ setAnnotations(new Map(annotations.set(rangeKey, newAnnotation)))
+ const publicAnnotation: PublicAnnotatedRange = {
+ emotions: new Map([[defaultEmotion, 1]]),
+ range: newAnnotation.range,
+ }
+ setPublicAnnotations(
+ new Map(publicAnnotations.set(rangeKey, publicAnnotation)),
+ )
+
+ // Highlight the selected text
+ const span = document.createElement('span')
+ span.className = 'highlight'
+ const contents = range.cloneContents()
+ span.appendChild(contents)
+ range.deleteContents()
+ range.insertNode(span)
+ }
+ }
+ }
+
+ const handleDelete = (rangeKey: string) => {
+ const newAnnotations = new Map(annotations)
+ newAnnotations.delete(rangeKey)
+ setAnnotations(newAnnotations)
+ }
+
+ const handleToggleEmotion = (rangeKey: string, emotion: Emotion) => {
+ const newAnnotations = new Map(annotations)
+ const annotation = newAnnotations.get(rangeKey)
+ if (annotation) {
+ const emotionIndex = annotation.emotions.indexOf(emotion)
+ if (emotionIndex > -1) {
+ annotation.emotions.splice(emotionIndex, 1)
+ } else {
+ annotation.emotions.push(emotion)
+ }
+ setAnnotations(new Map(newAnnotations))
+ }
+ }
+
+ const handleCopyPublicAnnotation = (rangeKey: string) => {
+ const publicAnnotation = publicAnnotations.get(rangeKey)
+ if (publicAnnotation) {
+ const newAnnotation: AnnotatedRange = {
+ emotions: Array.from(publicAnnotation.emotions.keys()),
+ range: publicAnnotation.range,
+ }
+ setAnnotations(new Map(annotations.set(rangeKey, newAnnotation)))
+ }
+ }
+
+ const handleDefaultEmotionChange = (emotion: Emotion) => {
+ setDefaultEmotion(emotion)
+ setHighlightedEmotion(emotion)
+ }
+
+ const numberOfRanges = annotations.size
+ const numberOfUsedEmotions = new Set(
+ Array.from(annotations.values()).flatMap(annotation => annotation.emotions),
+ ).size
+
+ const wordHeatmap = text.split(' ').map((word, index) => {
+ const start = text.split(' ', index).join(' ').length + (index > 0 ? 1 : 0)
+ const end = start + word.length
+ let heat = 0
+
+ annotations.forEach(annotation => {
+ if (annotation.range.start <= end && annotation.range.end >= start) {
+ heat += annotation.emotions.length
+ }
+ })
+
+ publicAnnotations.forEach(publicAnnotation => {
+ if (
+ publicAnnotation.range.start <= end &&
+ publicAnnotation.range.end >= start
+ ) {
+ heat += Array.from(publicAnnotation.emotions.values()).reduce(
+ (a, b) => a + b,
+ 0,
+ )
+ }
+ })
+
+ return { word, heat }
+ })
+
+ const getHeatColor = (heat: number) => {
+ console.log(heat)
+ if (heat > 250) return 'bg-red-600'
+ if (heat > 138) return 'bg-red-500'
+ if (heat > 0) return 'bg-red-400'
+ return ''
+ }
+
+ return (
+
+
+
+
+
+ {'## Emotion Annotation'}
+
+
+ Default Emotion:
+ {potentialEmotions.map(emotion => (
+ handleDefaultEmotionChange(emotion)}
+ >
+ {emotionDetails[emotion].emoji}
+
+ ))}
+ setIsPlaying(!isPlaying)}>
+ {isPlaying ? 'Pause' : 'Play'}
+
+ setIsHighlighting(!isHighlighting)}
+ >
+ {isHighlighting
+ ? 'Disable Highlighting'
+ : 'Enable Highlighting'}
+
+
+
+ {text.split(' ').map((word, index) => {
+ const start =
+ text.split(' ', index).join(' ').length + (index > 0 ? 1 : 0)
+ const end = start + word.length
+ const isHighlighted =
+ isHighlighting &&
+ Array.from(annotations.values()).some(
+ annotation =>
+ annotation.emotions.includes(highlightedEmotion) &&
+ annotation.range.start <= end &&
+ annotation.range.end >= start,
+ )
+ return (
+
+ {word}{' '}
+
+ )
+ })}
+
+
+
+
+
+ Your Annotations (Ranges: {numberOfRanges},{' '}
+ {numberOfUsedEmotions} emotions)
+
+ {potentialEmotions.map(emotion => (
+
+ {emotionDetails[emotion].emoji}
+
+ {
+ Array.from(annotations.values()).filter(annotation =>
+ annotation.emotions.includes(emotion),
+ ).length
+ }
+
+
+ ))}
+
+
+
+
+
+
+
+ Public Annotations
+
+
+ {Array.from(publicAnnotations.entries()).map(
+ ([rangeKey, annotation]) => (
+
+
+
+ {annotation.range.start === 0 &&
+ annotation.range.end === text.length
+ ? '**full post**'
+ : '> ' +
+ text.slice(
+ annotation.range.start,
+ annotation.range.end,
+ )}
+
+
+ {Array.from(annotation.emotions.entries()).map(
+ ([emotion, count]) => (
+
+ {emotionDetails[emotion].emoji}
+ {count}
+
+ ),
+ )}
+ handleCopyPublicAnnotation(rangeKey)}
+ >
+ Agree
+
+ {/*
+ Range: {annotation.range.start} - {annotation.range.end}:
+ */}
+
+
+ ),
+ )}
+
+
+
+
+
+
+
+ )
+}
diff --git a/app/routes/rhetoric.$id.tsx b/app/routes/rhetoric.$id.tsx
new file mode 100644
index 00000000..a618075d
--- /dev/null
+++ b/app/routes/rhetoric.$id.tsx
@@ -0,0 +1,27 @@
+import { json, type LoaderFunctionArgs } from '@remix-run/node'
+import { useLoaderData } from '@remix-run/react'
+import {
+ loadRhetorics,
+ RhetoricalElementDetail,
+ type RhetoricalElement,
+} from './rhetorics.tsx'
+
+export async function loader({ params }: LoaderFunctionArgs) {
+ const id = params.id
+ if (!id) {
+ return json({ id: null })
+ }
+ const response = await loadRhetorics()
+ const rhetorics = await response.json()
+ const rhetoric = rhetorics.find(rhetoric => rhetoric.id === id)
+ if (!rhetoric) {
+ return json({ id: null })
+ }
+ return json(rhetoric)
+}
+
+export default function RhetoricPage() {
+ const rhetoric = useLoaderData() as RhetoricalElement
+
+ return
+}
diff --git a/app/routes/rhetorics.tsx b/app/routes/rhetorics.tsx
new file mode 100644
index 00000000..19703aa5
--- /dev/null
+++ b/app/routes/rhetorics.tsx
@@ -0,0 +1,324 @@
+import fs from 'fs/promises'
+import path from 'path'
+import { type LoaderFunction, json } from '@remix-run/node'
+import { Link, useLoaderData } from '@remix-run/react'
+import { Markdown } from '#app/components/markdown.tsx'
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from '#app/components/ui/card.tsx'
+
+export interface Reference {
+ title: string
+ url: string
+}
+
+export interface Score {
+ logos: number
+ pathos: number
+ ethos: number
+}
+
+export interface RhetoricalElement {
+ title: string
+ id?: string
+ prev?: string
+ next?: string
+ category: string
+ strikingExample: string
+ akas: string[]
+ description: string
+ score: Score
+ references: Reference[]
+}
+
+export async function loadRhetorics() {
+ const filePath = path.join(process.cwd(), 'public', 'rhetorics.json')
+ const fileContents = await fs.readFile(filePath, 'utf-8')
+ const rhetorics = JSON.parse(fileContents) as RhetoricalElement[]
+ const data = rhetorics.map((rhetoric, index) => {
+ const prevIdx = (index + rhetorics.length - 1) % rhetorics.length
+ const nextIdx = (index + 1) % rhetorics.length
+ if (rhetorics[nextIdx] && rhetorics[prevIdx]) {
+ rhetoric.prev = titleToSnakeCase(
+ (rhetorics[prevIdx] as RhetoricalElement).title,
+ )
+ rhetoric.next = titleToSnakeCase(
+ (rhetorics[nextIdx] as RhetoricalElement).title,
+ )
+ rhetoric.id = titleToSnakeCase(rhetoric.title)
+ return rhetoric
+ } else {
+ throw new Error(
+ 'Invalid index, prevIdx was ' +
+ prevIdx +
+ ' and nextIdx was ' +
+ nextIdx +
+ ' and total length was ' +
+ rhetorics.length +
+ ' for ' +
+ index +
+ ' rhetorics[prevIdx] evaluated to ' +
+ rhetorics[prevIdx] +
+ ' and rhetorics[nextIdx] evaluated to ' +
+ rhetorics[nextIdx],
+ )
+ }
+ })
+ return json(data)
+}
+
+export const loader: LoaderFunction = async () => {
+ return await loadRhetorics()
+}
+
+function titleToSnakeCase(title: string) {
+ return title.toLowerCase().replace(/\s/g, '-')
+}
+
+export default function OverviewPage() {
+ const data = useLoaderData()
+
+ return (
+
+
+
+
+
+ {'## Rhetorical Elements'}
+
+
+ If you are free of guilt so throw the first stone. Bad rhetorics
+ are everywhere. Make yourself aware of them. Some groups use them
+ more than others. Once you know them, you can spot them. Make
+ others accountable for their rhetorics. Even yourself.
+
+
+
+
+
+
+ {data.map((element: RhetoricalElement, index: number) => (
+
+
+
+
+ {element.title}
+
+
+ {element.description}
+
+
+
+
+ ))}
+
+
+ )
+}
+
+interface Point {
+ x: number
+ y: number
+}
+
+interface EnhancedScoreValue {
+ value: number
+ angle: number
+ base: Point
+ outer: Point
+ point: Point
+ textPos: Point
+ text: string
+ abbreviation: string
+ description: string
+}
+
+function createSpiderDiagram(size: number, score: Score) {
+ const center = size / 2
+ const radiusText = center - 5
+ const radius = radiusText * 0.9
+ const keys = Object.keys(score)
+ const total = keys.length
+ function enhance(
+ value: number,
+ pos: number,
+ text: string,
+ description: string,
+ ): EnhancedScoreValue {
+ const scale = value / 10
+ const angle = (2 * Math.PI * pos) / total
+ const baseX = Math.cos(angle)
+ const baseY = Math.sin(angle)
+ return {
+ value,
+ angle,
+ text,
+ abbreviation: text[0] ?? '',
+ description,
+ base: {
+ x: baseX,
+ y: baseY,
+ },
+ outer: {
+ x: center + baseX * radius,
+ y: center + baseY * radius,
+ },
+ point: {
+ x: center + baseX * radius * scale,
+ y: center + baseY * radius * scale,
+ },
+ textPos: {
+ x: center + baseX * radiusText,
+ y: center + baseY * radiusText,
+ },
+ }
+ }
+ const enhancedScore = {
+ logos: enhance(
+ score.logos,
+ 0,
+ 'Logos',
+ 'Try to convince through logic and evidence, appealing to reason.',
+ ),
+ pathos: enhance(
+ score.pathos,
+ 1,
+ 'Pathos',
+ 'Try to convince through emotions, appealing to feelings and values.',
+ ),
+ ethos: enhance(
+ score.ethos,
+ 2,
+ 'Ethos',
+ 'Try to convince through credibility, appealing to trust and authority.',
+ ),
+ }
+ const points = Object.values(enhancedScore)
+ .map(s => `${s.point.x},${s.point.y}`)
+ .join(' ')
+
+ return (
+
+
+
+ {Object.values(enhancedScore).map((s, index) => (
+
+ ))}
+
+ {Object.values(enhancedScore).map((s, index) => (
+
+
+ {s.text}: {s.description}
+
+ {s.abbreviation}
+
+ ))}
+
+ )
+}
+
+export function RhetoricalElementDetail(element: RhetoricalElement) {
+ return (
+
+
+
+
+ {element.title}
+
+
+ Category: {element.category}
+
+
+ Also known as: {element.akas.join(', ')}
+
+
+ Logos: {element.score.logos}
+ Pathos: {element.score.pathos}
+ Ethos: {element.score.ethos}
+
+
+
+
+ {createSpiderDiagram(100, element.score)}
+
+
+
+
+
+ {element.strikingExample}
+
+
{element.description}
+
+
+
+
+
+
+ Previous
+
+
+ Next
+
+
+
+ )
+}
diff --git a/app/styles/tailwind.css b/app/styles/tailwind.css
index 883c998a..6b46b4b0 100644
--- a/app/styles/tailwind.css
+++ b/app/styles/tailwind.css
@@ -98,7 +98,7 @@
--secondary: 217.2 20% 24%;
--secondary-foreground: 210 40% 98%;
- --accent: 217.2 32.6% 10%;
+ --accent: 217.2 32.6% 25%;
--accent-foreground: 210 40% 98%;
--destructive: 0 60% 40%;
diff --git a/justfile b/justfile
index 150118f3..cde71fb8 100644
--- a/justfile
+++ b/justfile
@@ -17,7 +17,7 @@ reset-all:
rm -rf ~/social-protocols-data/*.db
rm -rf ~/social-protocols-data/*.db-wal
rm -rf ~/social-protocols-data/*.db-shm
- mkdir -p ~/social-protocols-data
+ mkdir -p $SOCIAL_PROTOCOLS_DATADIR
rm -f "$GB_DATABASE_PATH"
just reset-db
diff --git a/package-lock.json b/package-lock.json
index 74110c1d..10bafdbc 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,11 +14,15 @@
"@johnwarden/twitter-scraper": "github:johnwarden/twitter-scraper#1ddf60604783f5d33c24b8808a8ea85f461fdc0b",
"@nasa-gcn/remix-seo": "^2.0.0",
"@radix-ui/react-checkbox": "^1.0.4",
+ "@radix-ui/react-collapsible": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-label": "^2.0.2",
+ "@radix-ui/react-radio-group": "^1.2.2",
+ "@radix-ui/react-select": "^2.1.3",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-tabs": "^1.1.0",
"@radix-ui/react-toast": "^1.1.5",
+ "@radix-ui/react-toggle": "^1.1.1",
"@radix-ui/react-tooltip": "^1.0.7",
"@react-email/components": "^0.0.12",
"@remix-run/css-bundle": "^2.9.2",
@@ -2043,6 +2047,11 @@
"node": ">=16"
}
},
+ "node_modules/@radix-ui/number": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz",
+ "integrity": "sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ=="
+ },
"node_modules/@radix-ui/primitive": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.1.tgz",
@@ -2104,6 +2113,192 @@
}
}
},
+ "node_modules/@radix-ui/react-collapsible": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.2.tgz",
+ "integrity": "sha512-PliMB63vxz7vggcyq0IxNYk8vGDrLXVWw4+W4B8YnwI1s18x7YZYqlG9PLX7XxAJUi0g2DxP4XKJMFHh/iVh9A==",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-id": "1.1.0",
+ "@radix-ui/react-presence": "1.1.2",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-use-controllable-state": "1.1.0",
+ "@radix-ui/react-use-layout-effect": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/primitive": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz",
+ "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA=="
+ },
+ "node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-compose-refs": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz",
+ "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-context": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz",
+ "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-id": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz",
+ "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-presence": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz",
+ "integrity": "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-use-layout-effect": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-primitive": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz",
+ "integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-slot": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz",
+ "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-use-callback-ref": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz",
+ "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-use-controllable-state": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz",
+ "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==",
+ "dependencies": {
+ "@radix-ui/react-use-callback-ref": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-use-layout-effect": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz",
+ "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-collection": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.3.tgz",
@@ -2462,68 +2657,21 @@
}
}
},
- "node_modules/@radix-ui/react-roving-focus": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz",
- "integrity": "sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==",
- "dependencies": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/primitive": "1.0.1",
- "@radix-ui/react-collection": "1.0.3",
- "@radix-ui/react-compose-refs": "1.0.1",
- "@radix-ui/react-context": "1.0.1",
- "@radix-ui/react-direction": "1.0.1",
- "@radix-ui/react-id": "1.0.1",
- "@radix-ui/react-primitive": "1.0.3",
- "@radix-ui/react-use-callback-ref": "1.0.1",
- "@radix-ui/react-use-controllable-state": "1.0.1"
- },
- "peerDependencies": {
- "@types/react": "*",
- "@types/react-dom": "*",
- "react": "^16.8 || ^17.0 || ^18.0",
- "react-dom": "^16.8 || ^17.0 || ^18.0"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- },
- "@types/react-dom": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-slot": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
- "integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==",
- "dependencies": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/react-compose-refs": "1.0.1"
- },
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-tabs": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.0.tgz",
- "integrity": "sha512-bZgOKB/LtZIij75FSuPzyEti/XBhJH52ExgtdVqjCIh+Nx/FW+LhnbXtbCzIi34ccyMsyOja8T0thCzoHFXNKA==",
+ "node_modules/@radix-ui/react-radio-group": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.2.2.tgz",
+ "integrity": "sha512-E0MLLGfOP0l8P/NxgVzfXJ8w3Ch8cdO6UDzJfDChu4EJDy+/WdO5LqpdY8PYnCErkmZH3gZhDL1K7kQ41fAHuQ==",
"dependencies": {
- "@radix-ui/primitive": "1.1.0",
- "@radix-ui/react-context": "1.1.0",
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
"@radix-ui/react-direction": "1.1.0",
- "@radix-ui/react-id": "1.1.0",
- "@radix-ui/react-presence": "1.1.0",
- "@radix-ui/react-primitive": "2.0.0",
- "@radix-ui/react-roving-focus": "1.1.0",
- "@radix-ui/react-use-controllable-state": "1.1.0"
+ "@radix-ui/react-presence": "1.1.2",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-roving-focus": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.1.0",
+ "@radix-ui/react-use-previous": "1.1.0",
+ "@radix-ui/react-use-size": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
@@ -2540,20 +2688,20 @@
}
}
},
- "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/primitive": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz",
- "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA=="
+ "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/primitive": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz",
+ "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA=="
},
- "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-collection": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz",
- "integrity": "sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw==",
+ "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-collection": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.1.tgz",
+ "integrity": "sha512-LwT3pSho9Dljg+wY2KN2mrrh6y3qELfftINERIzBUO9e0N+t0oMTyn3k9iv+ZqgrwGkRnLpNJrsMv9BZlt2yuA==",
"dependencies": {
- "@radix-ui/react-compose-refs": "1.1.0",
- "@radix-ui/react-context": "1.1.0",
- "@radix-ui/react-primitive": "2.0.0",
- "@radix-ui/react-slot": "1.1.0"
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-slot": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
@@ -2570,10 +2718,10 @@
}
}
},
- "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-compose-refs": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz",
- "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==",
+ "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-compose-refs": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz",
+ "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
@@ -2584,10 +2732,10 @@
}
}
},
- "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-context": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz",
- "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==",
+ "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-context": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz",
+ "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
@@ -2598,7 +2746,7 @@
}
}
},
- "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-direction": {
+ "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-direction": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz",
"integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==",
@@ -2612,7 +2760,7 @@
}
}
},
- "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-id": {
+ "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-id": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz",
"integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==",
@@ -2629,12 +2777,12 @@
}
}
},
- "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-presence": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.0.tgz",
- "integrity": "sha512-Gq6wuRN/asf9H/E/VzdKoUtT8GC9PQc9z40/vEr0VCJ4u5XvvhWIrSsCB6vD2/cH7ugTdSfYq9fLJCcM00acrQ==",
+ "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-presence": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz",
+ "integrity": "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==",
"dependencies": {
- "@radix-ui/react-compose-refs": "1.1.0",
+ "@radix-ui/react-compose-refs": "1.1.1",
"@radix-ui/react-use-layout-effect": "1.1.0"
},
"peerDependencies": {
@@ -2652,12 +2800,12 @@
}
}
},
- "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-primitive": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz",
- "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==",
+ "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-primitive": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz",
+ "integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==",
"dependencies": {
- "@radix-ui/react-slot": "1.1.0"
+ "@radix-ui/react-slot": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
@@ -2674,9 +2822,815 @@
}
}
},
- "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-roving-focus": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.0.tgz",
+ "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-roving-focus": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.1.tgz",
+ "integrity": "sha512-QE1RoxPGJ/Nm8Qmk0PxP8ojmoaS67i0s7hVssS7KuI2FQoc/uzVlZsqKfQvxPE6D8hICCPHJ4D88zNhT3OOmkw==",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-collection": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-direction": "1.1.0",
+ "@radix-ui/react-id": "1.1.0",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-use-callback-ref": "1.1.0",
+ "@radix-ui/react-use-controllable-state": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-slot": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz",
+ "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-use-callback-ref": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz",
+ "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-use-controllable-state": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz",
+ "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==",
+ "dependencies": {
+ "@radix-ui/react-use-callback-ref": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-use-layout-effect": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz",
+ "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-use-previous": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz",
+ "integrity": "sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-use-size": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz",
+ "integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-roving-focus": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz",
+ "integrity": "sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/primitive": "1.0.1",
+ "@radix-ui/react-collection": "1.0.3",
+ "@radix-ui/react-compose-refs": "1.0.1",
+ "@radix-ui/react-context": "1.0.1",
+ "@radix-ui/react-direction": "1.0.1",
+ "@radix-ui/react-id": "1.0.1",
+ "@radix-ui/react-primitive": "1.0.3",
+ "@radix-ui/react-use-callback-ref": "1.0.1",
+ "@radix-ui/react-use-controllable-state": "1.0.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0",
+ "react-dom": "^16.8 || ^17.0 || ^18.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-select": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.3.tgz",
+ "integrity": "sha512-tlLwaewTfrKetiex8iW9wwME/qrYlzlH0qcgYmos7xS54MO00SiPHasLoAykg/yVrjf41GQptPPi4oXzrP+sgg==",
+ "dependencies": {
+ "@radix-ui/number": "1.1.0",
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-collection": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-direction": "1.1.0",
+ "@radix-ui/react-dismissable-layer": "1.1.2",
+ "@radix-ui/react-focus-guards": "1.1.1",
+ "@radix-ui/react-focus-scope": "1.1.1",
+ "@radix-ui/react-id": "1.1.0",
+ "@radix-ui/react-popper": "1.2.1",
+ "@radix-ui/react-portal": "1.1.3",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-slot": "1.1.1",
+ "@radix-ui/react-use-callback-ref": "1.1.0",
+ "@radix-ui/react-use-controllable-state": "1.1.0",
+ "@radix-ui/react-use-layout-effect": "1.1.0",
+ "@radix-ui/react-use-previous": "1.1.0",
+ "@radix-ui/react-visually-hidden": "1.1.1",
+ "aria-hidden": "^1.1.1",
+ "react-remove-scroll": "2.6.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-select/node_modules/@radix-ui/primitive": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz",
+ "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA=="
+ },
+ "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-arrow": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.1.tgz",
+ "integrity": "sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w==",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.0.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-collection": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.1.tgz",
+ "integrity": "sha512-LwT3pSho9Dljg+wY2KN2mrrh6y3qELfftINERIzBUO9e0N+t0oMTyn3k9iv+ZqgrwGkRnLpNJrsMv9BZlt2yuA==",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-slot": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-compose-refs": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz",
+ "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-context": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz",
+ "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-direction": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz",
+ "integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-dismissable-layer": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.2.tgz",
+ "integrity": "sha512-kEHnlhv7wUggvhuJPkyw4qspXLJOdYoAP4dO2c8ngGuXTq1w/HZp1YeVB+NQ2KbH1iEG+pvOCGYSqh9HZOz6hg==",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-use-callback-ref": "1.1.0",
+ "@radix-ui/react-use-escape-keydown": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-focus-guards": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz",
+ "integrity": "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-focus-scope": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.1.tgz",
+ "integrity": "sha512-01omzJAYRxXdG2/he/+xy+c8a8gCydoQ1yOxnWNcRhrrBW5W+RQJ22EK1SaO8tb3WoUsuEw7mJjBozPzihDFjA==",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-use-callback-ref": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-id": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz",
+ "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-popper": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.1.tgz",
+ "integrity": "sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw==",
+ "dependencies": {
+ "@floating-ui/react-dom": "^2.0.0",
+ "@radix-ui/react-arrow": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-use-callback-ref": "1.1.0",
+ "@radix-ui/react-use-layout-effect": "1.1.0",
+ "@radix-ui/react-use-rect": "1.1.0",
+ "@radix-ui/react-use-size": "1.1.0",
+ "@radix-ui/rect": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-portal": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.3.tgz",
+ "integrity": "sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw==",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-use-layout-effect": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-primitive": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz",
+ "integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz",
+ "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-use-callback-ref": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz",
+ "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-use-controllable-state": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz",
+ "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==",
+ "dependencies": {
+ "@radix-ui/react-use-callback-ref": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-use-escape-keydown": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz",
+ "integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==",
+ "dependencies": {
+ "@radix-ui/react-use-callback-ref": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-use-layout-effect": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz",
+ "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-use-previous": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz",
+ "integrity": "sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-use-rect": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz",
+ "integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==",
+ "dependencies": {
+ "@radix-ui/rect": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-use-size": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz",
+ "integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-visually-hidden": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.1.tgz",
+ "integrity": "sha512-vVfA2IZ9q/J+gEamvj761Oq1FpWgCDaNOOIfbPVp2MVPLEomUr5+Vf7kJGwQ24YxZSlQVar7Bes8kyTo5Dshpg==",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.0.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-select/node_modules/@radix-ui/rect": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz",
+ "integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg=="
+ },
+ "node_modules/@radix-ui/react-select/node_modules/react-remove-scroll": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz",
+ "integrity": "sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==",
+ "dependencies": {
+ "react-remove-scroll-bar": "^2.3.6",
+ "react-style-singleton": "^2.2.1",
+ "tslib": "^2.1.0",
+ "use-callback-ref": "^1.3.0",
+ "use-sidecar": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-slot": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
+ "integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/react-compose-refs": "1.0.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-tabs": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.0.tgz",
+ "integrity": "sha512-bZgOKB/LtZIij75FSuPzyEti/XBhJH52ExgtdVqjCIh+Nx/FW+LhnbXtbCzIi34ccyMsyOja8T0thCzoHFXNKA==",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.0",
+ "@radix-ui/react-context": "1.1.0",
+ "@radix-ui/react-direction": "1.1.0",
+ "@radix-ui/react-id": "1.1.0",
+ "@radix-ui/react-presence": "1.1.0",
+ "@radix-ui/react-primitive": "2.0.0",
+ "@radix-ui/react-roving-focus": "1.1.0",
+ "@radix-ui/react-use-controllable-state": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/primitive": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz",
+ "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA=="
+ },
+ "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-collection": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz",
+ "integrity": "sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw==",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.0",
+ "@radix-ui/react-context": "1.1.0",
+ "@radix-ui/react-primitive": "2.0.0",
+ "@radix-ui/react-slot": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-compose-refs": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz",
+ "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-context": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz",
+ "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-direction": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz",
+ "integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-id": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz",
+ "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-presence": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.0.tgz",
+ "integrity": "sha512-Gq6wuRN/asf9H/E/VzdKoUtT8GC9PQc9z40/vEr0VCJ4u5XvvhWIrSsCB6vD2/cH7ugTdSfYq9fLJCcM00acrQ==",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.0",
+ "@radix-ui/react-use-layout-effect": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-primitive": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz",
+ "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-roving-focus": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.0.tgz",
"integrity": "sha512-EA6AMGeq9AEeQDeSH0aZgG198qkfHSbvWTf1HvoDmOB5bBG/qTxjYMWUKMnYiV6J/iP/J8MEFSuB2zRU2n7ODA==",
"dependencies": {
"@radix-ui/primitive": "1.1.0",
@@ -2800,6 +3754,119 @@
}
}
},
+ "node_modules/@radix-ui/react-toggle": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.1.tgz",
+ "integrity": "sha512-i77tcgObYr743IonC1hrsnnPmszDRn8p+EGUsUt+5a/JFn28fxaM88Py6V2mc8J5kELMWishI0rLnuGLFD/nnQ==",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-use-controllable-state": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/primitive": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz",
+ "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA=="
+ },
+ "node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/react-compose-refs": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz",
+ "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/react-primitive": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz",
+ "integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/react-slot": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz",
+ "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/react-use-callback-ref": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz",
+ "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/react-use-controllable-state": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz",
+ "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==",
+ "dependencies": {
+ "@radix-ui/react-use-callback-ref": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-tooltip": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.0.7.tgz",
@@ -7535,9 +8602,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001620",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001620.tgz",
- "integrity": "sha512-WJvYsOjd1/BYUY6SNGUosK9DUidBPDTnOARHp3fSmFO1ekdxaY6nKRttEVrfMmYi80ctS0kz1wiWmm14fVc3ew==",
+ "version": "1.0.30001679",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001679.tgz",
+ "integrity": "sha512-j2YqID/YwpLnKzCmBOS4tlZdWprXm3ZmQLBH9ZBXFOhoxLA46fwyBvx6toCBWBmnuwUY/qB3kEU6gFx8qgCroA==",
"funding": [
{
"type": "opencollective",
diff --git a/package.json b/package.json
index bd7e5bcf..cc0dae32 100644
--- a/package.json
+++ b/package.json
@@ -41,11 +41,15 @@
"@johnwarden/twitter-scraper": "github:johnwarden/twitter-scraper#1ddf60604783f5d33c24b8808a8ea85f461fdc0b",
"@nasa-gcn/remix-seo": "^2.0.0",
"@radix-ui/react-checkbox": "^1.0.4",
+ "@radix-ui/react-collapsible": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-label": "^2.0.2",
+ "@radix-ui/react-radio-group": "^1.2.2",
+ "@radix-ui/react-select": "^2.1.3",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-tabs": "^1.1.0",
"@radix-ui/react-toast": "^1.1.5",
+ "@radix-ui/react-toggle": "^1.1.1",
"@radix-ui/react-tooltip": "^1.0.7",
"@react-email/components": "^0.0.12",
"@remix-run/css-bundle": "^2.9.2",
diff --git a/public/img/jabble-running.png b/public/img/jabble-running.png
new file mode 100644
index 00000000..487e3cf2
Binary files /dev/null and b/public/img/jabble-running.png differ
diff --git a/public/rhetorics.json b/public/rhetorics.json
new file mode 100644
index 00000000..b9c33d01
--- /dev/null
+++ b/public/rhetorics.json
@@ -0,0 +1,1397 @@
+[
+ {
+ "title": "Ad Hominem",
+ "category": "Logical Fallacy",
+ "strikingExample": "You can't be serious about climate change if you drive a gas-guzzler.",
+ "akas": [
+ "Personal attack",
+ "attack against the person",
+ "ad hominem attack"
+ ],
+ "description": "Occurs when a counterargument attacks the character or personal traits of the individual making a claim, instead of addressing the argument itself. This shifts the discussion away from the claim, undermining fair debate.",
+ "score": {
+ "logos": 2,
+ "pathos": 8,
+ "ethos": 4
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/Ad_hominem"
+ },
+ {
+ "title": "Populismus Online",
+ "url": "https://populismus.online/#adhominem"
+ },
+ {
+ "title": "MAFALDA: Fallacy Detection Benchmark Study",
+ "url": "https://arxiv.org/abs/2311.09761"
+ },
+ {
+ "title": "Fallacy Files",
+ "url": "https://www.fallacyfiles.org/adhomine.html"
+ },
+ {
+ "title": "Wikipedia (DE)",
+ "url": "https://de.wikipedia.org/wiki/Argumentum_ad_hominem"
+ },
+ {
+ "title": "Your Logical Fallacy Is",
+ "url": "https://yourlogicalfallacyis.com/ad-hominem"
+ }
+ ]
+ },
+ {
+ "title": "Ad Populum",
+ "category": "Logical Fallacy",
+ "strikingExample": "Everyone loves clean energy, so wind turbines must be perfect everywhere!",
+ "akas": [
+ "Appeal to the masses",
+ "Appeal to consensus",
+ "bandwagon fallacy",
+ "appeal to popularity"
+ ],
+ "description": "Occurs when an argument claims that something is true or valid simply because it is widely accepted. This appeal to popularity undermines the strength of the claim by substituting consensus for evidence.",
+ "score": {
+ "logos": 2,
+ "pathos": 9,
+ "ethos": 2
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/Argumentum_ad_populum"
+ },
+ {
+ "title": "MAFALDA: Fallacy Detection Benchmark Study",
+ "url": "https://arxiv.org/abs/2311.09761"
+ },
+ {
+ "title": "Fallacy Files",
+ "url": "https://www.fallacyfiles.org/bandwagn.html"
+ },
+ {
+ "title": "Wikipedia (DE)",
+ "url": "https://de.wikipedia.org/wiki/Argumentum_ad_populum"
+ },
+ {
+ "title": "Your Logical Fallacy Is",
+ "url": "https://yourlogicalfallacyis.com/bandwagon"
+ }
+ ]
+ },
+ {
+ "title": "Appeal to Anger",
+ "category": "Rhetorical Trick",
+ "strikingExample": "How dare they think of ruining our scenic view with wind turbines! We must stop this nonsense!",
+ "akas": [
+ "Appeal to Emotion",
+ "Appeal to Rage",
+ "Argumentum ad Iracundiam"
+ ],
+ "description": "Occurs when an argument attempts to persuade by evoking anger or outrage, rather than presenting logical reasoning. The claim is framed to provoke emotional reactions, making it difficult for rational counterarguments to take hold.",
+ "score": {
+ "logos": 2,
+ "pathos": 9,
+ "ethos": 3
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/Appeal_to_emotion#Appeal_to_anger"
+ },
+ {
+ "title": "MAFALDA: Fallacy Detection Benchmark Study",
+ "url": "https://arxiv.org/abs/2311.09761"
+ },
+ {
+ "title": "Fallacy Files",
+ "url": "https://www.fallacyfiles.org/emotiona.html"
+ },
+ {
+ "title": "Populismus Online",
+ "url": "https://populismus.online/#appealtoanger"
+ },
+ {
+ "title": "Your Logical Fallacy Is",
+ "url": "https://yourlogicalfallacyis.com/appeal-to-emotion"
+ }
+ ]
+ },
+ {
+ "title": "Appeal to Authority",
+ "category": "Rhetorical Trick",
+ "strikingExample": "My favorite celebrity says wind turbines ruin the landscape, so they must be bad.",
+ "akas": [
+ "Appeal to (False) Authority",
+ "Argument from Authority",
+ "Celebrity Endorsement Fallacy"
+ ],
+ "description": "Occurs when an argument relies on the authority of someone who is not an expert in the relevant field, using their reputation to support the claim rather than presenting sound evidence. This weakens the claim by substituting the authority’s status for proper reasoning or data.",
+ "score": {
+ "logos": 3,
+ "pathos": 4,
+ "ethos": 8
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/Argument_from_authority"
+ },
+ {
+ "title": "MAFALDA: Fallacy Detection Benchmark Study",
+ "url": "https://arxiv.org/abs/2311.09761"
+ },
+ {
+ "title": "Fallacy Files",
+ "url": "https://www.fallacyfiles.org/authorit.html"
+ },
+ {
+ "title": "Wikipedia (DE)",
+ "url": "https://de.wikipedia.org/wiki/Autorit%C3%A4tsargument"
+ },
+ {
+ "title": "Your Logical Fallacy Is",
+ "url": "https://yourlogicalfallacyis.com/appeal-to-authority"
+ }
+ ]
+ },
+ {
+ "title": "Appeal to Consequences",
+ "category": "Rhetorical Trick",
+ "strikingExample": "If climate change is real, it will ruin the economy, so it can't be true.",
+ "akas": [
+ "Appeal to outcome",
+ "Argument from consequences",
+ "Consequentialism fallacy"
+ ],
+ "description": "Occurs when an argument claims that the truth or falsity of a proposition depends on the consequences of accepting it. This rhetorical trick appeals to fears or hopes about outcomes, rather than evidence for or against the proposition itself.",
+ "score": {
+ "logos": 4,
+ "pathos": 7,
+ "ethos": 3
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/Consequentialism"
+ }
+ ]
+ },
+ {
+ "title": "Appeal to Fear",
+ "category": "Logical Fallacy",
+ "strikingExample": "Wind turbines will lower property values and harm our health.",
+ "akas": [
+ "Appeal to consequence",
+ "scare tactic",
+ "alarmist argument"
+ ],
+ "description": "Occurs when an argument attempts to persuade by evoking fear or anxiety, rather than presenting logical reasoning. The claim is presented alongside exaggerated negative consequences, discouraging rational counterarguments.",
+ "score": {
+ "logos": 2,
+ "pathos": 9,
+ "ethos": 3
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/Appeal_to_fear"
+ },
+ {
+ "title": "MAFALDA: Fallacy Detection Benchmark Study",
+ "url": "https://arxiv.org/abs/2311.09761"
+ },
+ {
+ "title": "Fallacy Files",
+ "url": "https://www.fallacyfiles.org/emotiona.html"
+ },
+ {
+ "title": "Wikipedia (DE)",
+ "url": "https://de.wikipedia.org/wiki/Argumentum_ad_metur"
+ },
+ {
+ "title": "Your Logical Fallacy Is",
+ "url": "https://yourlogicalfallacyis.com/appeal-to-emotion"
+ }
+ ]
+ },
+ {
+ "title": "Appeal to Ignorance",
+ "category": "Rhetorical Trick",
+ "strikingExample": "We’ve never seen evidence of aliens, so they must not exist.",
+ "akas": [
+ "Argument from ignorance",
+ "Argumentum ad ignorantiam"
+ ],
+ "description": "Occurs when an argument claims that something is true because it has not been proven false, or vice versa. This rhetorical trick exploits the lack of evidence as support for a claim, rather than providing actual proof.",
+ "score": {
+ "logos": 4,
+ "pathos": 5,
+ "ethos": 3
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/Argument_from_ignorance"
+ }
+ ]
+ },
+ {
+ "title": "Appeal to Nature",
+ "category": "Rhetorical Trick",
+ "strikingExample": "Wind power is natural, so it must be the best choice for energy everywhere.",
+ "akas": [
+ "Appeal to Naturalism",
+ "Naturalistic Fallacy",
+ "Argument from Nature"
+ ],
+ "description": "Occurs when an argument claims something is better or more valid simply because it is perceived as 'natural.' This weakens the claim by assuming that anything natural is inherently good, without considering evidence or context.",
+ "score": {
+ "logos": 3,
+ "pathos": 6,
+ "ethos": 3
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/Appeal_to_nature"
+ },
+ {
+ "title": "MAFALDA: Fallacy Detection Benchmark Study",
+ "url": "https://arxiv.org/abs/2311.09761"
+ },
+ {
+ "title": "Fallacy Files",
+ "url": "https://www.fallacyfiles.org/adnature.html"
+ },
+ {
+ "title": "Wikipedia (DE)",
+ "url": "https://de.wikipedia.org/wiki/Argumentum_ad_naturam"
+ },
+ {
+ "title": "Your Logical Fallacy Is",
+ "url": "https://yourlogicalfallacyis.com/appeal-to-nature"
+ }
+ ]
+ },
+ {
+ "title": "Appeal to Novelty",
+ "category": "Rhetorical Trick",
+ "strikingExample": "This new method must be better because it's modern and innovative.",
+ "akas": [
+ "Argumentum ad novitatem",
+ "Newer is better fallacy"
+ ],
+ "description": "Occurs when an argument suggests that something is better or more correct simply because it is new or modern. This rhetorical trick equates novelty with truth or improvement without providing substantive reasoning.",
+ "score": {
+ "logos": 3,
+ "pathos": 6,
+ "ethos": 3
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/Appeal_to_novelty"
+ }
+ ]
+ },
+ {
+ "title": "Appeal to Pity",
+ "category": "Rhetorical Trick",
+ "strikingExample": "Sure, solar farms are great, but think of the families who might lose their beautiful view!",
+ "akas": [
+ "Appeal to sympathy",
+ "argumentum ad misericordiam",
+ "emotional appeal"
+ ],
+ "description": "Occurs when an argument seeks to win support by evoking sympathy or emotional pity rather than presenting factual evidence. This rhetorical trick uses emotional appeals to divert attention from the actual merits of the claim, making it difficult to engage in a rational counterargument.",
+ "score": {
+ "logos": 2,
+ "pathos": 9,
+ "ethos": 3
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/Appeal_to_pity"
+ },
+ {
+ "title": "MAFALDA: Fallacy Detection Benchmark Study",
+ "url": "https://arxiv.org/abs/2311.09761"
+ },
+ {
+ "title": "Fallacy Files",
+ "url": "https://www.fallacyfiles.org/emotiona.html"
+ },
+ {
+ "title": "Wikipedia (DE)",
+ "url": "https://de.wikipedia.org/wiki/Argumentum_ad_misericordiam"
+ },
+ {
+ "title": "Your Logical Fallacy Is",
+ "url": "https://yourlogicalfallacyis.com/appeal-to-emotion"
+ }
+ ]
+ },
+ {
+ "title": "Appeal to Positive Emotion",
+ "category": "Rhetorical Trick",
+ "strikingExample": "Wind turbines will save the planet, create thousands of jobs, and secure a brighter future for everyone!",
+ "akas": [
+ "Appeal to Joy",
+ "Emotional Appeal",
+ "Appeal to Happiness"
+ ],
+ "description": "Occurs when an argument seeks to persuade by triggering positive emotions like joy or nostalgia, rather than providing solid reasoning or evidence. The claim may seem appealing due to the emotional response, but it lacks the logical foundation needed for sound argumentation.",
+ "score": {
+ "logos": 3,
+ "pathos": 8,
+ "ethos": 3
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/Appeal_to_emotion"
+ },
+ {
+ "title": "MAFALDA: Fallacy Detection Benchmark Study",
+ "url": "https://arxiv.org/abs/2311.09761"
+ },
+ {
+ "title": "Fallacy Files",
+ "url": "https://www.fallacyfiles.org/emotiona.html"
+ },
+ {
+ "title": "Your Logical Fallacy Is",
+ "url": "https://yourlogicalfallacyis.com/appeal-to-emotion"
+ }
+ ]
+ },
+ {
+ "title": "Appeal to Ridicule",
+ "category": "Rhetorical Trick",
+ "strikingExample": "Oh sure, let's definitely build a public train system, as long as it doesn't run near my house!",
+ "akas": [
+ "Appeal to Mockery",
+ "Appeal to Mock",
+ "Ridicule Fallacy"
+ ],
+ "description": "Occurs when an argument mocks or dismisses an opposing claim rather than engaging with its reasoning. This rhetorical trick avoids countering the claim directly by provoking laughter or scorn, diverting attention from valid counterarguments.",
+ "score": {
+ "logos": 2,
+ "pathos": 7,
+ "ethos": 3
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/Appeal_to_ridicule"
+ },
+ {
+ "title": "MAFALDA: Fallacy Detection Benchmark Study",
+ "url": "https://arxiv.org/abs/2311.09761"
+ },
+ {
+ "title": "Fallacy Files",
+ "url": "https://www.fallacyfiles.org/adbacula.html"
+ },
+ {
+ "title": "Wikipedia (DE)",
+ "url": "https://de.wikipedia.org/wiki/Argumentum_ad_ridiculum"
+ }
+ ]
+ },
+ {
+ "title": "Appeal to Tradition",
+ "category": "Logical Fallacy",
+ "strikingExample": "We’ve always had the festival here, so it must stay.",
+ "akas": [
+ "Appeal to antiquity",
+ "traditionalist fallacy"
+ ],
+ "description": "Occurs when an argument claims something is right or better simply because it has been traditionally accepted or practiced. This rhetorical trick weakens the argument by relying on historical precedent instead of evidence or logical reasoning to support the claim.",
+ "score": {
+ "logos": 3,
+ "pathos": 5,
+ "ethos": 4
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/Appeal_to_tradition"
+ },
+ {
+ "title": "MAFALDA: Fallacy Detection Benchmark Study",
+ "url": "https://arxiv.org/abs/2311.09761"
+ },
+ {
+ "title": "Fallacy Files",
+ "url": "https://www.fallacyfiles.org/adtradit.html"
+ },
+ {
+ "title": "Wikipedia (DE)",
+ "url": "https://de.wikipedia.org/wiki/Traditionsargument"
+ }
+ ]
+ },
+ {
+ "title": "Appeal to Worse Problems",
+ "category": "Rhetorical Trick",
+ "strikingExample": "Why worry about local pollution when climate change is the bigger threat?",
+ "akas": [
+ "Relative privation",
+ "Not as bad as fallacy",
+ "Comparative problem fallacy"
+ ],
+ "description": "Occurs when a claim is dismissed by pointing to a larger or more severe problem, suggesting the current issue is unimportant. This rhetorical trick sidesteps addressing the argument directly by shifting focus away from the immediate concern.",
+ "score": {
+ "logos": 3,
+ "pathos": 7,
+ "ethos": 2
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/Relative_privation"
+ },
+ {
+ "title": "MAFALDA: Fallacy Detection Benchmark Study",
+ "url": "https://arxiv.org/abs/2311.09761"
+ },
+ {
+ "title": "Fallacy Files",
+ "url": "https://www.fallacyfiles.org/redherrf.html"
+ },
+ {
+ "title": "Populismus Online",
+ "url": "https://populismus.online/#appealzuworseproblems"
+ }
+ ]
+ },
+ {
+ "title": "Burden of Proof",
+ "category": "Logical Fallacy",
+ "strikingExample": "You can’t prove that ghosts don't exist, so they must be real.",
+ "akas": [
+ "Shifting the burden of proof",
+ "onus probandi"
+ ],
+ "description": "Occurs when someone claims that their position is true unless it is proven false, placing the responsibility of proof on the opposing side. In rational debate, the burden of proof lies with the person making the claim.",
+ "score": {
+ "logos": 6,
+ "pathos": 4,
+ "ethos": 3
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/Burden_of_proof_(philosophy)"
+ },
+ {
+ "title": "Your Logical Fallacy Is",
+ "url": "https://yourlogicalfallacyis.com/burden-of-proof"
+ }
+ ]
+ },
+ {
+ "title": "Causal Oversimplification",
+ "category": "Logical Fallacy",
+ "strikingExample": "Wind farms are ugly, so they must be bad for the environment.",
+ "akas": [
+ "Causal reductionism",
+ "oversimplified causation",
+ "causal fallacy"
+ ],
+ "description": "Occurs when a complex problem is reduced to a single cause, overlooking other contributing factors, which weakens the overall argument. This type of argument often ignores the full scope of an issue, leading to incomplete conclusions.",
+ "score": {
+ "logos": 4,
+ "pathos": 5,
+ "ethos": 2
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/Causal_sufficiency"
+ },
+ {
+ "title": "MAFALDA: Fallacy Detection Benchmark Study",
+ "url": "https://arxiv.org/abs/2311.09761"
+ },
+ {
+ "title": "Fallacy Files",
+ "url": "https://www.fallacyfiles.org/noncause.html"
+ },
+ {
+ "title": "Wikipedia (DE)",
+ "url": "https://de.wikipedia.org/wiki/Monokausalit%C3%A4t"
+ }
+ ]
+ },
+ {
+ "title": "Cherry Picking",
+ "category": "Rhetorical Trick",
+ "strikingExample": "Look at this one cold winter, global warming can't be real!",
+ "akas": [
+ "Suppressing evidence",
+ "Fallacy of incomplete evidence",
+ "The Texas Sharpshooter",
+ "Selective evidence fallacy"
+ ],
+ "description": "Occurs when an argument selectively presents only favorable evidence, while ignoring evidence that contradicts the claim. This rhetorical trick creates a misleading or incomplete picture of reality.",
+ "score": {
+ "logos": 4,
+ "pathos": 6,
+ "ethos": 3
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/Cherry_picking"
+ },
+ {
+ "title": "Fallacy Files",
+ "url": "https://www.fallacyfiles.org/onesided.html"
+ },
+ {
+ "title": "Wikipedia (DE)",
+ "url": "https://de.wikipedia.org/wiki/Best%C3%A4tigungsfehler"
+ }
+ ]
+ },
+ {
+ "title": "Circular Reasoning",
+ "category": "Rhetorical Trick",
+ "strikingExample": "Wind turbines are essential for clean energy because they provide renewable power, and renewable power is vital for a sustainable future.",
+ "akas": [
+ "Begging the question",
+ "circular logic",
+ "tautological reasoning"
+ ],
+ "description": "Occurs when an argument’s conclusion is used as a premise to support itself, resulting in a loop that fails to provide new evidence or reasoning. This weakens the argument by relying solely on the claim rather than offering external support.",
+ "score": {
+ "logos": 5,
+ "pathos": 4,
+ "ethos": 3
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/Circular_reasoning"
+ },
+ {
+ "title": "MAFALDA: Fallacy Detection Benchmark Study",
+ "url": "https://arxiv.org/abs/2311.09761"
+ },
+ {
+ "title": "Fallacy Files",
+ "url": "https://www.fallacyfiles.org/begquest.html"
+ },
+ {
+ "title": "Wikipedia (DE)",
+ "url": "https://de.wikipedia.org/wiki/Zirkelschluss"
+ },
+ {
+ "title": "Your Logical Fallacy Is",
+ "url": "https://yourlogicalfallacyis.com/begging-the-question"
+ }
+ ]
+ },
+ {
+ "title": "Demonization",
+ "category": "Rhetorical Trick",
+ "strikingExample": "Anyone who supports this policy is clearly a traitor!",
+ "akas": [
+ "Villainizing",
+ "Scapegoating",
+ "Maligning fallacy"
+ ],
+ "description": "Occurs when an argument portrays an individual, group, or idea as wicked or evil, rather than addressing the merits of the argument itself. This rhetorical trick stirs emotions and biases, detracting from rational discourse.",
+ "score": {
+ "logos": 2,
+ "pathos": 9,
+ "ethos": 3
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/Demonization"
+ }
+ ]
+ },
+ {
+ "title": "Doomism",
+ "category": "Rhetorical Trick",
+ "strikingExample": "No matter what we do, the planet is doomed. So why bother?",
+ "akas": [
+ "Defeatism fallacy",
+ "Nihilism fallacy",
+ "Hopelessness fallacy"
+ ],
+ "description": "Occurs when an argument suggests that a situation is so bad that nothing can be done to improve it, leading to a defeatist or nihilistic attitude. This rhetorical trick undermines constructive problem-solving by promoting despair and inaction.",
+ "score": {
+ "logos": 2,
+ "pathos": 9,
+ "ethos": 2
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/Doomism"
+ }
+ ]
+ },
+ {
+ "title": "Equivocation",
+ "category": "Rhetorical Trick",
+ "strikingExample": "The word 'green' means energy-efficient, so my gas-powered car must be green too.",
+ "akas": [
+ "Ambiguity",
+ "double meaning",
+ "misleading nuance"
+ ],
+ "description": "Occurs when an argument uses ambiguous language to shift the meaning of a key term in a claim. This rhetorical trick relies on the dual meanings of words to mislead the audience and obscure the actual issue being debated.",
+ "score": {
+ "logos": 4,
+ "pathos": 5,
+ "ethos": 3
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/Equivocation"
+ },
+ {
+ "title": "MAFALDA: Fallacy Detection Benchmark Study",
+ "url": "https://arxiv.org/abs/2311.09761"
+ },
+ {
+ "title": "Fallacy Files",
+ "url": "https://www.fallacyfiles.org/equivoqu.html"
+ },
+ {
+ "title": "Wikipedia (DE)",
+ "url": "https://de.wikipedia.org/wiki/%C3%84quivokation"
+ }
+ ]
+ },
+ {
+ "title": "Fallacy of Division",
+ "category": "Rhetorical Trick",
+ "strikingExample": "Wind power is good for the planet, so every wind farm must be beneficial too!",
+ "akas": [
+ "Fallacy of incomplete evidence",
+ "part-whole fallacy",
+ "generalization fallacy"
+ ],
+ "description": "Occurs when an argument assumes that what is true for a whole must also be true for its individual parts. This leads to unsupported claims about smaller components based on the characteristics of the entire group, weakening the argument.",
+ "score": {
+ "logos": 5,
+ "pathos": 4,
+ "ethos": 3
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/Fallacy_of_division"
+ },
+ {
+ "title": "MAFALDA: Fallacy Detection Benchmark Study",
+ "url": "https://arxiv.org/abs/2311.09761"
+ },
+ {
+ "title": "Fallacy Files",
+ "url": "https://www.fallacyfiles.org/division.html"
+ },
+ {
+ "title": "Wikipedia (DE)",
+ "url": "https://de.wikipedia.org/wiki/Divisionsfehlschluss"
+ },
+ {
+ "title": "Your Logical Fallacy Is",
+ "url": "https://yourlogicalfallacyis.com/composition-division"
+ }
+ ]
+ },
+ {
+ "title": "False Analogy",
+ "category": "Logical Fallacy",
+ "strikingExample": "We need wind turbines like we need hospitals in every backyard.",
+ "akas": [
+ "False comparison",
+ "misleading analogy",
+ "weak analogy"
+ ],
+ "description": "Occurs when an argument draws an invalid comparison between two unrelated situations, assuming that because they share one characteristic, they are alike in other ways. This weakens the claim by oversimplifying the comparison.",
+ "score": {
+ "logos": 4,
+ "pathos": 6,
+ "ethos": 3
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/False_analogy"
+ },
+ {
+ "title": "MAFALDA: Fallacy Detection Benchmark Study",
+ "url": "https://arxiv.org/abs/2311.09761"
+ },
+ {
+ "title": "Fallacy Files",
+ "url": "https://www.fallacyfiles.org/wanalogy.html"
+ },
+ {
+ "title": "Wikipedia (DE)",
+ "url": "https://de.wikipedia.org/wiki/Falscher_Vergleich"
+ }
+ ]
+ },
+ {
+ "title": "False Balance",
+ "category": "Rhetorical Trick",
+ "strikingExample": "Climate scientists say the Earth is warming, but flat-Earthers deserve equal time in the debate.",
+ "akas": [
+ "Both sides fallacy",
+ "Equal time fallacy",
+ "False equivalency fallacy"
+ ],
+ "description": "Occurs when an argument gives equal weight to two sides of a debate, even when one side lacks merit or evidence. This rhetorical trick creates a false sense of equivalence between opposing viewpoints, often obscuring the truth.",
+ "score": {
+ "logos": 3,
+ "pathos": 6,
+ "ethos": 4
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/False_balance"
+ }
+ ]
+ },
+ {
+ "title": "False Causality",
+ "category": "Logical Fallacy",
+ "strikingExample": "Crime dropped after the park was built, so parks prevent crime.",
+ "akas": [
+ "Post hoc reasoning",
+ "false cause",
+ "causation fallacy",
+ "questionable cause"
+ ],
+ "description": "Occurs when an argument assumes that because one event follows another, the first must have caused the second. This oversimplifies the relationship between events, leading to faulty conclusions about cause and effect in the claim.",
+ "score": {
+ "logos": 4,
+ "pathos": 5,
+ "ethos": 2
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/Post_hoc_ergo_propter_hoc"
+ },
+ {
+ "title": "MAFALDA: Fallacy Detection Benchmark Study",
+ "url": "https://arxiv.org/abs/2311.09761"
+ },
+ {
+ "title": "Fallacy Files",
+ "url": "https://www.fallacyfiles.org/posthocf.html"
+ },
+ {
+ "title": "Wikipedia (DE)",
+ "url": "https://de.wikipedia.org/wiki/Fehlschluss"
+ },
+ {
+ "title": "Your Logical Fallacy Is",
+ "url": "https://yourlogicalfallacyis.com/false-cause"
+ }
+ ]
+ },
+ {
+ "title": "False Dilemma",
+ "category": "Logical Fallacy",
+ "strikingExample": "We either build this highway or stay stuck in traffic forever.",
+ "akas": [
+ "False dichotomy",
+ "either-or fallacy",
+ "black-and-white thinking"
+ ],
+ "description": "Occurs when an argument presents only two extreme options, ignoring other viable alternatives. This simplification of the claim forces a choice between two extremes, limiting the scope of the discussion and preventing consideration of other possibilities.",
+ "score": {
+ "logos": 4,
+ "pathos": 7,
+ "ethos": 2
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/False_dilemma"
+ },
+ {
+ "title": "Populismus Online",
+ "url": "https://populismus.online/#falschesdilemma"
+ },
+ {
+ "title": "MAFALDA: Fallacy Detection Benchmark Study",
+ "url": "https://arxiv.org/abs/2311.09761"
+ },
+ {
+ "title": "Fallacy Files",
+ "url": "https://www.fallacyfiles.org/eitheror.html"
+ },
+ {
+ "title": "Wikipedia (DE)",
+ "url": "https://de.wikipedia.org/wiki/Entweder-oder-Irrtum"
+ },
+ {
+ "title": "Your Logical Fallacy Is",
+ "url": "https://yourlogicalfallacyis.com/black-or-white"
+ }
+ ]
+ },
+ {
+ "title": "False Equivalence",
+ "category": "Rhetorical Trick",
+ "strikingExample": "Banning straws is like banning all plastic.",
+ "akas": [
+ "False analogy of equality",
+ "Equivocation of equals"
+ ],
+ "description": "Occurs when an argument incorrectly assumes that two things are morally or logically equivalent, even though they are not. This rhetorical trick blurs important distinctions and weakens critical analysis.",
+ "score": {
+ "logos": 3,
+ "pathos": 6,
+ "ethos": 3
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/False_equivalence"
+ }
+ ]
+ },
+ {
+ "title": "Genetic Fallacy",
+ "category": "Logical Fallacy",
+ "strikingExample": "We shouldn't trust this scientific study because it was funded by a corporation.",
+ "akas": [
+ "Origin fallacy",
+ "fallacy of origins"
+ ],
+ "description": "Occurs when an argument is dismissed or accepted based on its source rather than its merit. This fallacy focuses on where the information came from, rather than the argument itself.",
+ "score": {
+ "logos": 4,
+ "pathos": 4,
+ "ethos": 3
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/Genetic_fallacy"
+ },
+ {
+ "title": "Your Logical Fallacy Is",
+ "url": "https://yourlogicalfallacyis.com/genetic"
+ }
+ ]
+ },
+ {
+ "title": "Guilt by Association",
+ "category": "Logical Fallacy",
+ "strikingExample": "He supports climate action, so I can't trust him because he's friends with a known activist.",
+ "akas": [
+ "Guilt by proximity",
+ "association fallacy",
+ "guilt by affiliation"
+ ],
+ "description": "Occurs when an opinion or argument is discredited because of the person or group associated with it, rather than addressing the merits of the claim itself. This shifts focus from the actual issue to irrelevant connections.",
+ "score": {
+ "logos": 3,
+ "pathos": 7,
+ "ethos": 4
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/Association_fallacy"
+ },
+ {
+ "title": "MAFALDA: Fallacy Detection Benchmark Study",
+ "url": "https://arxiv.org/abs/2311.09761"
+ },
+ {
+ "title": "Fallacy Files",
+ "url": "https://www.fallacyfiles.org/guiltbya.html"
+ },
+ {
+ "title": "Wikipedia (DE)",
+ "url": "https://de.wikipedia.org/wiki/Assoziationsfehler"
+ }
+ ]
+ },
+ {
+ "title": "Hasty Generalization",
+ "category": "Logical Fallacy",
+ "strikingExample": "I had one rude cashier, so all cashiers are rude!",
+ "akas": [
+ "Jumping to conclusions",
+ "insufficient sample size",
+ "anecdotal evidence",
+ "anecdotal fallacy"
+ ],
+ "description": "Occurs when a conclusion is drawn about a claim or opinion based on insufficient or biased evidence, leading to broad, unsupported generalizations. This weakens the argument by ignoring the need for more representative data.",
+ "score": {
+ "logos": 3,
+ "pathos": 6,
+ "ethos": 2
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/Hasty_generalization"
+ },
+ {
+ "title": "MAFALDA: Fallacy Detection Benchmark Study",
+ "url": "https://arxiv.org/abs/2311.09761"
+ },
+ {
+ "title": "Fallacy Files",
+ "url": "https://www.fallacyfiles.org/hastygen.html"
+ },
+ {
+ "title": "Wikipedia (DE)",
+ "url": "https://de.wikipedia.org/wiki/Fehlende_Repr%C3%A4sentativit%C3%A4t"
+ }
+ ]
+ },
+ {
+ "title": "Loaded Question",
+ "category": "Rhetorical Trick",
+ "strikingExample": "Have you stopped lying to your customers yet?",
+ "akas": [
+ "Complex question",
+ "Presupposition fallacy",
+ "Entrapment question"
+ ],
+ "description": "Occurs when a question is phrased in a way that assumes a particular answer or implies something without evidence. This rhetorical trick traps the respondent, making it difficult to answer without accepting the hidden premise.",
+ "score": {
+ "logos": 2,
+ "pathos": 7,
+ "ethos": 4
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/Loaded_question"
+ },
+ {
+ "title": "Fallacy Files",
+ "url": "https://www.fallacyfiles.org/loadques.html"
+ },
+ {
+ "title": "Wikipedia (DE)",
+ "url": "https://de.wikipedia.org/wiki/Komplexe_Frage"
+ },
+ {
+ "title": "Your Logical Fallacy Is",
+ "url": "https://yourlogicalfallacyis.com/loaded-question"
+ }
+ ]
+ },
+ {
+ "title": "Middle Ground",
+ "category": "Logical Fallacy",
+ "strikingExample": "Some say the Earth is flat, others say it’s round. The truth must be somewhere in between.",
+ "akas": [
+ "False compromise",
+ "argument to moderation"
+ ],
+ "description": "Occurs when someone assumes that the truth lies between two opposing extremes, regardless of whether either extreme is based on fact. This fallacy suggests that compromise, rather than evidence, is the best way to find the truth.",
+ "score": {
+ "logos": 4,
+ "pathos": 5,
+ "ethos": 3
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/Argument_to_moderation"
+ },
+ {
+ "title": "Your Logical Fallacy Is",
+ "url": "https://yourlogicalfallacyis.com/middle-ground"
+ }
+ ]
+ },
+ {
+ "title": "Motte & Bailey",
+ "category": "Rhetorical Trick",
+ "strikingExample": "Everyone who disagrees is wrong! Well, I just mean they are misinformed.",
+ "akas": [
+ "Motte-and-Bailey Doctrine",
+ "Defensive Retreat",
+ "Switching Argument"
+ ],
+ "description": "Occurs when someone shifts between an easily defensible position (Motte) and a more controversial one (Bailey). When challenged, they retreat to the Motte, but return to the Bailey when the challenge subsides, creating an illusion of defending a stronger claim than they actually are.",
+ "score": {
+ "logos": 4,
+ "pathos": 5,
+ "ethos": 5
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/Motte-and-bailey_fallacy"
+ },
+ {
+ "title": "Populismus Online",
+ "url": "https://populismus.online/#mottebailey"
+ }
+ ]
+ },
+ {
+ "title": "Moving the Goalposts",
+ "category": "Rhetorical Trick",
+ "strikingExample": "Sure, you passed the test, but can you do it again under pressure?",
+ "akas": [
+ "Raising the bar",
+ "Changing the criteria",
+ "Shifting the target"
+ ],
+ "description": "Occurs when an argument shifts the criteria for success or validity after those criteria have been met. This rhetorical trick makes it difficult to win an argument by continually changing the standards.",
+ "score": {
+ "logos": 3,
+ "pathos": 6,
+ "ethos": 4
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/Moving_the_goalposts"
+ }
+ ]
+ },
+ {
+ "title": "NIMBY",
+ "category": "Rhetorical Trick",
+ "strikingExample": "Wind turbines are all good, but not here!",
+ "akas": [
+ "Not In My Backyard",
+ "local opposition",
+ "self-interested activism",
+ "placemaking obstruction"
+ ],
+ "description": "Occurs when an argument acknowledges the necessity of a project or policy in theory but opposes it when it affects the speaker personally. This rhetorical trick weakens the claim by placing personal interests above broader societal benefits, limiting the discussion’s scope.",
+ "score": {
+ "logos": 2,
+ "pathos": 8,
+ "ethos": 3
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/NIMBY"
+ }
+ ]
+ },
+ {
+ "title": "No True Scotsman",
+ "category": "Logical Fallacy",
+ "strikingExample": "No true environmentalist would ever drive a car.",
+ "akas": [
+ "Purity fallacy",
+ "appeal to purity"
+ ],
+ "description": "Occurs when someone avoids criticism of their argument by redefining their group in a way that excludes counterexamples. By changing the criteria, they protect their claim from valid challenges.",
+ "score": {
+ "logos": 4,
+ "pathos": 5,
+ "ethos": 3
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/No_true_Scotsman"
+ },
+ {
+ "title": "Your Logical Fallacy Is",
+ "url": "https://yourlogicalfallacyis.com/no-true-scotsman"
+ }
+ ]
+ },
+ {
+ "title": "Personal Incredulity",
+ "category": "Logical Fallacy",
+ "strikingExample": "I don’t understand how evolution works, so it must not be true.",
+ "akas": [
+ "Argument from incredulity",
+ "appeal to disbelief"
+ ],
+ "description": "Occurs when someone rejects an argument because they personally can’t understand or believe it. This fallacy shifts the focus from the merits of the argument to the speaker’s own lack of knowledge or imagination.",
+ "score": {
+ "logos": 3,
+ "pathos": 5,
+ "ethos": 2
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/Argument_from_ignorance#Argument_from_incredulity"
+ },
+ {
+ "title": "Your Logical Fallacy Is",
+ "url": "https://yourlogicalfallacyis.com/personal-incredulity"
+ }
+ ]
+ },
+ {
+ "title": "Red Herring",
+ "category": "Rhetorical Trick",
+ "strikingExample": "Why are we discussing climate change when the economy is in trouble?",
+ "akas": [
+ "Distraction fallacy",
+ "Beside the point fallacy",
+ "Irrelevant thesis fallacy"
+ ],
+ "description": "Occurs when an argument introduces an irrelevant topic to divert attention away from the main issue. This rhetorical trick distracts from the original argument by shifting the focus to a different subject.",
+ "score": {
+ "logos": 3,
+ "pathos": 7,
+ "ethos": 2
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/Red_herring"
+ },
+ {
+ "title": "Fallacy Files",
+ "url": "https://www.fallacyfiles.org/redherrf.html"
+ },
+ {
+ "title": "Wikipedia (DE)",
+ "url": "https://de.wikipedia.org/wiki/Red_Herring"
+ }
+ ]
+ },
+ {
+ "title": "Silent Majority",
+ "category": "Rhetorical Trick",
+ "strikingExample": "Despite the vocal minority opposing the new policy, the 'silent majority' of people support it.",
+ "akas": [
+ "Appeal to Silent Support",
+ "Unvoiced Majority",
+ "Hidden Support Fallacy"
+ ],
+ "description": "Occurs when an argument claims that a majority supports a stance but remains silent, suggesting that the lack of vocal opposition means widespread hidden approval. This rhetorical trick attempts to bolster an argument by invoking invisible or unverifiable support.",
+ "score": {
+ "logos": 3,
+ "pathos": 7,
+ "ethos": 4
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/Silent_majority"
+ },
+ {
+ "title": "Populismus Online",
+ "url": "https://populismus.online/#schweigendemehrheit"
+ }
+ ]
+ },
+ {
+ "title": "Slippery Slope",
+ "category": "Logical Fallacy",
+ "strikingExample": "If we drop one rule, soon there’ll be no rules at all.",
+ "akas": [
+ "Camel's nose fallacy",
+ "thin edge of the wedge fallacy",
+ "domino fallacy"
+ ],
+ "description": "Occurs when an argument claims that accepting a small step will inevitably lead to a chain of disastrous consequences, without providing a logical link between the initial action and the supposed outcome. This often serves to discredit the initial claim through fear.",
+ "score": {
+ "logos": 4,
+ "pathos": 8,
+ "ethos": 3
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/Slippery_slope"
+ },
+ {
+ "title": "MAFALDA: Fallacy Detection Benchmark Study",
+ "url": "https://arxiv.org/abs/2311.09761"
+ },
+ {
+ "title": "Fallacy Files",
+ "url": "https://www.fallacyfiles.org/slipslop.html"
+ },
+ {
+ "title": "Wikipedia (DE)",
+ "url": "https://de.wikipedia.org/wiki/Dammbruchargument"
+ },
+ {
+ "title": "Your Logical Fallacy Is",
+ "url": "https://yourlogicalfallacyis.com/slippery-slope"
+ }
+ ]
+ },
+ {
+ "title": "Special Pleading",
+ "category": "Logical Fallacy",
+ "strikingExample": "Yes, I know I said everyone should pay taxes, but I have special circumstances, so I shouldn't have to.",
+ "akas": [
+ "Double standard",
+ "exception fallacy"
+ ],
+ "description": "Occurs when someone applies different rules or standards to themselves or their argument without providing adequate justification. This fallacy creates an exception to a rule without a valid reason for doing so.",
+ "score": {
+ "logos": 4,
+ "pathos": 5,
+ "ethos": 3
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/Special_pleading"
+ },
+ {
+ "title": "Your Logical Fallacy Is",
+ "url": "https://yourlogicalfallacyis.com/special-pleading"
+ }
+ ]
+ },
+ {
+ "title": "Straw Man",
+ "category": "Logical Fallacy",
+ "strikingExample": "They want to ban plastic, so soon everything will be illegal!",
+ "akas": [
+ "Misrepresentation Fallacy",
+ "Distortion Fallacy",
+ "Oversimplification Fallacy"
+ ],
+ "description": "Occurs when an argument misrepresents an opposing claim in order to refute it more easily. This rhetorical trick oversimplifies or distorts the original argument, making it easier to counter while avoiding the real issue.",
+ "score": {
+ "logos": 3,
+ "pathos": 7,
+ "ethos": 3
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/Straw_man"
+ },
+ {
+ "title": "Populismus Online",
+ "url": "https://populismus.online/#strohmann"
+ },
+ {
+ "title": "MAFALDA: Fallacy Detection Benchmark Study",
+ "url": "https://arxiv.org/abs/2311.09761"
+ },
+ {
+ "title": "Fallacy Files",
+ "url": "https://www.fallacyfiles.org/strawman.html"
+ },
+ {
+ "title": "Wikipedia (DE)",
+ "url": "https://de.wikipedia.org/wiki/Strohmann-Argument"
+ },
+ {
+ "title": "Your Logical Fallacy Is",
+ "url": "https://yourlogicalfallacyis.com/strawman"
+ }
+ ]
+ },
+ {
+ "title": "The Fallacy Fallacy",
+ "category": "Logical Fallacy",
+ "strikingExample": "You made a mistake in your reasoning, so your entire argument must be wrong.",
+ "akas": [
+ "Argument from fallacy",
+ "fallacist's fallacy"
+ ],
+ "description": "Occurs when someone assumes that just because an argument contains a fallacy, the entire conclusion is false. While flawed reasoning can weaken a claim, it doesn’t necessarily mean the claim itself is incorrect.",
+ "score": {
+ "logos": 7,
+ "pathos": 3,
+ "ethos": 5
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/Argument_from_fallacy"
+ },
+ {
+ "title": "Fallacy Files",
+ "url": "https://www.fallacyfiles.org/fallfall.html"
+ }
+ ]
+ },
+ {
+ "title": "The Gambler's Fallacy",
+ "category": "Logical Fallacy",
+ "strikingExample": "The roulette ball has landed on red five times in a row, so it must land on black next time.",
+ "akas": [
+ "Monte Carlo fallacy",
+ "maturity of chances fallacy"
+ ],
+ "description": "Occurs when someone believes that if something happens more frequently than normal during a given period, it will happen less frequently in the future, or vice versa. This fallacy misunderstands the independence of random events.",
+ "score": {
+ "logos": 5,
+ "pathos": 4,
+ "ethos": 2
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/Gambler%27s_fallacy"
+ },
+ {
+ "title": "Your Logical Fallacy Is",
+ "url": "https://yourlogicalfallacyis.com/the-gamblers-fallacy"
+ }
+ ]
+ },
+ {
+ "title": "Tu Quoque",
+ "category": "Logical Fallacy",
+ "strikingExample": "You preach healthy eating, but you binge on junk food every weekend.",
+ "akas": [
+ "Appeal to hypocrisy",
+ "two wrongs fallacy",
+ "you too fallacy"
+ ],
+ "description": "Occurs when a counterargument accuses the opponent of hypocrisy instead of addressing the original claim. This rhetorical trick shifts focus from the argument itself to the accuser’s behavior, diverting attention away from the actual issue being debated.",
+ "score": {
+ "logos": 3,
+ "pathos": 7,
+ "ethos": 4
+ },
+ "references": [
+ {
+ "title": "Wikipedia",
+ "url": "https://en.wikipedia.org/wiki/Tu_quoque"
+ },
+ {
+ "title": "MAFALDA: Fallacy Detection Benchmark Study",
+ "url": "https://arxiv.org/abs/2311.09761"
+ },
+ {
+ "title": "Fallacy Files",
+ "url": "https://www.fallacyfiles.org/tuquoque.html"
+ },
+ {
+ "title": "Wikipedia (DE)",
+ "url": "https://de.wikipedia.org/wiki/Tu-quoque-Argument"
+ },
+ {
+ "title": "Your Logical Fallacy Is",
+ "url": "https://yourlogicalfallacyis.com/tu-quoque"
+ }
+ ]
+ }
+]
diff --git a/public/speech/habeck/centered.json b/public/speech/habeck/centered.json
new file mode 100644
index 00000000..195529e4
--- /dev/null
+++ b/public/speech/habeck/centered.json
@@ -0,0 +1,231 @@
+[
+ {
+ "emotions": [
+ "joy"
+ ],
+ "range": {
+ "start": 80,
+ "end": 130
+ }
+ },
+ {
+ "emotions": [
+ "surprise"
+ ],
+ "range": {
+ "start": 131,
+ "end": 191
+ }
+ },
+ {
+ "emotions": [
+ "anger"
+ ],
+ "range": {
+ "start": 327,
+ "end": 358
+ }
+ },
+ {
+ "emotions": [
+ "disgust"
+ ],
+ "range": {
+ "start": 602,
+ "end": 682
+ }
+ },
+ {
+ "emotions": [
+ "joy"
+ ],
+ "range": {
+ "start": 822,
+ "end": 870
+ }
+ },
+ {
+ "emotions": [
+ "sadness"
+ ],
+ "range": {
+ "start": 1092,
+ "end": 1191
+ }
+ },
+ {
+ "emotions": [
+ "joy"
+ ],
+ "range": {
+ "start": 1193,
+ "end": 1249
+ }
+ },
+ {
+ "emotions": [
+ "joy"
+ ],
+ "range": {
+ "start": 1298,
+ "end": 1337
+ }
+ },
+ {
+ "emotions": [
+ "fear",
+ "anger",
+ "disgust"
+ ],
+ "range": {
+ "start": 1959,
+ "end": 2046
+ }
+ },
+ {
+ "emotions": [
+ "anger",
+ "disgust"
+ ],
+ "range": {
+ "start": 2047,
+ "end": 2135
+ }
+ },
+ {
+ "emotions": [
+ "sadness"
+ ],
+ "range": {
+ "start": 2316,
+ "end": 2386
+ }
+ },
+ {
+ "emotions": [
+ "disgust"
+ ],
+ "range": {
+ "start": 2387,
+ "end": 2463
+ }
+ },
+ {
+ "emotions": [
+ "sadness"
+ ],
+ "range": {
+ "start": 2606,
+ "end": 2724
+ }
+ },
+ {
+ "emotions": [
+ "sadness"
+ ],
+ "range": {
+ "start": 2773,
+ "end": 2800
+ }
+ },
+ {
+ "emotions": [
+ "sadness"
+ ],
+ "range": {
+ "start": 2801,
+ "end": 2864
+ }
+ },
+ {
+ "emotions": [
+ "disgust"
+ ],
+ "range": {
+ "start": 2865,
+ "end": 2931
+ }
+ },
+ {
+ "emotions": [
+ "joy"
+ ],
+ "range": {
+ "start": 2962,
+ "end": 3007
+ }
+ },
+ {
+ "emotions": [
+ "joy"
+ ],
+ "range": {
+ "start": 3079,
+ "end": 3113
+ }
+ },
+ {
+ "emotions": [
+ "joy"
+ ],
+ "range": {
+ "start": 3114,
+ "end": 3151
+ }
+ },
+ {
+ "emotions": [
+ "joy"
+ ],
+ "range": {
+ "start": 3152,
+ "end": 3228
+ }
+ },
+ {
+ "emotions": [
+ "surprise",
+ "joy"
+ ],
+ "range": {
+ "start": 3305,
+ "end": 3453
+ }
+ },
+ {
+ "emotions": [
+ "joy"
+ ],
+ "range": {
+ "start": 3455,
+ "end": 3527
+ }
+ },
+ {
+ "emotions": [
+ "joy"
+ ],
+ "range": {
+ "start": 3528,
+ "end": 3559
+ }
+ },
+ {
+ "emotions": [
+ "joy"
+ ],
+ "range": {
+ "start": 3560,
+ "end": 3586
+ }
+ },
+ {
+ "emotions": [
+ "joy"
+ ],
+ "range": {
+ "start": 3588,
+ "end": 3633
+ }
+ }
+]
\ No newline at end of file
diff --git a/public/speech/habeck/left-wing.json b/public/speech/habeck/left-wing.json
new file mode 100644
index 00000000..c962ca19
--- /dev/null
+++ b/public/speech/habeck/left-wing.json
@@ -0,0 +1,182 @@
+[
+ {
+ "emotions": [
+ "surprise"
+ ],
+ "range": {
+ "start": 80,
+ "end": 130
+ }
+ },
+ {
+ "emotions": [
+ "surprise"
+ ],
+ "range": {
+ "start": 131,
+ "end": 191
+ }
+ },
+ {
+ "emotions": [
+ "anger"
+ ],
+ "range": {
+ "start": 227,
+ "end": 326
+ }
+ },
+ {
+ "emotions": [
+ "anger"
+ ],
+ "range": {
+ "start": 327,
+ "end": 358
+ }
+ },
+ {
+ "emotions": [
+ "anger"
+ ],
+ "range": {
+ "start": 602,
+ "end": 682
+ }
+ },
+ {
+ "emotions": [
+ "anger"
+ ],
+ "range": {
+ "start": 684,
+ "end": 730
+ }
+ },
+ {
+ "emotions": [
+ "fear"
+ ],
+ "range": {
+ "start": 731,
+ "end": 821
+ }
+ },
+ {
+ "emotions": [
+ "joy"
+ ],
+ "range": {
+ "start": 1250,
+ "end": 1337
+ }
+ },
+ {
+ "emotions": [
+ "disgust"
+ ],
+ "range": {
+ "start": 1844,
+ "end": 1922
+ }
+ },
+ {
+ "emotions": [
+ "fear"
+ ],
+ "range": {
+ "start": 1959,
+ "end": 2046
+ }
+ },
+ {
+ "emotions": [
+ "sadness"
+ ],
+ "range": {
+ "start": 2316,
+ "end": 2386
+ }
+ },
+ {
+ "emotions": [
+ "disgust"
+ ],
+ "range": {
+ "start": 2464,
+ "end": 2550
+ }
+ },
+ {
+ "emotions": [
+ "sadness"
+ ],
+ "range": {
+ "start": 2606,
+ "end": 2724
+ }
+ },
+ {
+ "emotions": [
+ "sadness"
+ ],
+ "range": {
+ "start": 2773,
+ "end": 2800
+ }
+ },
+ {
+ "emotions": [
+ "disgust"
+ ],
+ "range": {
+ "start": 2865,
+ "end": 2931
+ }
+ },
+ {
+ "emotions": [
+ "joy"
+ ],
+ "range": {
+ "start": 2933,
+ "end": 3007
+ }
+ },
+ {
+ "emotions": [
+ "joy"
+ ],
+ "range": {
+ "start": 3079,
+ "end": 3113
+ }
+ },
+ {
+ "emotions": [
+ "joy"
+ ],
+ "range": {
+ "start": 3152,
+ "end": 3228
+ }
+ },
+ {
+ "emotions": [
+ "joy"
+ ],
+ "range": {
+ "start": 3528,
+ "end": 3586
+ }
+ },
+ {
+ "emotions": [
+ "joy"
+ ],
+ "range": {
+ "start": 3588,
+ "end": 3633
+ }
+ }
+]
\ No newline at end of file
diff --git a/public/speech/habeck/right-wing.json b/public/speech/habeck/right-wing.json
new file mode 100644
index 00000000..9f479096
--- /dev/null
+++ b/public/speech/habeck/right-wing.json
@@ -0,0 +1,141 @@
+[
+ {
+ "emotions": [
+ "anger"
+ ],
+ "range": {
+ "start": 327,
+ "end": 358
+ }
+ },
+ {
+ "emotions": [
+ "anger"
+ ],
+ "range": {
+ "start": 602,
+ "end": 682
+ }
+ },
+ {
+ "emotions": [
+ "anger"
+ ],
+ "range": {
+ "start": 684,
+ "end": 730
+ }
+ },
+ {
+ "emotions": [
+ "disgust",
+ "anger"
+ ],
+ "range": {
+ "start": 1756,
+ "end": 1801
+ }
+ },
+ {
+ "emotions": [
+ "anger",
+ "disgust"
+ ],
+ "range": {
+ "start": 1803,
+ "end": 1922
+ }
+ },
+ {
+ "emotions": [
+ "fear",
+ "anger"
+ ],
+ "range": {
+ "start": 1959,
+ "end": 2046
+ }
+ },
+ {
+ "emotions": [
+ "sadness"
+ ],
+ "range": {
+ "start": 2316,
+ "end": 2386
+ }
+ },
+ {
+ "emotions": [
+ "disgust",
+ "anger"
+ ],
+ "range": {
+ "start": 2464,
+ "end": 2550
+ }
+ },
+ {
+ "emotions": [
+ "disgust"
+ ],
+ "range": {
+ "start": 2552,
+ "end": 2605
+ }
+ },
+ {
+ "emotions": [
+ "fear"
+ ],
+ "range": {
+ "start": 2725,
+ "end": 2771
+ }
+ },
+ {
+ "emotions": [
+ "joy"
+ ],
+ "range": {
+ "start": 3079,
+ "end": 3113
+ }
+ },
+ {
+ "emotions": [
+ "joy"
+ ],
+ "range": {
+ "start": 3114,
+ "end": 3151
+ }
+ },
+ {
+ "emotions": [
+ "joy"
+ ],
+ "range": {
+ "start": 3152,
+ "end": 3228
+ }
+ },
+ {
+ "emotions": [
+ "joy"
+ ],
+ "range": {
+ "start": 3528,
+ "end": 3586
+ }
+ },
+ {
+ "emotions": [
+ "joy"
+ ],
+ "range": {
+ "start": 3588,
+ "end": 3633
+ }
+ }
+]
\ No newline at end of file
diff --git a/public/speech/habeck/speech.txt b/public/speech/habeck/speech.txt
new file mode 100644
index 00000000..80979df8
--- /dev/null
+++ b/public/speech/habeck/speech.txt
@@ -0,0 +1,37 @@
+Liebe Zuschauerinnen, liebe Zuschauer, sehr geehrte Damen, sehr geehrte Herren. Ich bin heute hier, um Ihnen etwas zu versprechen. Aber ich bin auch hier, um Ihnen etwas nicht zu versprechen.
+
+Ich fange mit dem Versprechen an. In den kommenden Wochen werden Parteien, Politikerinnen und Politiker miteinander ringen, streiten. Wahlkampf kommt ja von Kämpfen.
+
+Wir werden Unterschiede zwischen den Parteien herausarbeiten. Und das ist wichtig für Sie als Wählerinnen und Wähler, um zu wissen, wofür die Parteien stehen, was Sie wollen und was Sie unterscheidet, damit Sie dann Ihre Wahl treffen können. Wer die eigenen Vorschläge herausstellt, wird dabei die der anderen kritisieren.
+
+Im Jargon der Politik reden wir von Angriffen. Aber man sollte immer im Bewusstsein handeln, man trifft sich mindestens zweimal im Leben. Und in diesem Geist möchte ich Wahlkampf führen.
+
+Wenn meine Kinder früher in der Handballhalle ein Spiel hatten, mit dem Gegnerrang um die Tore wetteiferten, gab es für mich als Vater immer einen besonderen Moment des Stolzes. Nämlich nach dem Spiel. Dann standen sich Sieger und Verlierer gegenüber, die einen strahlend, die anderen enttäuscht, aber alle verschwitzt.
+
+Und dann reichten sie sich die Hand, klatschten sich ab. Und so werde ich es in diesem Wahlkampf machen. Klar in der Sache, aber voller Respekt.
+
+Und in dem Wissen, du musst dem anderen danach immer die Hand reichen können. Das, sehr geehrte Damen und Herren, das ist mein Versprechen. Olaf Scholz und Friedrich Merz würden andere Bundeskanzler sein als ich.
+
+Aber, wie ich, fühlen sie sich dem Wohle des Landes verpflichtet. Uns eint, was die Spielerinnen und Spieler in der Halle eint, dass wir nach den gemeinsamen Regeln unserer liberalen Demokratie spielen. Regeln allerdings, die andere brechen wollen.
+
+Dann, um unsere Demokratie zu zerstören. Sie greifen zu Desinformationen, schamlosen Lügen, erfundenen Beschuldigungen. Im Netz, bei X oder TikTok und Co.
+
+Sie wollen unserer Gesellschaft Kirre machen, Angst, Misstrauen, Spaltung herbeiführen. Dem sollten wir in diesem Wahlkampf als demokratische Parteien gemeinsam entgegentreten. Im Wettstreit in den Hallen und auf den Plätzen, in den Studios.
+
+Gemeinsam für unsere Demokratie. Bündnisfähig. Was ich Ihnen dagegen nicht verspreche, ist das Blaue vom Himmel.
+
+Ich verspreche Ihnen nicht, dass plötzlich alles gut und einfach wird. Und, sehr geehrte Damen und Herren, lassen Sie sich das auch nicht einreden. Lassen Sie sich nicht einreden, alles wird anders, weil die Regierung eine andere ist.
+
+Weil wieder irgendwelche Wänden herbeigerufen werden. Die Wirklichkeit, mit der sich jede Regierung gleich welcher Konstellation auseinandersetzen muss, ist exakt dieselbe. Wir leben mit der Gleichzeitigkeit der Krisen.
+
+Keiner kann sie wegzaubern. Diese Wirklichkeit geht auch nicht weg, wenn man sie ignoriert. Und wer Ihnen etwas anderes erzählt, erzählt Ihnen etwas Falsches.
+
+Aber ich bin auch überzeugt. Wir können sicher durch diese Krisen steuern. Deutschland kann neue Stärke entwickeln, wird neue Stärke entwickeln.
+
+Wenn wir uns der Realität stellen. Wenn wir die Dinge beim Namen nennen. Wenn wir Lösungen für die Probleme anbieten, statt die Probleme zu beklagen.
+
+Diese Lösungen, sie müssen nicht allen gefallen. Dann sollten bessere her. Und wenn ich der Meinung bin, andere haben bessere Lösungen, dann werde ich das zugeben, sie übernehmen und den politischen Mitbewerber dafür loben.
+
+Hauptsache, wir lösen die Probleme und verzagen nicht, wenn neue kommen. Diese Zuversicht, die habe ich. Deutschland hat die Kraft.
+
+Gemeinsam haben wir die Kraft für diese Zeit.
diff --git a/public/speech/merz/centered.json b/public/speech/merz/centered.json
new file mode 100644
index 00000000..da315eec
--- /dev/null
+++ b/public/speech/merz/centered.json
@@ -0,0 +1 @@
+[{"emotions":["surprise"],"range":{"start":36,"end":110}},{"emotions":["joy"],"range":{"start":111,"end":193}},{"emotions":["joy"],"range":{"start":328,"end":413}},{"emotions":["joy"],"range":{"start":415,"end":509}},{"emotions":["anger","sadness"],"range":{"start":605,"end":650}},{"emotions":["anger","sadness"],"range":{"start":652,"end":703}},{"emotions":["fear","sadness","anger"],"range":{"start":1014,"end":1177}},{"emotions":["sadness","anger"],"range":{"start":1179,"end":1246}},{"emotions":["sadness","fear"],"range":{"start":1251,"end":1322}},{"emotions":["surprise"],"range":{"start":1578,"end":1698}},{"emotions":["anger"],"range":{"start":1699,"end":1793}},{"emotions":["joy"],"range":{"start":1839,"end":1992}},{"emotions":["joy"],"range":{"start":1993,"end":2112}},{"emotions":["surprise"],"range":{"start":2430,"end":2478}},{"emotions":["joy"],"range":{"start":2531,"end":2646}},{"emotions":["joy"],"range":{"start":2648,"end":2704}},{"emotions":["fear"],"range":{"start":2705,"end":2778}},{"emotions":["fear","surprise"],"range":{"start":2780,"end":2865}},{"emotions":["joy"],"range":{"start":2866,"end":2995}},{"emotions":["joy"],"range":{"start":2997,"end":3118}}]
diff --git a/public/speech/merz/left-wing.json b/public/speech/merz/left-wing.json
new file mode 100644
index 00000000..79644cde
--- /dev/null
+++ b/public/speech/merz/left-wing.json
@@ -0,0 +1 @@
+[{"emotions":["surprise"],"range":{"start":36,"end":110}},{"emotions":["joy"],"range":{"start":802,"end":875}},{"emotions":["anger","fear"],"range":{"start":1014,"end":1177}},{"emotions":["sadness"],"range":{"start":1179,"end":1246}},{"emotions":["joy"],"range":{"start":2216,"end":2309}},{"emotions":["surprise","joy"],"range":{"start":2350,"end":2478}},{"emotions":["fear","sadness"],"range":{"start":2780,"end":2865}}]
diff --git a/public/speech/merz/right-wing.json b/public/speech/merz/right-wing.json
new file mode 100644
index 00000000..b7e2a22b
--- /dev/null
+++ b/public/speech/merz/right-wing.json
@@ -0,0 +1 @@
+[{"emotions":["surprise"],"range":{"start":36,"end":110}},{"emotions":["joy"],"range":{"start":111,"end":193}},{"emotions":["joy"],"range":{"start":195,"end":272}},{"emotions":["joy","sadness"],"range":{"start":328,"end":413}},{"emotions":["joy"],"range":{"start":415,"end":509}},{"emotions":["anger"],"range":{"start":605,"end":650}},{"emotions":["anger"],"range":{"start":704,"end":801}},{"emotions":["anger","joy"],"range":{"start":802,"end":875}},{"emotions":["joy"],"range":{"start":877,"end":934}},{"emotions":["fear"],"range":{"start":935,"end":1013}},{"emotions":["fear","anger","disgust"],"range":{"start":1014,"end":1177}},{"emotions":["sadness"],"range":{"start":1179,"end":1246}},{"emotions":["fear","sadness"],"range":{"start":1247,"end":1322}},{"emotions":["anger","joy"],"range":{"start":1578,"end":1698}},{"emotions":["anger"],"range":{"start":1699,"end":1837}},{"emotions":["joy"],"range":{"start":1839,"end":1992}},{"emotions":["joy"],"range":{"start":1993,"end":2112}},{"emotions":["joy"],"range":{"start":2216,"end":2309}},{"emotions":["joy"],"range":{"start":2310,"end":2349}},{"emotions":["anger","disgust"],"range":{"start":2350,"end":2478}},{"emotions":["anger"],"range":{"start":2480,"end":2530}},{"emotions":["joy"],"range":{"start":2531,"end":2704}},{"emotions":["fear"],"range":{"start":2705,"end":2778}},{"emotions":["fear"],"range":{"start":2780,"end":2865}},{"emotions":["anger","joy"],"range":{"start":2866,"end":2995}}]
diff --git a/public/speech/merz/speech.txt b/public/speech/merz/speech.txt
new file mode 100644
index 00000000..dc0a8ccd
--- /dev/null
+++ b/public/speech/merz/speech.txt
@@ -0,0 +1,27 @@
+Liebe Zuschauerinnen und Zuschauer. Sie werden wahrscheinlich überrascht sein, mich heute Abend hier zu sehen. Aber in diesem Jahr haben wir in Deutschland zwei große Gründungsmomente gefeiert.
+
+Die Gründung der Bundesrepublik Deutschland und 75 Jahre Deutscher Bundestag. 75 Jahre Grundgesetz und 75 Jahre Deutscher Bundestag. In diesen 75 Jahren haben wir gemeinsam erfahren, was es heißt, in Freiheit zu leben.
+
+Demokratie ist die Meinungsbildung und der Meinungsaustausch zwischen Bürgerinnen und Bürgern. Die idealerweise nicht nur das Beste für sich selbst, sondern für alle in unserem Land wollen. Dieser Meinungsaustausch kann auch hart sein.
+
+Überzeugungen diskutieren wir oft leidenschaftlich. Richtungsentscheidungen für unser Land müssen diskutiert werden, damit die Ergebnisse gut werden. Demokratie ist eben streitbar, aber sie ist genau deshalb auch lernfähig.
+
+So ist sie ein Motor für den Fortschritt in unserem Land. Aber weil die Demokratie streitbar ist, ist unsere Demokratie auch verwundbar. Wettstreit kann zum Kampf werden, wo der demokratische Gegner zum Feind erklärt wird, wo mit Ängsten gespielt wird, wo das Misstrauen untereinander überhand nimmt.
+
+Da wird die gemeinsame Arbeit an guten Lösungen für alle unmöglich. Und im schlimmsten Fall wirkt das Misstrauen bis in unsere Familien hinein. In den nächsten Wochen ist in Deutschland Wahlkampf.
+
+Wir werden und wir müssen gemeinsam über Grundsatzfragen sprechen und streiten. Dabei tragen wir Meinungsunterschiede miteinander aus. Wir stellen Zukunftsideen für Deutschland vor und gegeneinander.
+
+Ich spreche heute auch als Oppositionsführer im Deutschen Bundestag und als Kanzlerkandidat der Unionsparteien zu Ihnen. Meine Partei, die CDU, hat in den vergangenen drei Jahren Kritik an der Bundesregierung geübt. So wie es unsere demokratische Aufgabe ist.
+
+Aber wir haben und wir werden auch weiterhin, und das verspreche ich Ihnen, den Regeln des Anstands und des persönlichen Respekts in jedem Moment folgen. Nur wenn wir davon ausgehen, dass der andere auch einmal Recht haben könnte, werden wir zu gemeinsamen Lösungen finden. Liebe Zuschauerinnen und Zuschauer, wir Deutschen sind nicht unter den ältesten Demokratien der Welt.
+
+Aber wir haben eine demokratische Kultur geschaffen, auf die wir gemeinsam stolz sein können. Wir haben eine starke politische Mitte. Bei allen Meinungsunterschieden in der Sache sage ich aus tiefster Überzeugung, Olaf Scholz und Robert Habeck sind keine Feinde.
+
+Sie sind politische Konkurrenten und Wettbewerber. Wenn wir es richtig machen, dann ist die Bundesrepublik Deutschland an ihrem 100. und vielleicht auch an ihrem 150.
+
+Geburtstag ein Beweis für den Erfolg unserer Demokratie. Und genau das wünsche ich mir. Denn es steht ziemlich viel auf dem Spiel.
+
+Wir haben für unser Land so viel zu gewinnen. Wir können aber auch einiges verlieren. In diesem Sinne wünsche ich mir einen guten, demokratischen, streitbaren Wettbewerb, an dessen Ende Sie die Entscheidung treffen.
+
+Und bis dahin wünsche ich Ihnen eine gute Adventszeit, eine schöne Weihnachtszeit und einen guten Start in das Jahr 2025.
diff --git a/public/speech/scholz/centered.json b/public/speech/scholz/centered.json
new file mode 100644
index 00000000..bb230a52
--- /dev/null
+++ b/public/speech/scholz/centered.json
@@ -0,0 +1 @@
+[{"emotions":["fear"],"range":{"start":165,"end":243}},{"emotions":["sadness"],"range":{"start":244,"end":322}},{"emotions":["surprise"],"range":{"start":781,"end":846}},{"emotions":["anger"],"range":{"start":1143,"end":1166}},{"emotions":["disgust"],"range":{"start":1287,"end":1331}},{"emotions":["joy"],"range":{"start":2174,"end":2226}}]
diff --git a/public/speech/scholz/left-wing.json b/public/speech/scholz/left-wing.json
new file mode 100644
index 00000000..d4a5b474
--- /dev/null
+++ b/public/speech/scholz/left-wing.json
@@ -0,0 +1 @@
+[{"emotions":["fear"],"range":{"start":165,"end":243}},{"emotions":["sadness","disgust"],"range":{"start":244,"end":322}},{"emotions":["anger"],"range":{"start":1120,"end":1166}},{"emotions":["anger","disgust"],"range":{"start":1287,"end":1331}},{"emotions":["disgust"],"range":{"start":1332,"end":1452}},{"emotions":["anger","disgust"],"range":{"start":1801,"end":1882}},{"emotions":["anger","disgust"],"range":{"start":1883,"end":2008}},{"emotions":["joy"],"range":{"start":1603,"end":1677}},{"emotions":["joy"],"range":{"start":2024,"end":2105}},{"emotions":["joy"],"range":{"start":2106,"end":2172}},{"emotions":["joy","surprise"],"range":{"start":2174,"end":2226}},{"emotions":["joy","sadness"],"range":{"start":2298,"end":2442}},{"emotions":["joy","surprise"],"range":{"start":2702,"end":2880}}]
diff --git a/public/speech/scholz/right-wing.json b/public/speech/scholz/right-wing.json
new file mode 100644
index 00000000..bd2f57ac
--- /dev/null
+++ b/public/speech/scholz/right-wing.json
@@ -0,0 +1 @@
+[{"emotions":["anger","fear"],"range":{"start":0,"end":87}},{"emotions":["fear","sadness"],"range":{"start":165,"end":243}},{"emotions":["disgust","sadness"],"range":{"start":244,"end":322}},{"emotions":["anger"],"range":{"start":552,"end":621}},{"emotions":["disgust","anger"],"range":{"start":622,"end":711}},{"emotions":["anger"],"range":{"start":1143,"end":1166}},{"emotions":["anger","disgust"],"range":{"start":1287,"end":1331}},{"emotions":["anger"],"range":{"start":1332,"end":1452}},{"emotions":["joy","surprise"],"range":{"start":2106,"end":2172}},{"emotions":["joy"],"range":{"start":2227,"end":2297}},{"emotions":["joy"],"range":{"start":2174,"end":2226}},{"emotions":["joy"],"range":{"start":2911,"end":3045}},{"emotions":["joy","surprise"],"range":{"start":3082,"end":3136}}]
diff --git a/public/speech/scholz/speech.txt b/public/speech/scholz/speech.txt
new file mode 100644
index 00000000..d41ebbe5
--- /dev/null
+++ b/public/speech/scholz/speech.txt
@@ -0,0 +1,27 @@
+Es ist gerade ganz schön was los. Den ganzen Tag über prasseln Nachrichten auf uns ein. Wichtiges und unwichtiges, Zuspitzungen, Allgemeines, Reden und Gegenreden.
+
+Vielleicht kann das dazu führen, sich verunsichert oder verängstigt zu fühlen. Und vielleicht auch dazu, dass man sich am liebsten von all dem abwenden will. Damit das nicht passiert, lohnt es, mal innezuhalten.
+
+Sich zu fragen, worauf kommt es wirklich an? Was ist wichtig? Was ist stimmungsmachend?
+
+Und wem schenke ich in dieser Lage mein Vertrauen? Diese Fragen sind wichtig, gerade wenn nun der Wahlkampf, der Wettbewerb zwischen den Parteien beginnt. Wenn wir Politikerinnen und Politiker im Wettbewerb miteinander und gegeneinander stehen.
+
+Wenn wir klar Positionen beziehen und Unterschiede deutlich machen. Nächste Woche werde ich im Bundestag die Vertrauensfrage stellen. Das Ziel ist es, den Weg freizumachen für vorgezogene Bundestagswahlen.
+
+Damit die Bürgerinnen und Bürger entscheiden können, wem sie für die nächsten vier Jahre ihr Vertrauen schenken. Welche Themen ihnen wichtig sind, wer eine Regierung bilden und wer sie anführen soll. Wichtig ist mir dabei, Politik ist kein Spiel.
+
+Politikerinnen und Politiker werden gewählt, um Probleme zu lösen und das Land auch durch schwierige Zeiten zu lenken. Politik darf niemals zum Selbstzweck werden. Politik ist nicht der Wettstreit um das größte Maß an Aufmerksamkeit, um die steilste These oder die extremste Position.
+
+Politik ist dazu da, Fragen miteinander zu klären und Probleme zu lösen. Ich bin überzeugt, was wir brauchen, das ist ein Versprechen an uns selbst. Dass wir die wichtigsten Regeln des demokratischen Miteinanders einhalten.
+
+Und zwar wir alle, die Parteien und ihre Spitzenkandidaten genauso wie die Medien. Unsere drei wichtigsten Regeln lauten, es geht um Ehrlichkeit und Transparenz, nicht um Manipulation und Desinformation. Es geht ums Informieren und darum zu hinterfragen und zu überprüfen, nicht aber ums Verunglimpfen, Verletzen und Herabsetzen.
+
+Und drittens, wir können und müssen uns unterschiedliche Meinungen zumuten. Das ist Demokratie. Wir können diskutieren, aber wir gehen respektvoll miteinander um.
+
+Wir können uns streiten, aber wir sind keine Feinde. Das ist ein Versprechen, das wir uns auch untereinander geben sollten. Eigentlich eine Selbstverständlichkeit, trotz aller nötigen Zuspitzung, trotz allem Ringen um Unterschiede, müssen wir ehrlich und fair bleiben.
+
+Mein Eindruck ist, bei den Gesprächen, die ich überall im Land führe, bei Firmenbesuchen, auf Marktplätzen, bei Bürgerdialogen und im Kleingartenverein, wir kommen immer weiter, wenn wir miteinander reden, statt nur noch übereinander oder aneinander vorbei. Klar, der Wahlkampf dient dazu, Unterschiede aufzuzeigen. Nach dem Wahltag geht es aber auch wieder darum, Gemeinsamkeiten auszuloten, Brücken zu bauen, Kompromisse zu schmieden.
+
+Dafür brauchen wir einander. Es liegt an uns, allen gemeinsam, an allen demokratischen Politikerinnen und Politikern, an den Medien und an jedem Einzelnen von uns. Lassen Sie uns einander vertrauen.
+
+Ich bin heute hier, um Ihnen genau das zu versprechen.