fix video and landing page
This commit is contained in:
parent
ed1d5d913b
commit
acf77b11c9
@ -202,12 +202,24 @@ export default function PayCommissionPage() {
|
|||||||
const freightCurrency = booking.freightCurrency || booking.primaryCurrency || 'USD';
|
const freightCurrency = booking.freightCurrency || booking.primaryCurrency || 'USD';
|
||||||
const fobCurrency = booking.fobCurrency || 'EUR';
|
const fobCurrency = booking.fobCurrency || 'EUR';
|
||||||
const hasBreakdown = freightTotal != null || fobTotal != null;
|
const hasBreakdown = freightTotal != null || fobTotal != null;
|
||||||
// Single transport total, always shown in USD = freight + FOB summed.
|
|
||||||
// New bookings store the freight/FOB breakdown; legacy ones only keep the
|
// Le total de transport n'a de sens que si ses composantes partagent une
|
||||||
// per-currency price sums. In both cases the charges are added together.
|
// devise. L'ancien calcul additionnait fret et frais FOB sans les comparer —
|
||||||
const transportTotalUSD = hasBreakdown
|
// et, dans le cas des anciennes reservations, `priceUSD + priceEUR`, soit des
|
||||||
|
// dollars ajoutes a des euros — puis etiquetait systematiquement le resultat
|
||||||
|
// en USD. Un fret de 353 EUR s'affichait donc « 353,00 $US ».
|
||||||
|
const primaryCurrency = booking.primaryCurrency || 'EUR';
|
||||||
|
const hasFob = fobTotal != null && fobTotal > 0;
|
||||||
|
const currenciesMatch = !hasFob || fobCurrency === freightCurrency;
|
||||||
|
|
||||||
|
const transportCurrency = hasBreakdown ? freightCurrency : primaryCurrency;
|
||||||
|
const transportTotal = hasBreakdown
|
||||||
|
? currenciesMatch
|
||||||
? (freightTotal ?? 0) + (fobTotal ?? 0)
|
? (freightTotal ?? 0) + (fobTotal ?? 0)
|
||||||
: (booking.priceUSD || 0) + (booking.priceEUR || 0);
|
: // Devises differentes : aucun total honnete n'est calculable ici, les
|
||||||
|
// deux lignes ci-dessus se suffisent.
|
||||||
|
null
|
||||||
|
: ((primaryCurrency === 'USD' ? booking.priceUSD : booking.priceEUR) ?? 0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-brand-blue/5 py-10 px-4">
|
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-brand-blue/5 py-10 px-4">
|
||||||
@ -562,13 +574,15 @@ export default function PayCommissionPage() {
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{/* Single transport total — always in USD (fret + FOB) */}
|
{/* Total de transport, dans la devise de ses composantes. */}
|
||||||
|
{transportTotal != null && (
|
||||||
<div className="flex justify-between text-sm border-t pt-3">
|
<div className="flex justify-between text-sm border-t pt-3">
|
||||||
<span className="text-neutral-700 font-medium">Total</span>
|
<span className="text-neutral-700 font-medium">Total</span>
|
||||||
<span className="font-bold text-brand-navy">
|
<span className="font-bold text-brand-navy">
|
||||||
{formatPrice(transportTotalUSD, 'USD')}
|
{formatPrice(transportTotal, transportCurrency)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
<p className="text-xs text-neutral-400 pt-1">
|
<p className="text-xs text-neutral-400 pt-1">
|
||||||
Fret et frais FOB sont réglés directement au transporteur.
|
Fret et frais FOB sont réglés directement au transporteur.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@ -159,9 +159,9 @@
|
|||||||
"ctaRegister": "Create a free account",
|
"ctaRegister": "Create a free account",
|
||||||
"ctaDemo": "See the demo",
|
"ctaDemo": "See the demo",
|
||||||
"demo": {
|
"demo": {
|
||||||
"ariaLabel": "Demo: ocean rate search from {origin} to {destination}, three offers compared",
|
"ariaLabel": "Sample: ocean rate search from {origin} to {destination}, three offers compared",
|
||||||
"label": "Rate search",
|
"label": "Rate search",
|
||||||
"live": "Live",
|
"live": "Sample",
|
||||||
"origin": "Origin",
|
"origin": "Origin",
|
||||||
"destination": "Destination",
|
"destination": "Destination",
|
||||||
"load": "LCL · 2.4 CBM · 850 kg · 1 pallet",
|
"load": "LCL · 2.4 CBM · 850 kg · 1 pallet",
|
||||||
|
|||||||
@ -159,9 +159,9 @@
|
|||||||
"ctaRegister": "Créer un compte gratuit",
|
"ctaRegister": "Créer un compte gratuit",
|
||||||
"ctaDemo": "Voir la démo",
|
"ctaDemo": "Voir la démo",
|
||||||
"demo": {
|
"demo": {
|
||||||
"ariaLabel": "Démonstration : recherche de tarif maritime de {origin} vers {destination}, trois offres comparées",
|
"ariaLabel": "Exemple : recherche de tarif maritime de {origin} vers {destination}, trois offres comparées",
|
||||||
"label": "Recherche de tarif",
|
"label": "Recherche de tarif",
|
||||||
"live": "En direct",
|
"live": "Exemple",
|
||||||
"origin": "Départ",
|
"origin": "Départ",
|
||||||
"destination": "Arrivée",
|
"destination": "Arrivée",
|
||||||
"load": "LCL · 2,4 CBM · 850 kg · 1 palette",
|
"load": "LCL · 2,4 CBM · 850 kg · 1 palette",
|
||||||
|
|||||||
BIN
apps/frontend/public/assets/videos/demo-xpeditis-2026.webm
Normal file
BIN
apps/frontend/public/assets/videos/demo-xpeditis-2026.webm
Normal file
Binary file not shown.
@ -60,9 +60,13 @@ export function DemoVideoModal({ isOpen, onClose }: DemoVideoModalProps) {
|
|||||||
<X className="w-7 h-7" />
|
<X className="w-7 h-7" />
|
||||||
</button>
|
</button>
|
||||||
<div className="rounded-2xl overflow-hidden shadow-2xl ring-1 ring-white/20 bg-black aspect-video">
|
<div className="rounded-2xl overflow-hidden shadow-2xl ring-1 ring-white/20 bg-black aspect-video">
|
||||||
|
{/* Le WebM 2026 passe en premier : le navigateur retient la
|
||||||
|
premiere source qu'il sait lire, et c'est la seule a jour du
|
||||||
|
design actuel. Le MP4 de juillet reste en dernier recours
|
||||||
|
pour Safari, qui ne lit pas le VP8. */}
|
||||||
<video controls autoPlay playsInline className="w-full h-full object-contain">
|
<video controls autoPlay playsInline className="w-full h-full object-contain">
|
||||||
|
<source src="/assets/videos/demo-xpeditis-2026.webm" type="video/webm" />
|
||||||
<source src="/assets/videos/demo-xpeditis.mp4" type="video/mp4" />
|
<source src="/assets/videos/demo-xpeditis.mp4" type="video/mp4" />
|
||||||
<source src="/assets/videos/demo-xpeditis.webm" type="video/webm" />
|
|
||||||
</video>
|
</video>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|||||||
@ -16,14 +16,36 @@ import { EASE } from './motion';
|
|||||||
* differents. Aucune donnee reelle n'est appelee : la sequence est scriptee.
|
* differents. Aucune donnee reelle n'est appelee : la sequence est scriptee.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route et tarifs de la demonstration.
|
||||||
|
*
|
||||||
|
* Ce ne sont **pas** les tarifs commerciaux : ils sont volontairement arrondis
|
||||||
|
* et decales. Ils sont en revanche cales sur les vraies grilles export
|
||||||
|
* (ECU Worldwide, NVO Consolidation, SSC Consolidation, TCC Logistics), pour
|
||||||
|
* un envoi LCL de 2,4 CBM et 850 kg au depart du Havre — origine qui porte
|
||||||
|
* l'essentiel de la couverture. Les ordres de grandeur, les ecarts entre
|
||||||
|
* transporteurs et les temps de transit sont donc realistes.
|
||||||
|
*
|
||||||
|
* Les montants sont des « tout compris » : fret maritime plus frais FOB
|
||||||
|
* (documentation, ISPS, manutention, SOLAS, douane), et non le seul fret — un
|
||||||
|
* fret nu se chiffre a quelques dizaines d'euros et donnerait une idee fausse
|
||||||
|
* de ce que paie le client.
|
||||||
|
*
|
||||||
|
* Alexandrie a ete retenue parce que la route est couverte par trois
|
||||||
|
* compagnies, libellee en euros de bout en bout, et qu'elle illustre le vrai
|
||||||
|
* arbitrage du metier : la plus rapide n'est pas la moins chere.
|
||||||
|
*
|
||||||
|
* Recalcul : lire les CSV de `grille_tarifaire_xpeditis_prod`, prendre
|
||||||
|
* `max(freightRatePerCBM x 2,4 ; freightMinimum)` puis ajouter les postes FOB,
|
||||||
|
* et arrondir.
|
||||||
|
*/
|
||||||
const ORIGIN = { code: 'FRLEH', city: 'Le Havre' };
|
const ORIGIN = { code: 'FRLEH', city: 'Le Havre' };
|
||||||
const DESTINATION = { code: 'CNSHA', city: 'Shanghai' };
|
const DESTINATION = { code: 'EGEDK', city: 'Alexandrie' };
|
||||||
|
|
||||||
/** Transporteurs repris du bandeau partenaires, tarifs indicatifs. */
|
|
||||||
const OFFERS = [
|
const OFFERS = [
|
||||||
{ carrier: 'ICL', days: 26, price: 1390 },
|
{ carrier: 'SSC Consolidation', days: 11, price: 180, best: true },
|
||||||
{ carrier: 'ECU Line', days: 28, price: 1240 },
|
{ carrier: 'TCC Logistics', days: 14, price: 215 },
|
||||||
{ carrier: 'Vanguard', days: 31, price: 1180, best: true },
|
{ carrier: 'ECU Worldwide', days: 9, price: 260 },
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
53
docs/script-video-demo.md
Normal file
53
docs/script-video-demo.md
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# Vidéo de présentation Xpeditis — script
|
||||||
|
|
||||||
|
**Personne :** Camille Roux, responsable transit chez un transitaire français.
|
||||||
|
**Ce qu'elle veut :** placer ses expéditions LCL au meilleur prix, et savoir à tout
|
||||||
|
moment où en est son activité.
|
||||||
|
**Durée visée :** ~1 min 10 s · 1280×720 · sans voix off (lisible en autoplay muet).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Séquence
|
||||||
|
|
||||||
|
| # | Écran | Ce que le spectateur comprend |
|
||||||
|
|---|---|---|
|
||||||
|
| 1 | Carton d'ouverture | Xpeditis est une plateforme B2B de fret maritime LCL. |
|
||||||
|
| 2 | Page d'accueil, recherche jouée | Deux ports, trois offres comparables en 1,8 s. C'est la promesse. |
|
||||||
|
| 3 | **Tableau de bord — six temps** | **Le cœur, ~40 s.** Le projecteur désigne successivement : ce qui demande une action, les indicateurs du mois, la courbe sur six mois, les réponses des transporteurs, les routes les plus actives, le classement des transporteurs. Puis la vue d'ensemble se rouvre. |
|
||||||
|
| 4 | Page de règlement | Le forfait Xpeditis est distinct du fret, réglé en ligne. |
|
||||||
|
| 5 | Liste des réservations | Chaque expédition et son statut, de la demande à l'acceptation. |
|
||||||
|
| 6 | Carton de clôture | Appel à l'action. |
|
||||||
|
|
||||||
|
## Règles de réalisation
|
||||||
|
|
||||||
|
1. **Pas de carton plein écran entre les scènes.** Ils immobilisaient l'image
|
||||||
|
cinq secondes à chaque fois et transformaient la démo en diaporama. Seuls
|
||||||
|
l'ouverture et la clôture en gardent un ; ailleurs, le commentaire passe en
|
||||||
|
**sous-titre sur l'interface vivante**, qui ne quitte jamais l'écran.
|
||||||
|
2. **Un projecteur désigne l'élément commenté.** Sans lui, un simple défilement
|
||||||
|
laisse le spectateur chercher où regarder. C'est ce qui « montre » vraiment.
|
||||||
|
3. **Les transitions passent par un voile opaque**, jamais par un fondu croisé :
|
||||||
|
croiser deux images à 50 % se lit comme un bug d'affichage.
|
||||||
|
4. **« Prête » se vérifie, ne se suppose pas :** plus aucun squelette
|
||||||
|
`animate-pulse`, images *visibles* chargées, plafond à 2,5 s. Ne **jamais**
|
||||||
|
attendre `networkidle` — le websocket HMR du serveur de dev ne se tait pas,
|
||||||
|
l'attente va au bout de ses 30 s et fige l'image.
|
||||||
|
5. **Défilements amortis** (cubic-out), pas de `scroll-behavior: smooth`.
|
||||||
|
6. **Rien de parasite :** pastille cookies, indicateur de dev.
|
||||||
|
7. **Toujours enregistrer sur un serveur de dev fraîchement démarré.** Après
|
||||||
|
quelques heures, il finit par ne plus rendre l'application.
|
||||||
|
8. **Vérifier par lecture réelle, pas par `seek` :** les images-clés du WebM
|
||||||
|
VP8 sont trop espacées, un échantillonnage par `seek` fait croire à une
|
||||||
|
vidéo figée.
|
||||||
|
9. **Données fictives mais plausibles**, cohérentes d'un écran à l'autre.
|
||||||
|
10. **Aucune image parfaitement fixe.** C'est la règle la plus importante : une
|
||||||
|
frame identique à la précédente pendant plusieurs secondes se lit comme un
|
||||||
|
gel, même quand rien ne charge. Les panneaux de transition portent une
|
||||||
|
lueur turquoise qui dérive, le projecteur respire pendant les temps de
|
||||||
|
lecture. Se vérifie à la mesure — écart moyen entre images consécutives —
|
||||||
|
et non à l'œil.
|
||||||
|
11. **Le ciblage du projecteur remonte automatiquement** jusqu'au premier
|
||||||
|
ancêtre d'au moins 260 × 140 px, et la recherche du texte couvre aussi
|
||||||
|
`label`, `td`, `h4`… Exiger une classe précise échouait silencieusement sur
|
||||||
|
les pages qui ne l'ont pas — l'écran restait alors sans projecteur, donc
|
||||||
|
parfaitement immobile.
|
||||||
Loading…
Reference in New Issue
Block a user