Ajoute le pipeline PageSpec (validation, rendu, publication /p/{slug}),
les démos TipTap /demo, et le simulateur Carnot (modes frigo/PAC/moteur,
énergie kJ vs puissance W, unités K/°C/°F) avec correctifs d’équations KaTeX.
Co-authored-by: Cursor <cursoragent@cursor.com>
115 lines
5.2 KiB
TypeScript
115 lines
5.2 KiB
TypeScript
'use client'
|
||
|
||
import { useDarkMode } from '@/components/interactive-demo/demo-speak'
|
||
|
||
/**
|
||
* T–s diagram of the Carnot cycle — canonical 2nd-law diagram.
|
||
* Driven by AnimPlayerShell via `step` (0..4). SVG, transform/opacity only.
|
||
* Cycle = rectangle: horizontal isotherms (Th, Tc), vertical adiabatics.
|
||
*/
|
||
|
||
const X0 = 110 // y-axis x
|
||
const Y0 = 280 // x-axis y
|
||
const S1 = 190 // entropy left
|
||
const S2 = 520 // entropy right
|
||
const YH = 80 // T_h line y
|
||
const YC = 220 // T_c line y
|
||
|
||
const EASE = 'transform 700ms cubic-bezier(0.22, 1, 0.36, 1), opacity 500ms ease'
|
||
|
||
// marker position per beat (end of each phase)
|
||
const MARKER = [
|
||
{ x: S2, y: YH }, // b1 end: right-top
|
||
{ x: S2, y: YC }, // b2 end: right-bottom
|
||
{ x: S1, y: YC }, // b3 end: left-bottom
|
||
{ x: S1, y: YH }, // b4 end: left-top
|
||
{ x: S1, y: YH }, // b5: stay
|
||
]
|
||
|
||
export function TsDiagramView({ step, lang }: { step: number; lang: string }) {
|
||
const fr = lang.startsWith('fr')
|
||
const dark = useDarkMode()
|
||
const s = Math.min(step, 4)
|
||
|
||
const ink = dark ? '#EDE7DB' : '#242422'
|
||
const muted = dark ? '#A39C8D' : '#686762'
|
||
const plum = dark ? '#D07AA6' : '#9F3F70'
|
||
const blue = dark ? '#7FA8CC' : '#3F6F9F'
|
||
const paper = dark ? '#17140F' : '#F4F0E8'
|
||
|
||
const m = MARKER[s]
|
||
const qhW = S2 - S1
|
||
|
||
return (
|
||
<svg
|
||
viewBox="0 0 680 320"
|
||
className="w-full"
|
||
role="img"
|
||
aria-label={fr ? 'Diagramme T–s du cycle de Carnot' : 'T–s diagram of the Carnot cycle'}
|
||
style={{ background: paper, display: 'block' }}
|
||
>
|
||
{/* axes */}
|
||
<line x1={X0} y1={Y0} x2={650} y2={Y0} stroke={ink} strokeWidth={1.5} />
|
||
<line x1={X0} y1={Y0} x2={X0} y2={30} stroke={ink} strokeWidth={1.5} />
|
||
<text x={648} y={Y0 + 18} fontSize={12} fill={muted} textAnchor="end" fontStyle="italic">s</text>
|
||
<text x={X0 - 8} y={40} fontSize={12} fill={muted} textAnchor="end" fontStyle="italic">T</text>
|
||
|
||
{/* isotherm T_h */}
|
||
<line x1={X0} y1={YH} x2={650} y2={YH} stroke={plum} strokeWidth={1} strokeDasharray="5 4" opacity={0.45} />
|
||
<text x={X0 - 8} y={YH + 4} fontSize={11} fill={plum} textAnchor="end" fontWeight={600}>T_h</text>
|
||
{/* isotherm T_c */}
|
||
<line x1={X0} y1={YC} x2={650} y2={YC} stroke={blue} strokeWidth={1} strokeDasharray="5 4" opacity={0.45} />
|
||
<text x={X0 - 8} y={YC + 4} fontSize={11} fill={blue} textAnchor="end" fontWeight={600}>T_c</text>
|
||
|
||
{/* Q_h area (beat ≥ 0, shown from beat 0) */}
|
||
<rect
|
||
x={S1} y={YH} width={qhW} height={Y0 - YH}
|
||
fill={plum} opacity={s >= 0 ? 0.10 : 0}
|
||
style={{ transition: 'opacity 600ms ease' }}
|
||
/>
|
||
{s === 0 ? (
|
||
<text x={(S1 + S2) / 2} y={(YH + Y0) / 2} textAnchor="middle" fontSize={15} fontWeight={800} fill={plum}>Q_h</text>
|
||
) : null}
|
||
{/* Q_c area (from beat 2) */}
|
||
<rect
|
||
x={S1} y={YC} width={qhW} height={Y0 - YC}
|
||
fill={blue} opacity={s >= 2 ? 0.16 : 0}
|
||
style={{ transition: 'opacity 600ms ease' }}
|
||
/>
|
||
{s >= 2 && s < 4 ? (
|
||
<text x={(S1 + S2) / 2} y={(YC + Y0) / 2} textAnchor="middle" fontSize={15} fontWeight={800} fill={blue}>Q_c</text>
|
||
) : null}
|
||
{/* W = cycle area (beat 4) */}
|
||
<rect
|
||
x={S1} y={YH} width={qhW} height={YC - YH}
|
||
fill={plum} opacity={s === 4 ? 0.18 : 0}
|
||
style={{ transition: 'opacity 600ms ease' }}
|
||
/>
|
||
{s === 4 ? (
|
||
<text x={(S1 + S2) / 2} y={(YH + YC) / 2} textAnchor="middle" fontSize={17} fontWeight={800} fill={plum}>W</text>
|
||
) : null}
|
||
|
||
{/* cycle edges, drawn progressively */}
|
||
{/* top: b1 (isothermal expansion) */}
|
||
<line x1={S1} y1={YH} x2={S2} y2={YH} stroke={ink} strokeWidth={2.5} opacity={s >= 0 ? 1 : 0.15} />
|
||
<polygon points={`${S2 - 14},${YH - 5} ${S2 - 4},${YH} ${S2 - 14},${YH + 5}`} fill={ink} opacity={s >= 0 ? 1 : 0.15} />
|
||
{/* right: b2 (adiabatic expansion) */}
|
||
<line x1={S2} y1={YH} x2={S2} y2={YC} stroke={ink} strokeWidth={2.5} opacity={s >= 1 ? 1 : 0.15} style={{ transition: 'opacity 500ms ease' }} />
|
||
<polygon points={`${S2 - 5},${YC - 14} ${S2},${YC - 4} ${S2 + 5},${YC - 14}`} fill={ink} opacity={s >= 1 ? 1 : 0.15} style={{ transition: 'opacity 500ms ease' }} />
|
||
{/* bottom: b3 (isothermal compression) */}
|
||
<line x1={S2} y1={YC} x2={S1} y2={YC} stroke={ink} strokeWidth={2.5} opacity={s >= 2 ? 1 : 0.15} style={{ transition: 'opacity 500ms ease' }} />
|
||
<polygon points={`${S1 + 14},${YC - 5} ${S1 + 4},${YC} ${S1 + 14},${YC + 5}`} fill={ink} opacity={s >= 2 ? 1 : 0.15} style={{ transition: 'opacity 500ms ease' }} />
|
||
{/* left: b4 (adiabatic compression) */}
|
||
<line x1={S1} y1={YC} x2={S1} y2={YH} stroke={ink} strokeWidth={2.5} opacity={s >= 3 ? 1 : 0.15} style={{ transition: 'opacity 500ms ease' }} />
|
||
<polygon points={`${S1 - 5},${YH + 14} ${S1},${YH + 4} ${S1 + 5},${YH + 14}`} fill={ink} opacity={s >= 3 ? 1 : 0.15} style={{ transition: 'opacity 500ms ease' }} />
|
||
|
||
{/* entropy ticks */}
|
||
<text x={S1} y={Y0 + 16} fontSize={10} fill={muted} textAnchor="middle">s₁</text>
|
||
<text x={S2} y={Y0 + 16} fontSize={10} fill={muted} textAnchor="middle">s₂</text>
|
||
|
||
{/* state marker */}
|
||
<circle cx={m.x} cy={m.y} r={6} fill={plum} stroke={paper} strokeWidth={2} style={{ transition: EASE }} />
|
||
</svg>
|
||
)
|
||
}
|