/* =================================================================================================
   LA PAGE DE COMMANDE DES PROFESSIONNELS, ET LA PAGE D'ATTENTE.
   =================================================================================================

   Toutes les valeurs viennent de src/theme/tokens.ts, recopiees en dur : une page statique ne peut
   pas importer un module de l'application React Native. Quand un chiffre change la-bas, il change
   ici, et le commentaire dit lequel.

   LA SCENE D'USAGE, qui a decide de la mise en page. Un restaurateur commande le soir ou tot le
   matin, le plus souvent sur son telephone dans une cuisine, parfois sur la tablette ou l'ordinateur
   du bureau. Il connait deja ses produits, il en commande les memes chaque semaine. Ce qui compte
   n'est donc pas de lui presenter un catalogue, c'est de lui faire remplir vite un formulaire qu'il
   connait par coeur, sans jamais perdre de vue le bouton d'envoi.

   LA MISE EN PAGE EST LA MEME PARTOUT, et c'est un choix. Une seule colonne, du haut vers le bas,
   sur les trois tailles d'ecran : quand, quoi, un mot, envoyer. Ce qui change avec la largeur est la
   LISTE DES PRODUITS, qui passe a deux puis trois colonnes, parce que c'est la seule partie dont la
   longueur depend du catalogue de la boulangerie. Une mise en page qui se reorganise entre le
   telephone et la tablette obligerait a rapprendre l'ecran ; une liste qui s'elargit, non.
   ============================================================================================== */

/* -------------------------------------------------------------------------------------------------
   Figtree, hebergee ici et nulle part ailleurs. L'application en fait sa seule famille et batit sa
   hierarchie sur les graisses (tokens.ts, « font ») : la page porte donc la meme. Servie depuis le
   site et non depuis Google Fonts, pour deux raisons qui comptent autant l'une que l'autre : la
   politique de securite de _headers ferme toute origine externe, et aucune requete d'un client de
   boulangerie ne part chez un tiers. Licence OFL, copiee dans assets/figtree/LICENSE.txt.

   font-display: swap. Le texte s'affiche immediatement dans la police systeme puis bascule : sur une
   4G de cuisine, attendre 40 Ko avant de montrer quoi que ce soit serait le mauvais compromis.
   ---------------------------------------------------------------------------------------------- */
@font-face {
  font-family: "Figtree";
  src: url("/assets/figtree/Figtree_400Regular.ttf") format("truetype");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Figtree";
  src: url("/assets/figtree/Figtree_500Medium.ttf") format("truetype");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Figtree";
  src: url("/assets/figtree/Figtree_600SemiBold.ttf") format("truetype");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Figtree";
  src: url("/assets/figtree/Figtree_700Bold.ttf") format("truetype");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Figtree";
  src: url("/assets/figtree/Figtree_800ExtraBold.ttf") format("truetype");
  font-weight: 800;
  font-style: normal;
  font-display: swap;
}

:root {
  /* Les bruns et les pierres de tokens.ts. Seuls ceux que cette page emploie sont recopies. */
  --titre: #2B1A0C;        /* text.title */
  --intitule: #432A14;     /* text.heading */
  --corps: #5C3A1C;        /* text.body */
  --discret: #6E6049;      /* text.muted, 6.12:1 sur blanc */
  --croute: #8D5A2B;       /* brand.crust, et border.focus */
  --mie: #FDFBD4;          /* brand.crumb, texte sur brun */
  --trait: #E8DCC4;        /* border.default, decoratif */
  --trait-carte: #C3B69C;  /* 2.00:1 sur blanc, contour des cartes seulement */
  --trait-fort: #8C7D61;   /* border.strong, 4.02:1, contours porteurs de sens */
  --creux: #F7F2E7;        /* surface.inset */
  --accent-aplat: #A85B14; /* accentFill, 5.03:1 avec du blanc */
  --accent-doux: #F9E4CC;  /* accentSoft */
  --accent-texte: #8A4D12; /* text.onAccentSoft */
  --erreur-fond: #F8E0D8;  /* errorSoft */
  --erreur-texte: #8E3220; /* errorStrong */
  --ok-fond: #EAF0D4;      /* successSoft */
  --ok-texte: #465F21;     /* successStrong */

  --gouttiere: 12px;       /* mesuree sur Levain, decision du 29 juillet 2026 */
  --rayon: 16px;           /* radius.card */
  --rayon-chip: 10px;      /* radius.chip */
  --hauteur-champ: 52px;   /* metrics.fieldHeight et btnHeight */

  /* durations.base et durations.fast de tokens.ts. */
  --duree: 200ms;
  --duree-courte: 120ms;
}

*,
*::before,
*::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  color-scheme: light;
}

/* LE VOILE DE MIE, decision du design system reprise telle quelle : fond blanc franc, chaleur sur le
   haut seulement (gradients.screen, #F6E7CD a 0 % puis blanc a 38 %). Sa hauteur est FIXE en pixels
   et non en pourcentage de l'ecran : sur un ordinateur, « le tiers haut » d'une fenetre de 1200 px
   ferait une bande de 400 px sur le telephone et de 400 px ici, ce qui est justement l'effet
   recherche. Un pourcentage aurait donne une teinte differente selon la machine. */
body {
  margin: 0;
  min-height: 100%;
  background-color: #FFFFFF;
  background-image: linear-gradient(
    to bottom,
    #F6E7CD 0px,
    #FBF3E4 52px,
    #FEFCF8 122px,
    #FFFFFF 166px
  );
  background-repeat: no-repeat;
  color: var(--corps);
  font-family: "Figtree", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-size: 16px;
  line-height: 1.4;
  -webkit-font-smoothing: antialiased;
}

/* -------------------------------------------------------------------------------------------------
   L'ossature
   ---------------------------------------------------------------------------------------------- */
.enveloppe {
  max-width: 640px;
  margin: 0 auto;
  padding: 20px var(--gouttiere) 0;
}

.marque {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-weight: 800;
  letter-spacing: 0.3px;
  color: var(--croute);
  font-size: 16px;
  margin: 0 0 16px;
}
/* L'icone porte deja son propre fond et son propre arrondi (la tuile brune du logo) : rien a lui
   ajouter ici. alt vide et non « notemie » : le mot est juste a cote, un lecteur d'ecran qui le
   dirait deux fois serait moins clair, pas plus. */
.marque img {
  display: block;
  width: 24px;
  height: 24px;
}

/* Carte posee sur la page : bordure, JAMAIS d'ombre. L'elevation se declare une seule fois
   (tokens.ts, « shadows ») : l'ombre est reservee a ce qui flotte reellement, ici la seule barre
   d'envoi collante. Une bordure sous une ombre douce, c'est la carte fantome. */
/* Le contour de la carte est le SEUL a porter « --trait-carte » : sans ombre, c'est lui qui doit
   dire ou une carte finit et ou la suivante commence, et le decoratif « --trait » (1.28:1 sur
   blanc) n'y suffisait pas, mesure faite sur capture d'appareil le 7 aout 2026. Il reste plus
   clair que « --trait-fort », qui cerne les champs de saisie : une carte ne doit pas peser autant
   qu'une chose ou l'on ecrit. Les separateurs internes gardent « --trait ». */
.carte {
  background: #FFFFFF;
  border: 1px solid var(--trait-carte);
  border-radius: var(--rayon);
  padding: 20px;
  margin: 0 0 16px;
}

h1 {
  font-size: 28px;
  line-height: 1.15;
  font-weight: 700;
  color: var(--titre);
  margin: 0 0 6px;
  letter-spacing: -0.3px;
  text-wrap: balance;
}

.sous {
  margin: 0;
  color: var(--discret);
  font-size: 15px;
}

/* type.label de tokens.ts : le seul intitule en capitales de la page. Un intitule par carte, ce qui
   en fait une grammaire et non un tic. */
h2 label { color: inherit; cursor: pointer; }

h2 {
  font-size: 12px;
  line-height: 16px;
  text-transform: uppercase;
  letter-spacing: 0.8px;
  color: var(--discret);
  margin: 0 0 14px;
  font-weight: 600;
}

.libelle {
  display: block;
  font-size: 13px;
  color: var(--discret);
  margin: 0 0 6px;
}

/* -------------------------------------------------------------------------------------------------
   Les champs
   ---------------------------------------------------------------------------------------------- */
input[type="date"],
input[type="time"],
input[type="number"],
input[type="text"] {
  font: inherit;
  color: var(--titre);
  background: #FFFFFF;
  border: 1px solid var(--trait-fort);
  border-radius: 14px;
  padding: 0 14px;
  height: var(--hauteur-champ);
  width: 100%;
  transition: border-color var(--duree-courte) ease-out, box-shadow var(--duree-courte) ease-out;
}

/* CONVENTION DE LEVAIN, reprise par l'application le 29 juillet 2026 et par cette page : une valeur
   saisie s'ecrit en gras et dans le plus sombre des bruns, un champ vide reste gris et maigre. Un
   coup d'oeil suffit alors a voir ce qui est deja rempli. Sur les nombres, :placeholder-shown le
   fait sans une ligne de JavaScript ; sur les dates et les heures, ou ce selecteur n'est pas fiable
   d'un navigateur a l'autre, commander.js pose la classe « rempli ». */
input[type="number"] {
  font-weight: 700;
  color: var(--titre);
}
input[type="number"]:placeholder-shown {
  font-weight: 400;
  color: var(--discret);
}
input[type="date"],
input[type="time"] {
  font-weight: 400;
  color: var(--discret);
}
input.rempli {
  font-weight: 700;
  color: var(--titre);
}

/* Le gris par defaut des navigateurs tombe sous 4.5:1. text.muted tient 6.12:1 sur blanc. */
::placeholder {
  color: var(--discret);
  opacity: 1;
}

input:focus-visible,
button:focus-visible {
  outline: 2px solid var(--croute);
  outline-offset: 2px;
}
input:focus { border-color: var(--croute); }

/* Correction du 7 aout 2026, sur une capture de Paul-Antoine : « 07/08/2026 » arrivait tronque en
   « 07/08/202 » dans son champ. box-sizing etait deja bon, ce n'etait donc pas un debordement de
   boite mais un manque de place reel : deux champs a flex:1 se partagent la largeur SANS jamais
   regarder ce qu'ils contiennent, et un input[type=date] porte, en plus de sa date, l'icone native
   du selecteur du navigateur, large d'une vingtaine de pixels et impossible a retirer.

   Le remede n'est pas de grappiller quelques pixels de padding ou de police : ca ne ferait que
   deplacer le seuil, et le prochain telephone plus etroit le repasserait. flex-wrap plus une base
   de 150px laisse les deux champs cote a cote quand ils tiennent, et les EMPILE d'eux-memes quand
   ils ne tiennent pas. Aucun point de rupture ecrit en dur, donc rien a reajuster le jour d'un
   ecran different : c'est le contenu qui decide. */
.duo {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}
.duo > div {
  flex: 1 1 150px;
  min-width: 0;
}
.champ { margin: 0 0 16px; }
.champ:last-child { margin-bottom: 0; }

/* -------------------------------------------------------------------------------------------------
   LE CONTROLE SEGMENTE, recopie valeur par valeur de src/components/ui/SegmentedControl.tsx.

   Correction du 7 aout 2026, sur un defaut releve par Paul-Antoine et qui etait le mien. J'avais lu
   la regle de l'application, « un trait pour la piste posee et l'ombre pour le seul curseur qui
   flotte », comme si la piste n'avait QUE son trait. Elle a les deux : un fond beige (surface.inset)
   ET le trait des cartes. Sans le fond, le curseur blanc se posait sur une carte blanche et plus
   rien ne distinguait « Commande unique » de « Commande recurrente » : on ne pouvait pas deviner que
   l'un des deux etait un bouton.

   La geometrie vient du composant : piste de 3 px de marge et 1 px de trait autour d'un curseur de
   36 px, ce qui porte LA PISTE a 44 px, et des rayons concentriques, 10 px pour le curseur, 14 pour
   la piste.

   CE QU'ON TOUCHE FAIT 36 PX ET NON 44, ET CE COMMENTAIRE A DIT LE CONTRAIRE JUSQU'AU 3 SEPTEMBRE
   2026. Il affirmait que cette geometrie « fait exactement les 44 px de cible tactile du projet » :
   c'est vrai de la PISTE, qui n'est pas cliquable, et faux du bouton, dont le min-height vaut 36 px
   (regle « .segment button » plus bas). L'ecart est donc reel, il porte sur « Livraison / A
   retirer » comme sur « Commande unique / recurrente », et il est NOMME ICI plutot que corrige :
   cette feuille est partagee par tout le site, page d'accueil comprise, rien n'a ete vu sur un
   appareil, et une retouche de geometrie la veille d'une publication se paierait ailleurs. Un
   commentaire faux finit par autoriser sa propre suppression ; celui-la dit desormais ce qui est.

   L'option NON choisie recule en brun-pierre et en graisse 500, pas en gris : mesure faite sur
   l'appareil, la couleur de marque faisait de l'option non choisie le mot le plus chaud du
   controle, donc l'oeil partait du mauvais cote.
   ---------------------------------------------------------------------------------------------- */
.segment {
  position: relative;
  display: flex;
  gap: 2px;
  background: var(--creux);
  border: 1px solid var(--trait);
  border-radius: 14px;
  padding: 3px;
  margin: 0 0 16px;
}
.segment:last-child { margin-bottom: 0; }

/* LE CURSEUR QUI GLISSE. Sa largeur se deduit de la geometrie de la piste et n'est ecrite nulle part
   en dur : 100 % moins les 6 px de marge interieure et les 2 px d'ecart, divise par deux, soit
   « 50% - 4px ». Son deplacement vaut sa propre largeur plus l'ecart, d'ou « 100% + 2px ».
   Deux options exactement, ici comme dans l'application : ce calcul n'a pas a etre general. */
.segment .curseur {
  position: absolute;
  top: 3px;
  bottom: 3px;
  left: 3px;
  width: calc(50% - 4px);
  background: #FFFFFF;
  border-radius: var(--rayon-chip);
  /* shadows.sm de tokens.ts : teintee brown-950 et jamais grise, avec son decalage et son flou. */
  box-shadow: 0 1px 2px rgba(43, 26, 12, 0.08);
  /* Sortie exponentielle depuis un etat deja visible : le curseur part vite et se pose, il ne
     rebondit pas. 240 ms, la duree ou un deplacement se lit encore sans se faire attendre. */
  transition: transform 240ms cubic-bezier(0.16, 1, 0.3, 1);
}
.segment.seconde .curseur { transform: translateX(calc(100% + 2px)); }

/* « min-height » ET NON « height », correction du 7 aout 2026 sur une capture d'ecran de telephone :
   « Commande recurrente » passe a deux lignes sur un ecran etroit, et une hauteur figee a 36 px
   laissait le texte deborder du curseur blanc. Le curseur, lui, est tendu du haut au bas de la
   piste (top et bottom a 3 px) : il suit donc la hauteur reelle sans qu'on ait a la lui dire.

   C'est le meme remede que partout ailleurs dans ce projet : une hauteur qui se MESURE plutot
   qu'une hauteur qu'on decide. Raccourcir le libellé aurait marche sur ce telephone-ci et casse au
   premier reglage de police plus grand. */
.segment button {
  position: relative; /* au-dessus du curseur */
  flex: 1;
  font: inherit;
  font-weight: 500;
  font-size: 14px;
  line-height: 1.2;
  color: var(--discret);
  background: transparent;
  border: 0;
  border-radius: var(--rayon-chip);
  min-height: 36px;
  padding: 8px 6px;
  text-wrap: balance;
  cursor: pointer;
  transition: color var(--duree) ease-out;
}
.segment button[aria-pressed="true"] {
  color: var(--titre);
  font-weight: 600;
}
@media (hover: hover) {
  .segment button:not([aria-pressed="true"]):hover { color: var(--titre); }
}

/* -------------------------------------------------------------------------------------------------
   Les jours de la semaine
   ---------------------------------------------------------------------------------------------- */
.jours { display: flex; gap: 6px; }
.jours label {
  flex: 1;
  text-align: center;
  border: 1px solid var(--trait-fort);
  border-radius: 12px;
  padding: 12px 0;
  min-height: 44px;
  font-weight: 600;
  color: var(--corps);
  cursor: pointer;
  -webkit-user-select: none;
  user-select: none;
  position: relative;
  transition: background-color var(--duree-courte) ease-out, color var(--duree-courte) ease-out,
    border-color var(--duree-courte) ease-out;
}
.jours input {
  position: absolute;
  opacity: 0;
  width: 100%;
  height: 100%;
  left: 0;
  top: 0;
  margin: 0;
  cursor: pointer;
}
.jours label.actif {
  background: var(--titre);
  border-color: var(--titre);
  color: var(--mie);
}
.jours label:focus-within {
  outline: 2px solid var(--croute);
  outline-offset: 2px;
}

/* -------------------------------------------------------------------------------------------------
   La liste des produits.

   AUCUN NOM DE CATEGORIE, demande de Paul-Antoine du 7 aout 2026 : le professionnel connait ses
   produits et n'a pas besoin qu'on lui rappelle dans quel rayon la boulangerie les range. L'ordre du
   catalogue est neanmoins conserve, categorie par categorie, parce qu'il regroupe naturellement ce
   qui se ressemble.

   CHAQUE LIGNE PORTE SON TRAIT AU-DESSUS, y compris la premiere. En une colonne cela fait un filet
   sous l'intitule de la carte ; en deux ou trois colonnes, cela evite qu'une ligne se retrouve sans
   separateur en haut de sa colonne. Une regle plutot qu'une exception.
   ---------------------------------------------------------------------------------------------- */
.produits {
  display: grid;
  grid-template-columns: 1fr;
  column-gap: 28px;
}

/* LE CADRE QUI DEFILE, pose seulement au-dela de huit produits (classe « longue », posee par
   commander.js). Sans lui, un catalogue de trente references enterre le champ « un mot pour votre
   boulangerie » et la mention d'information sous dix-huit cents pixels de rangees.

   AUCUN DEGRADE DE BAS DE CADRE, et c'est un choix contre l'habitude : un masque qui estompe la
   derniere ligne l'estompe AUSSI quand on a fini de defiler, c'est-a-dire exactement au moment ou
   le restaurateur lit la ligne qu'il cherchait. La rangee coupee en deux par le bord et la barre de
   defilement disent deja qu'il y a une suite, sans mentir sur la fin. */
.produits-cadre { position: relative; }

/* LE CADRE ET SA BARRE, cote a cote. La zone n'existe que pour les tenir a la meme hauteur : c'est
   l'etirement par defaut d'une ligne flex qui donne a la piste la hauteur exacte du cadre, quel que
   soit le mode de la page. Aucune hauteur n'est ecrite en dur, ni ici ni dans commander.js. */
.produits-zone {
  position: relative;
  display: flex;
  align-items: stretch;
  gap: 8px;
}
.produits-zone > .produits-cadre {
  flex: 1;
  min-width: 0;
}

/* LA HAUTEUR DU CADRE, ramenee de 55 vh a 320 px le 7 aout 2026, sur un defaut vecu par
   Paul-Antoine : « on a du mal a defiler vers le bas la page une fois qu'on a selectionne nos
   produits ».

   C'est le piege connu du cadre qui defile dans une page qui defile. A 55 vh le cadre occupait plus
   de la moitie de l'ecran : ou que le doigt se pose, il tombait dedans, et c'est la liste qui
   bougeait au lieu de la page. Le defilement ne se transmet a la page qu'une fois la liste arrivee
   au bout, ce qui n'arrive jamais avec trente-deux produits.

   320 px, soit environ cinq rangees, laissent de la page a attraper au-dessus et au-dessous du
   cadre. Ce n'est pas une valeur de gout : c'est la surface qu'il faut rendre au pouce. La
   recherche est la pour que la liste n'ait pas besoin d'etre longue a l'ecran.

   Cette regle ne vaut QUE dans le mode ou la page defile. En mode ecran fixe, plus bas dans ce
   fichier, la page ne defile pas du tout : le piege n'existe pas, et le cadre reprend toute la
   place restante.

   « OVERFLOW: HIDDEN » ET NON « AUTO » DEPUIS LE 10 AOUT 2026, et c'est le remede de fond. Les 320 px
   n'avaient pas suffi : les clients professionnels d'un testeur n'arrivaient toujours pas a
   descendre en bas de la page, parce qu'un doigt pose dans la liste tombe dans le cadre et que le
   defilement ne se transmet a la page qu'une fois la liste au bout, ce qui n'arrive jamais avec
   trente-deux produits. Un cadre en « hidden » laisse passer le geste a la page et reste pilotable
   par scrollTop depuis le JavaScript : c'est tout ce dont la barre de droite a besoin.

   « scrollbar-gutter » est parti avec : il reservait la place de la barre du navigateur, qui
   n'existe plus ici. */
.carte.longue .produits-cadre {
  max-height: 320px;
  overflow: hidden;
  border-bottom: 1px solid var(--trait);
  padding-bottom: 4px;
}

/* -------------------------------------------------------------------------------------------------
   LA BARRE DE DEFILEMENT DE LA LISTE, propre a la page et sans aucune dependance.

   Elle reprend la grammaire du controle segmente, quelques lignes plus haut : une piste au fond
   beige (surface.inset) ET a son trait, un curseur pose dessus avec son ombre. C'est la lecon du
   7 aout 2026, ou j'avais lu « un trait pour la piste » comme si elle n'avait QUE son trait, et ou
   le curseur blanc se posait sur une carte blanche.

   Le trait de la piste est ici le trait FORT et non le trait decoratif : cet objet n'est pas une
   bordure de carte, c'est un controle qu'il faut voir pour s'en servir. Le curseur prend la couleur
   de mie, plus chaude que le blanc du controle segmente, et son contour brun-croute : c'est ce
   contour, avec les deux traits de prehension et le chevron, qui dit qu'on peut le prendre. Sans eux
   deux beiges voisins ne se distingueraient pas.

   AUCUNE HAUTEUR NI AUCUNE POSITION DE CURSEUR ICI : les deux se mesurent a l'execution dans
   commander.js, parce que la hauteur du cadre depend du mode de la page (320 px quand elle defile,
   la place restante en ecran fixe) et de la recherche en cours.
   ---------------------------------------------------------------------------------------------- */
.defilement {
  flex: 0 0 auto;
  display: flex;
  /* Le doigt qui prend la barre ne doit RIEN faire d'autre : sans cette ligne le navigateur
     interprete le meme geste comme un defilement de la page et le curseur ne suit plus. */
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
}
/* L'attribut « hidden » perd sinon contre le « display: flex » ci-dessus. C'est lui, et non une
   classe, qui cache la barre quand tout le catalogue tient dans le cadre. */
.defilement[hidden] { display: none; }

.defilement-piste {
  position: relative;
  width: 18px;
  border-radius: 9px;
  background: var(--creux);
  border: 1px solid var(--trait-fort);
  cursor: pointer;
}
.defilement-curseur {
  position: absolute;
  left: 1px;
  right: 1px;
  top: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  border-radius: 7px;
  background: var(--mie);
  border: 1px solid var(--croute);
  color: var(--croute);
  /* shadows.sm de tokens.ts : teintee brown-950 et jamais grise. */
  box-shadow: 0 1px 2px rgba(43, 26, 12, 0.08);
  cursor: grab;
}
.defilement-curseur .prise {
  width: 8px;
  height: 2px;
  border-radius: 1px;
  background: currentColor;
}
.defilement-curseur .chevron { margin-top: 1px; }

.recherche {
  position: relative;
  margin: 0 0 12px;
}
/* La loupe est posee dans le champ et ne se laisse pas toucher : un clic dessus doit donner le
   curseur au champ, pas rien du tout. */
.icone-recherche {
  position: absolute;
  left: 14px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--discret);
  pointer-events: none;
}
.recherche input[type="search"] {
  font: inherit;
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 44px;
  padding: 0 14px;
  border: 1px solid var(--trait-fort);
  border-radius: 14px;
  background: #FFFFFF;
  color: var(--titre);
  transition: border-color var(--duree-courte) ease-out;
}
/* La place de la loupe, plus l'ecart qui la separe du texte. */
.recherche input[type="search"] { padding-left: 41px; }
.recherche input[type="search"]:focus { border-color: var(--croute); }
.recherche input[type="search"]:focus-visible {
  outline: 2px solid var(--croute);
  outline-offset: 2px;
}

.vide {
  margin: 16px 0;
  color: var(--discret);
  font-size: 15px;
}

.produit {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 8px;
  border-top: 1px solid var(--trait);
  min-height: 60px;
}

/* Une rangee deja saisie se repere d'un coup d'oeil dans une liste de trente. Un aplat tres pale et
   non un liseré de couleur : un « border-left » colore sur une ligne de liste est la decoration par
   defaut de la categorie, et il ne dit rien de plus qu'un fond. */
.produit.saisi { background: var(--creux); }
.produit .nom {
  flex: 1;
  min-width: 0;
  color: var(--titre);
  font-weight: 600;
}
.produit .unite {
  display: block;
  font-weight: 400;
  font-size: 13px;
  color: var(--discret);
}
.produit input {
  width: 92px;
  text-align: center;
  padding: 0 6px;
  flex: none;
}
/* Largeur FIXE et non ajustee au contenu : c'est elle qui aligne les champs de quantite d'une
   rangee a l'autre, y compris celles qui n'ont aucun suffixe. Elle valait 12 px, mesure sur le seul
   « g » ; l'anglais y ecrit « oz » depuis le 10 aout 2026, qui ne tient pas dans 12 px. 18 px est la
   largeur de « oz » en Figtree 13 px arrondie au point superieur. */
.produit .suffixe {
  font-size: 13px;
  color: var(--discret);
  width: 18px;
  flex: none;
}

/* -------------------------------------------------------------------------------------------------
   LA BARRE D'ENVOI, collante en bas sur toutes les tailles d'ecran.

   C'est le seul ajout de structure de cette refonte, et il repond a un defaut mesure sur la
   premiere version : le bouton vivait tout en bas du document, donc hors de vue pendant tout le
   remplissage, et rien ne disait combien de produits etaient deja saisis. Un restaurateur qui tape
   quinze quantites ne devait pas avoir a remonter pour verifier.

   Elle FLOTTE, donc elle porte une ombre et aucune bordure : l'inverse exact des cartes.
   ---------------------------------------------------------------------------------------------- */
.barre {
  position: sticky;
  bottom: 0;
  z-index: 2;
  background: #FFFFFF;
  border-radius: var(--rayon) var(--rayon) 0 0;
  padding: 12px 16px calc(12px + env(safe-area-inset-bottom, 0px));
  margin: 0 calc(var(--gouttiere) * -1);
  display: flex;
  align-items: center;
  gap: 14px;
  /* shadows.raised, orientee vers le haut : la barre flotte AU-DESSUS du contenu qui passe dessous. */
  box-shadow: 0 -8px 24px rgba(43, 26, 12, 0.12);
  transition: transform var(--duree) cubic-bezier(0.16, 1, 0.3, 1),
    opacity var(--duree) ease-out;
}

/* ELLE NE SE MONTRE QU'EN BAS DE PAGE, demande du 7 aout 2026 : sur un telephone ou tout ne tient
   pas d'un coup, une barre posee en permanence mange un huitieme de l'ecran pendant tout le
   remplissage. Elle s'efface donc tant qu'il reste quelque chose a lire plus bas, et remonte quand
   on arrive au bout. C'est aussi le moment ou elle sert : on vient de finir de remplir.

   La sortie descend d'un peu plus que sa propre hauteur, pour que l'ombre portee sorte du cadre avec
   elle plutot que de laisser une trace grise au ras du bord. */
.barre.retiree {
  transform: translateY(calc(100% + 28px));
  opacity: 0;
  pointer-events: none;
}

/* Pose au chargement et retiree au premier passage de l'observateur : sans cela, une page ou tout
   tient ferait glisser sa barre a chaque ouverture, pour rien. */
.barre.sans-transition { transition: none; }

/* Un pixel de haut, en bloc. Un span vide ne mesure rien du tout, et un element sans surface n'est
   jamais rapporte comme visible par un observateur d'intersection : la barre ne serait alors jamais
   remontee. C'est le genre de detail qui ne se voit sur aucune capture d'ecran. */
#sentinelle {
  display: block;
  height: 1px;
}

.barre .decompte {
  flex: 1;
  min-width: 0;
  font-size: 14px;
  color: var(--discret);
}
.barre .decompte strong {
  display: block;
  font-size: 15px;
  font-weight: 700;
  color: var(--titre);
}

button.envoyer {
  flex: none;
  min-width: 190px;
  height: var(--hauteur-champ);
  border: 0;
  border-radius: var(--rayon);
  font: inherit;
  font-weight: 700;
  font-size: 17px;
  color: var(--mie);
  /* gradients.btnPrimary : fonce vers la droite. */
  background: linear-gradient(90deg, #5C3A1C, #2B1A0C);
  cursor: pointer;
  transition: transform var(--duree-courte) ease-out, opacity var(--duree-courte) ease-out;
}
button.envoyer:active { transform: scale(0.97); } /* metrics.pressScale */
button.envoyer[disabled] {
  opacity: 0.55;
  cursor: default;
  transform: none;
}
@media (hover: hover) {
  button.envoyer:not([disabled]):hover { background: linear-gradient(90deg, #432A14, #1D1108); }
}

/* -------------------------------------------------------------------------------------------------
   Les etats
   ---------------------------------------------------------------------------------------------- */
.erreur {
  background: var(--erreur-fond);
  color: var(--erreur-texte);
  border-radius: 14px;
  padding: 14px;
  margin: 0 0 16px;
  font-size: 15px;
}

/* Un squelette plutot qu'un tourniquet : il montre la forme de ce qui arrive, et il ne clignote pas
   au milieu d'une page vide. L'animation est une lueur qui traverse, coupee par prefers-reduced-motion
   tout en bas de ce fichier. */
.squelette { padding: 4px 0; }
.squelette .os {
  height: 14px;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--creux) 0%, #EFE6D3 50%, var(--creux) 100%);
  background-size: 200% 100%;
  animation: lueur 1400ms ease-in-out infinite;
  margin: 0 0 12px;
}
.squelette .os:nth-child(1) { width: 55%; }
.squelette .os:nth-child(2) { width: 80%; }
.squelette .os:nth-child(3) { width: 68%; }
.squelette .os:last-child { margin-bottom: 0; }
@keyframes lueur {
  0% { background-position: 100% 0; }
  100% { background-position: -100% 0; }
}

/* L'unique moment anime de la page : le formulaire, une fois son catalogue recu, monte de quatre
   pixels en s'ouvrant. Une seule fois, sur un seul element, et depuis un etat deja visible. */
.paraitre { animation: paraitre 220ms cubic-bezier(0.16, 1, 0.3, 1) both; }
@keyframes paraitre {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: none; }
}

.reussi { text-align: center; }
.pastille {
  width: 56px;
  height: 56px;
  border-radius: 999px;
  background: var(--ok-fond);
  color: var(--ok-texte);
  font-size: 30px;
  line-height: 56px;
  margin: 0 auto 14px;
  font-weight: 700;
}

/* La mention d'information des articles 13 et 14. Posee sur le FORMULAIRE et non dans l'enveloppe
   commune, qui sert aussi aux pages de refus : celles-la ne recueillent rien, une mention y serait
   du bruit. Discrete mais lisible, jamais en dessous de 12 px. */
.mention {
  color: var(--discret);
  font-size: 12px;
  line-height: 17px;
  margin: 18px 0 0;
}
.mention a { color: var(--accent-texte); }


[hidden] { display: none !important; }

/* Lu par les lecteurs d'ecran, invisible a l'oeil : le squelette d'attente ne dit rien tout seul. */
.visuellement-cache {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* -------------------------------------------------------------------------------------------------
   Les tailles d'ecran. Seule la liste des produits se reorganise, motif en tete de fichier.
   ---------------------------------------------------------------------------------------------- */
@media (min-width: 700px) {
  .enveloppe {
    max-width: 760px;
    padding-top: 32px;
  }
  .carte { padding: 24px; }
  h1 { font-size: 34px; letter-spacing: -0.4px; } /* type.display */
  .produits { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .barre { padding: 14px 20px; }
}

@media (min-width: 1100px) {
  .enveloppe { max-width: 1000px; }
  .produits { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* =================================================================================================
   L'ECRAN D'APPLICATION.

   Demande de Paul-Antoine du 7 aout 2026 : « pas de barre de defilement pour la page de commande en
   general », et une liste de produits qui ne prend pas plus de place que son emplacement d'origine.
   La page devient donc un ecran a hauteur fixe, comme celui d'une application : en-tete, dates,
   liste, message, mention et bouton tiennent tous a l'ecran, et SEULE la liste des produits defile,
   dans son cadre, exactement comme le volet des ingredients dans l'application.

   POURQUOI SOUS CONDITION DE HAUTEUR, ET PAS PARTOUT. Il faut environ 720 px pour loger tout cela
   sans en ecraser un. Les telephones recents en font plus (844 px sur un iPhone 14), un iPhone SE
   en fait 667 : en dessous du seuil, la page redefile normalement et la liste garde son cadre a
   55 vh. Forcer l'ecran fixe partout aurait donne, sur les petits telephones, une liste de deux
   rangees hautes comme un timbre, ou une mention d'information tronquee. Aucun des deux n'est
   acceptable, et le second ne l'est pas du tout : c'est la mention des articles 13 et 14.
   ============================================================================================== */
@media (min-height: 760px) {
  /* « auto » ET NON « hidden » SUR LE CORPS DU DOCUMENT, et c'est la decision la plus importante de
     ce bloc. Quand tout tient, auto ne montre AUCUNE barre : le resultat demande est atteint. Quand
     tout ne tient pas, parce qu'un visiteur a grossi la police de son navigateur ou que sa fenetre
     est plus courte que prevu, la page redefile au lieu de COUPER ce qui depasse. Avec « hidden », ce
     qui depasse est le bas de l'ecran, c'est-a-dire le bouton d'envoi : la page serait alors jolie et
     inutilisable, sans que rien ne le signale.

     Cela ne vaut QUE pour le corps du document. Le cadre des produits, lui, est en « hidden » depuis
     le 10 aout 2026 dans les deux modes, et ce qui y depasse s'atteint par la barre. */
  body.commande {
    height: 100vh;
    height: 100dvh;
    overflow-y: auto;
  }
  /* « min-height » et non « height » : l'enveloppe remplit l'ecran quand tout tient, et s'allonge
     au lieu de laisser deborder ses enfants quand ce n'est pas le cas. C'est ce qui rend le repli
     ci-dessus reellement sur, plutot que seulement declare. */
  body.commande .enveloppe {
    min-height: 100%;
    display: flex;
    flex-direction: column;
    padding-bottom: 0;
  }
  body.commande #contenu {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
  }
  body.commande #formulaire {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
  }

  /* La seule carte qui prend la place restante, et la seule qui defile. Son plancher vaut environ
     deux rangees : en dessous, elle ne montrerait plus rien d'utile et il vaudrait mieux rendre la
     page au defilement, ce que fait deja le seuil de hauteur ci-dessus. */
  body.commande .carte-produits {
    flex: 1;
    min-height: 148px;
    display: flex;
    flex-direction: column;
    overflow: hidden;
  }
  /* C'est la ZONE qui prend la place restante, et le cadre s'etire dedans par la ligne flex. Poser
     « flex: 1 » sur le cadre lui-meme laisserait la piste a la hauteur de son contenu, c'est-a-dire
     a rien. */
  body.commande .carte-produits .produits-zone,
  body.commande .carte.longue .produits-zone {
    flex: 1;
    min-height: 0;
  }
  body.commande .carte-produits .produits-cadre,
  body.commande .carte.longue .produits-cadre {
    min-height: 0;
    max-height: none;
    overflow: hidden;
  }

  /* CE QUI CEDE DE LA HAUTEUR, ET DANS QUEL ORDRE. Tout ne tient pas a 760 px sans resserrer, et il
     fallait choisir quoi comprimer. Par ordre de ce qui coute le moins : le message, seul bloc
     facultatif de l'ecran ; les marges interieures des cartes, qui ne portent aucune information ;
     et le titre, qui reste largement au-dessus de la taille du corps de texte. La mention
     d'information des articles 13 et 14 n'a rien cede : elle est la seule chose de cet ecran que la
     loi impose, et la rogner pour gagner trente pixels serait le mauvais echange. */
  body.commande .carte { padding: 16px; }
  body.commande h1 { font-size: 24px; }
  body.commande .mention { margin-top: 14px; }

  /* La barre n'a plus besoin d'etre collante, elle EST en bas. Elle garde son ombre : elle separe
     l'action de la mention d'information, et c'est bien elle qui flotte au-dessus du reste. */
  body.commande .barre {
    position: static;
    margin-top: auto;
  }

}

/* Une commande se passe aussi en tenant son telephone a l'horizontale, posé sur un plan de travail :
   la barre collante mangerait alors la moitie de la hauteur utile. Elle redevient un simple bloc en
   fin de page des que la fenetre est courte. Ce seuil est bien en dessous de celui de l'ecran fixe,
   les deux regles ne se rencontrent donc jamais. */
@media (max-height: 460px) {
  .barre { position: static; box-shadow: none; padding-left: 0; padding-right: 0; margin: 0; }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}
