feat: dashboard Second Brain, essai 7 jours et vérification e-mail
Rendre le dashboard actionnable (inbox, peek, carte mentale), aligner la facturation sur l’essai 7 jours, et bloquer le login e-mail tant que l’adresse n’est pas confirmée. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
134
memento-note/components/interactive-page/steps-block.tsx
Normal file
134
memento-note/components/interactive-page/steps-block.tsx
Normal file
@@ -0,0 +1,134 @@
|
||||
'use client'
|
||||
|
||||
import { useMemo } from 'react'
|
||||
import katex from 'katex'
|
||||
import { AnimPlayerShell } from '@/components/simulators/anim-player-shell'
|
||||
import type { StepsBlock } from '@/lib/interactive-page'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
function renderTex(tex: string, displayMode: boolean): string {
|
||||
try {
|
||||
return katex.renderToString(tex, { displayMode, throwOnError: false })
|
||||
} catch {
|
||||
return tex
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Step-by-step derivation (Symbolab/Khan style): equation states revealed
|
||||
* line by line, current line highlighted, transformation rule in the margin.
|
||||
* No boxes, no LLM drawing — pure KaTeX + deterministic chrome.
|
||||
*/
|
||||
export function StepsBlockView({
|
||||
block,
|
||||
lang,
|
||||
}: {
|
||||
block: StepsBlock
|
||||
lang: string
|
||||
}) {
|
||||
const fr = lang.startsWith('fr')
|
||||
const steps = block.steps
|
||||
|
||||
const beats = useMemo(
|
||||
() =>
|
||||
steps.map((s, i) => ({
|
||||
id: `st${i + 1}`,
|
||||
speak: {
|
||||
fr: s.speak || s.rule || (fr ? `Étape ${i + 1}` : `Step ${i + 1}`),
|
||||
en: s.speak || s.rule || `Step ${i + 1}`,
|
||||
},
|
||||
})),
|
||||
[steps, fr]
|
||||
)
|
||||
|
||||
const rendered = useMemo(() => steps.map((s) => renderTex(s.tex, true)), [steps])
|
||||
|
||||
return (
|
||||
<figure
|
||||
className="my-8 rounded-2xl border p-4 md:p-5"
|
||||
style={{ background: 'var(--pp-card)', borderColor: 'var(--pp-line)' }}
|
||||
>
|
||||
{block.title ? (
|
||||
<div className="mb-4 flex items-baseline justify-between gap-3">
|
||||
<h3 className="text-sm font-semibold tracking-tight">{block.title}</h3>
|
||||
<span
|
||||
className="text-[10px] font-semibold uppercase tracking-[0.16em]"
|
||||
style={{ color: 'var(--pp-muted)' }}
|
||||
>
|
||||
{fr ? 'Dérivation pas à pas' : 'Step-by-step derivation'}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<AnimPlayerShell beats={beats} lang={lang}>
|
||||
{(stepIndex) => (
|
||||
<div className="space-y-1.5 p-3 md:p-4" style={{ background: 'var(--pp-paper)' }}>
|
||||
{steps.slice(0, stepIndex + 1).map((s, i) => {
|
||||
const current = i === stepIndex
|
||||
return (
|
||||
<div
|
||||
key={i}
|
||||
className={cn(
|
||||
'flex items-start gap-3 rounded-lg border-l-4 px-3 py-2',
|
||||
current ? 'shadow-sm' : 'border-transparent'
|
||||
)}
|
||||
style={{
|
||||
borderLeftColor: current ? 'var(--pp-plum)' : 'transparent',
|
||||
background: current ? 'var(--pp-card)' : 'transparent',
|
||||
opacity: current ? 1 : 0.72,
|
||||
transition: 'opacity 400ms ease, background 400ms ease',
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className="mt-1 shrink-0 text-[10px] font-semibold tabular-nums"
|
||||
style={{ color: 'var(--pp-muted)', fontFamily: 'var(--pp-mono)' }}
|
||||
>
|
||||
{String(i + 1).padStart(2, '0')}
|
||||
</span>
|
||||
<div
|
||||
className="min-w-0 flex-1 overflow-x-auto text-[1.02em] [&_.katex-display]:my-1"
|
||||
dangerouslySetInnerHTML={{ __html: rendered[i] }}
|
||||
/>
|
||||
{s.rule ? (
|
||||
<span
|
||||
className="mt-0.5 shrink-0 rounded-md px-2 py-1 text-[11px] leading-snug"
|
||||
style={{
|
||||
fontFamily: 'var(--pp-mono)',
|
||||
color: 'var(--pp-plum)',
|
||||
background: 'color-mix(in oklab, var(--pp-plum) 10%, transparent)',
|
||||
maxWidth: '38%',
|
||||
}}
|
||||
>
|
||||
{s.rule}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</AnimPlayerShell>
|
||||
|
||||
{block.caption ? (
|
||||
<figcaption
|
||||
className="mt-3 text-center text-sm"
|
||||
style={{ color: 'var(--pp-muted)' }}
|
||||
>
|
||||
{block.caption}
|
||||
</figcaption>
|
||||
) : null}
|
||||
|
||||
{/* Without JS: full derivation visible + rules listed */}
|
||||
<noscript>
|
||||
<ol className="mt-3 list-inside list-decimal space-y-2 text-sm">
|
||||
{steps.map((s, i) => (
|
||||
<li key={i}>
|
||||
<span dangerouslySetInnerHTML={{ __html: rendered[i] }} />
|
||||
{s.rule ? <em className="ml-2 text-muted-foreground">({s.rule})</em> : null}
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</noscript>
|
||||
</figure>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user