L'optimiseur ne mutualisait que le pré-découpage. Il regroupe maintenant aussi les actions SETUP (préchauffage, ébullition) entre recettes, mais seulement quand elles sont compatibles : deux préchauffages ne fusionnent que s'ils visent la même température détectée. Deux préchauffages à des températures différentes restent séparés, ordonnés du plus froid au plus chaud, et la mise en place porte une note « Four requis à N températures — enchaîner … ». Défaut optimiste : deux préchauffages sans température détectée fusionnent quand même (supposés compatibles). - cooking-optimizer.ts : temperatureKey (comparaison exacte, aucune conversion inter-représentations), isPureSetupStep / setupSignature / extractMergedSetup en miroir du chemin merged-prep ; comparableTemp pour l'ordre seul ; detectApplianceTemperatureConflicts → CookingPhaseView.notes - cooking-session.service.ts : `temperatures` dans l'include + mapping - packages/shared : CookingTemperatureView, kind « merged-setup », CookingTaskView.temperatures, CookingPhaseNoteView, CookingPhaseView.notes - web : temperature-label.ts extrait de StepDescription et réutilisé ; taskHeadline gère merged-setup ; phaseNoteText ; bandeau .cooking-phase__note - i18n : cookingSession.task.mergedSetupLabel, cookingSession.note.* - tests : 7 cas Mocha « setup pooling » + test DB cooking-session, cas Cypress mode layout + scénario Gherkin Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
421 lines
11 KiB
SCSS
421 lines
11 KiB
SCSS
// =============================================================================
|
|
// Styles specific to CookingSessionPage — colocated next to
|
|
// CookingSessionPage.tsx since nothing else uses these classes.
|
|
//
|
|
// Layout: a fixed page shell (header + subtitle + tab bar), then one of two
|
|
// tab panels. The "Recettes" panel is a plain scrolling list; the "Plan"
|
|
// panel (`.cooking-plan`) is itself a flex column — a pinned stepper and
|
|
// meta line on top, a pinned prev/next bar at the bottom, and the current
|
|
// phase scrolling in between so the nav is always reachable.
|
|
//
|
|
// Every value goes through a `var(--token)` from _theme.scss; light/dark is
|
|
// handled entirely by those tokens being redefined, nothing here branches
|
|
// on the theme.
|
|
// =============================================================================
|
|
|
|
.cooking-session-page {
|
|
height: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
|
|
&__header {
|
|
flex-shrink: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
flex-wrap: wrap;
|
|
gap: var(--space-md);
|
|
margin-bottom: var(--space-sm);
|
|
}
|
|
|
|
&__subtitle {
|
|
flex-shrink: 0;
|
|
margin: 0 0 var(--space-md);
|
|
color: var(--color-text-muted);
|
|
font-size: var(--font-size-md);
|
|
}
|
|
|
|
&__status {
|
|
color: var(--color-text-muted);
|
|
font-size: var(--font-size-md);
|
|
}
|
|
|
|
&__status--error {
|
|
color: var(--color-error);
|
|
}
|
|
|
|
// --- Tab bar ---------------------------------------------------------------
|
|
// Same underline-tab language as the recipe catalog's `.recipe-tabs`
|
|
// (features/recipes/recipes.scss): scrolls within itself rather than
|
|
// widening the page (global.scss forbids a horizontal body scroll).
|
|
&__tabs {
|
|
flex-shrink: 0;
|
|
display: flex;
|
|
gap: var(--space-xs);
|
|
overflow-x: auto;
|
|
border-bottom: 1px solid var(--color-border);
|
|
margin-bottom: var(--space-md);
|
|
}
|
|
|
|
&__tab {
|
|
appearance: none;
|
|
flex-shrink: 0;
|
|
padding: var(--space-sm) var(--space-md);
|
|
font-family: var(--font-display);
|
|
font-size: var(--font-size-sm);
|
|
font-weight: 700;
|
|
letter-spacing: 0.02em;
|
|
color: var(--color-text-muted);
|
|
background: none;
|
|
border: none;
|
|
border-bottom: 2.5px solid transparent;
|
|
cursor: pointer;
|
|
transition:
|
|
color 0.15s ease,
|
|
border-color 0.15s ease;
|
|
|
|
&:hover {
|
|
color: var(--color-text);
|
|
}
|
|
|
|
&.active {
|
|
color: var(--color-primary);
|
|
border-color: var(--color-primary);
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- "Recettes" tab ---------------------------------------------------------
|
|
// The week's planned recipes as a flat list — name, its own portion count,
|
|
// and a jump to the recipe's page. Deliberately thin: `plan.recipes` only
|
|
// carries `{recipeId, name, portions}`.
|
|
.cooking-recipes {
|
|
flex: 1;
|
|
min-height: 0;
|
|
overflow-y: auto;
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--space-xs);
|
|
|
|
&__item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-sm);
|
|
padding: var(--space-sm) var(--space-md);
|
|
background: var(--color-surface);
|
|
border-radius: var(--radius-md);
|
|
box-shadow: var(--shadow-sm);
|
|
}
|
|
|
|
&__name {
|
|
font-weight: 600;
|
|
color: var(--color-text);
|
|
}
|
|
|
|
&__portions {
|
|
padding: 0.1rem var(--space-sm);
|
|
border-radius: var(--radius-pill);
|
|
background: var(--color-surface-alt);
|
|
font-size: var(--font-size-sm);
|
|
color: var(--color-text-muted);
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
// Pushed to the trailing edge — a quiet secondary action, not the point
|
|
// of the row.
|
|
&__link {
|
|
margin-left: auto;
|
|
flex-shrink: 0;
|
|
font-size: var(--font-size-sm);
|
|
font-weight: 600;
|
|
color: var(--color-primary);
|
|
}
|
|
}
|
|
|
|
// --- "Plan" tab -----------------------------------------------------------
|
|
.cooking-plan {
|
|
flex: 1;
|
|
min-height: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--space-md);
|
|
|
|
// Meta line under the stepper: "Étape 2 / 4 · Cuisson" on the left, the
|
|
// "{done} / {total} tâches" counter on the right.
|
|
&__meta {
|
|
flex-shrink: 0;
|
|
display: flex;
|
|
align-items: baseline;
|
|
justify-content: space-between;
|
|
flex-wrap: wrap;
|
|
gap: var(--space-sm);
|
|
}
|
|
|
|
&__position {
|
|
font-family: var(--font-display);
|
|
font-weight: 700;
|
|
font-size: var(--font-size-md);
|
|
color: var(--color-text);
|
|
}
|
|
|
|
&__count {
|
|
font-size: var(--font-size-sm);
|
|
color: var(--color-text-muted);
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
// The current phase scrolls; everything around it stays put.
|
|
.cooking-phase {
|
|
flex: 1;
|
|
min-height: 0;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
&__nav {
|
|
flex-shrink: 0;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
gap: var(--space-sm);
|
|
}
|
|
|
|
&__nav-btn {
|
|
appearance: none;
|
|
padding: var(--space-sm) var(--space-lg);
|
|
border: 1.5px solid var(--color-primary);
|
|
border-radius: var(--radius-base);
|
|
background: var(--color-surface);
|
|
color: var(--color-primary);
|
|
font-family: var(--font-display);
|
|
font-weight: 700;
|
|
font-size: var(--font-size-sm);
|
|
cursor: pointer;
|
|
transition:
|
|
background-color 0.15s ease,
|
|
opacity 0.15s ease;
|
|
|
|
&:hover:not(:disabled) {
|
|
background: color-mix(in srgb, var(--color-primary) 10%, var(--color-surface));
|
|
}
|
|
|
|
&:disabled {
|
|
opacity: 0.4;
|
|
cursor: not-allowed;
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- Stepper --------------------------------------------------------------
|
|
// One clickable node per phase, in a row that scrolls sideways within
|
|
// itself when the phases don't all fit. A thin connector sits between
|
|
// consecutive nodes.
|
|
.cooking-stepper {
|
|
flex-shrink: 0;
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0 0 var(--space-xs);
|
|
display: flex;
|
|
gap: var(--space-xs);
|
|
overflow-x: auto;
|
|
|
|
&__step {
|
|
flex: 0 0 auto;
|
|
display: flex;
|
|
align-items: center;
|
|
|
|
&:not(:last-child)::after {
|
|
content: "";
|
|
width: var(--space-md);
|
|
height: 2px;
|
|
background: var(--color-border);
|
|
margin: 0 var(--space-xs);
|
|
}
|
|
}
|
|
|
|
&__node {
|
|
appearance: none;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-xs);
|
|
padding: var(--space-xs) var(--space-sm);
|
|
border: 1.5px solid var(--color-border);
|
|
border-radius: var(--radius-pill);
|
|
background: var(--color-surface);
|
|
color: var(--color-text-muted);
|
|
font-family: var(--font-display);
|
|
font-size: var(--font-size-sm);
|
|
font-weight: 700;
|
|
white-space: nowrap;
|
|
cursor: pointer;
|
|
transition:
|
|
color 0.15s ease,
|
|
border-color 0.15s ease,
|
|
background-color 0.15s ease;
|
|
|
|
&:hover {
|
|
color: var(--color-text);
|
|
border-color: var(--color-primary);
|
|
}
|
|
|
|
// The phase currently on screen.
|
|
&--current {
|
|
color: var(--color-primary);
|
|
border-color: var(--color-primary);
|
|
background: color-mix(in srgb, var(--color-primary) 10%, var(--color-surface));
|
|
}
|
|
|
|
// Every task in the phase is ticked.
|
|
&--done {
|
|
color: var(--color-success);
|
|
border-color: var(--color-success);
|
|
}
|
|
}
|
|
|
|
// The leading circle — a step number, or a check once the phase is done.
|
|
&__marker {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 1.4rem;
|
|
height: 1.4rem;
|
|
border-radius: var(--radius-pill);
|
|
background: var(--color-surface-alt);
|
|
font-size: var(--font-size-xs);
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
&__node--current &__marker {
|
|
background: var(--color-primary);
|
|
color: var(--color-surface);
|
|
}
|
|
|
|
&__node--done &__marker {
|
|
background: var(--color-success);
|
|
color: var(--color-surface);
|
|
}
|
|
}
|
|
|
|
// --- One phase ----------------------------------------------------------------
|
|
.cooking-phase {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--space-sm);
|
|
|
|
// A phase-level caveat — today only the "four requis à 2 températures"
|
|
// conflict. A tinted callout in the accent colour so it reads as a
|
|
// heads-up, not a plain paragraph.
|
|
&__note {
|
|
margin: 0;
|
|
padding: var(--space-sm) var(--space-md);
|
|
border-left: 3px solid var(--color-accent);
|
|
border-radius: 0 var(--radius-md) var(--radius-md) 0;
|
|
background: color-mix(in srgb, var(--color-accent) 10%, var(--color-surface));
|
|
color: var(--color-text);
|
|
font-size: var(--font-size-sm);
|
|
font-weight: 600;
|
|
}
|
|
|
|
// "Pendant ce temps" — passive cooks still running from earlier phases.
|
|
&__background {
|
|
border-left: 3px solid var(--color-border);
|
|
padding: var(--space-xs) var(--space-md);
|
|
color: var(--color-text-muted);
|
|
font-size: var(--font-size-sm);
|
|
|
|
&-title {
|
|
display: block;
|
|
font-weight: 600;
|
|
margin-bottom: 0.15rem;
|
|
}
|
|
|
|
ul {
|
|
margin: 0;
|
|
padding-left: var(--space-md);
|
|
}
|
|
}
|
|
|
|
&__tasks {
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--space-sm);
|
|
|
|
> li {
|
|
display: flex;
|
|
}
|
|
|
|
// The task card *is* a shared "selectable card" checkbox label (see
|
|
// global.scss's `label:has(> input[type="checkbox"])`), so its frame,
|
|
// hover, focus ring and hidden native input all come from there. The
|
|
// rules below only set what's task-specific — a top-aligned multi-line
|
|
// body, the merged-prep accent, and a faded/struck "done" look — and
|
|
// are kept at `.cooking-phase__tasks` depth on purpose so they outrank
|
|
// that shared rule's own `&.is-selected` treatment.
|
|
.cooking-task {
|
|
flex: 1;
|
|
align-items: flex-start;
|
|
}
|
|
|
|
.cooking-task--merged-prep,
|
|
.cooking-task--merged-setup {
|
|
border-left: 3px solid var(--color-accent);
|
|
}
|
|
|
|
// Ticked = done: cancel the shared rule's "selected" green tint/border
|
|
// (which would clash with the stepper's own green "current" node) and
|
|
// just fade the card and strike its headline.
|
|
.cooking-task--done {
|
|
opacity: 0.7;
|
|
background: var(--color-surface);
|
|
border-color: var(--color-border);
|
|
|
|
.cooking-task__headline {
|
|
text-decoration: line-through;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- One task card --------------------------------------------------------
|
|
// (frame comes from the shared checkbox-label rule — see above)
|
|
.cooking-task {
|
|
&__body {
|
|
flex: 1;
|
|
min-width: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.2rem;
|
|
}
|
|
|
|
&__headline {
|
|
display: flex;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
gap: var(--space-xs);
|
|
font-weight: 600;
|
|
color: var(--color-text);
|
|
}
|
|
|
|
// A pooled prep task is the headline feature of this page — flag it.
|
|
&__badge {
|
|
padding: 0.05rem var(--space-xs);
|
|
border-radius: var(--radius-pill);
|
|
background: var(--color-accent);
|
|
color: var(--color-surface);
|
|
font-size: var(--font-size-xs);
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.03em;
|
|
}
|
|
|
|
&__recipes,
|
|
&__utensils {
|
|
display: block;
|
|
font-size: var(--font-size-sm);
|
|
color: var(--color-text-muted);
|
|
}
|
|
}
|