@font-face {
  font-family: "Kalam";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/fonts/kalam-400.woff2") format("woff2");
}

@font-face {
  font-family: "Kalam";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/fonts/kalam-700.woff2") format("woff2");
}

@font-face {
  font-family: "Caveat";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/fonts/caveat-variable.woff2") format("woff2");
}

@font-face {
  font-family: "Caveat";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/fonts/caveat-variable.woff2") format("woff2");
}

@font-face {
  font-family: "Source Sans 3";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/fonts/source-sans-3-variable.woff2") format("woff2");
}

:root {
  color-scheme: light dark;
  --gap: 12px;
  /* UI font: navigation, buttons, config section, timeline, step descriptions
     - anything that isn't explicitly scoped to --content-font below. */
  --ui-font: "Kalam";
  --ui-font-size-adjust: none;
  --ui-font-weight: 400;
  --ui-font-weight-medium: 600;
  --ui-font-weight-bold: 700;
  /* Content font: ingredient list, recipe card, description/user notes. */
  --content-font: "Kalam";
  --content-font-size-adjust: none;
  --content-font-weight: 400;
  --content-font-weight-medium: 600;
  --content-font-weight-bold: 700;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: var(--ui-font), "Segoe UI", system-ui, -apple-system, sans-serif;
  font-size-adjust: var(--ui-font-size-adjust);
  font-weight: var(--ui-font-weight);
  /* Fixed (not "normal") so per-font metrics - and font-size-adjust's used-size
     bump for Caveat - can't change line spacing when the font is swapped. */
  line-height: 25px;
  background-color: #fafafa;
  background-image: url("/img/background-tile.svg");
  background-repeat: repeat;
  color: #212121;
}

h1,
h2 {
  font-weight: var(--ui-font-weight-bold);
}

.app-header {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--gap);
  padding: 16px;
  position: sticky;
  top: 0;
  background: #fff;
  border-bottom: 1px solid #eee;
  z-index: 100;
}

.app-header h1 {
  font-size: 1.25rem;
  margin: 0;
}

.brand-title {
  font-family: var(--content-font);
  font-size-adjust: var(--content-font-size-adjust);
  font-weight: var(--content-font-weight-bold);
}

.back-link {
  font-size: 1.5rem;
  line-height: 1;
  text-decoration: none;
  color: inherit;
}

.header-kitchen {
  margin-left: auto;
  display: flex;
  align-items: center;
}

.header-kitchen .choice {
  padding: 0;
  border-bottom: none;
}

.header-font {
  margin-left: auto;
  display: flex;
  align-items: center;
}

.header-font .choice {
  padding: 0;
  border-bottom: none;
}

.icon-button {
  width: 2.2em;
  height: 2.2em;
  border-radius: 50%;
  border: 1px solid #ccc;
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: 1.3rem;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}

.import-dialog {
  width: min(600px, 90vw);
  max-height: 85vh;
  border: none;
  border-radius: 12px;
  padding: var(--gap);
  color: inherit;
  background: #fff;
}

.import-dialog::backdrop {
  background: rgba(0, 0, 0, 0.4);
}

.import-label {
  display: block;
  font-size: 0.95rem;
  margin-bottom: 6px;
}

#import-url {
  width: 100%;
  padding: 10px;
  border: 1px solid #ccc;
  border-radius: 8px;
  font: inherit;
}

.import-mode-toggle {
  display: flex;
  gap: 8px;
  margin-bottom: var(--gap);
}

.import-mode-button {
  flex: 1;
  padding: 8px 10px;
  border-radius: 8px;
  border: 1px solid #ccc;
  background: transparent;
  color: inherit;
  font: inherit;
  font-weight: var(--ui-font-weight-medium);
  cursor: pointer;
}

.import-mode-button-active {
  background: #6d4c41;
  border-color: #6d4c41;
  color: #fff;
}

.import-paste-section {
  margin-top: var(--gap);
}

.import-textarea {
  width: 100%;
  padding: 10px;
  border: 1px solid #ccc;
  border-radius: 8px;
  font: inherit;
  resize: vertical;
}

.import-actions {
  display: flex;
  gap: var(--gap);
  margin-top: 10px;
}

.import-actions .cook-button {
  flex: initial;
  padding: 10px 16px;
}

.import-result {
  margin: var(--gap) 0 0;
  padding: 10px;
  background: #f5f5f5;
  border-radius: 8px;
  font-size: 0.82rem;
  white-space: pre-wrap;
  word-break: break-word;
  overflow-y: auto;
  /* Scrolls rather than growing: a long failure must not push the recovery
     callout right under it off the bottom of the dialog. */
  max-height: 30vh;
}

.import-result:empty {
  display: none;
}

.import-result-error {
  color: #c62828;
}

.import-paste-hint {
  margin: 6px 0 0;
  color: #666;
  font-size: 0.78rem;
}

.login-main {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--gap);
  padding: 24px;
  text-align: center;
}

.login-logo {
  font-size: 2.5rem;
  margin: 0 0 8px;
}

.login-form {
  width: min(360px, 90vw);
  text-align: left;
}

.login-form #login-email {
  width: 100%;
  padding: 10px;
  border: 1px solid #ccc;
  border-radius: 8px;
  font: inherit;
  margin-bottom: var(--gap);
}

.login-form .cook-button {
  width: 100%;
}

.login-main .import-result {
  width: min(360px, 90vw);
}

/* A callout, not more form fields: it appears directly under the red error
   text, which otherwise takes the whole glance - so it has to read as the
   answer to that error rather than as part of the form above it. */
.import-view-source {
  margin-top: 10px;
  padding: 10px 12px;
  border: 1px solid #6d4c41;
  border-radius: 8px;
  background: #f6efe9;
}

.import-view-source-lead {
  margin: 0;
  font-size: 0.95rem;
  font-weight: var(--ui-font-weight-medium);
}

.import-view-source-steps {
  margin: 8px 0 0;
  font-size: 0.85rem;
}

.import-view-source-row {
  display: flex;
  gap: 8px;
  margin-top: 8px;
}

/* Monospace, dashed and see-through so it reads as a value to take away
   rather than as another box to fill in like the URL field above. Readonly
   because typing into it can only break the string. */
.import-view-source-input {
  flex: 1;
  min-width: 0;
  padding: 8px;
  border: 1px dashed #a1887f;
  border-radius: 6px;
  background: transparent;
  color: inherit;
  font-family: ui-monospace, monospace;
  font-size: 0.82rem;
}

.import-view-source-row .cook-button {
  flex: initial;
  padding: 8px 16px;
}

.import-from-scratch {
  display: block;
  width: 100%;
  margin-top: var(--gap);
  padding: 0;
  border: none;
  background: none;
  color: #4a6fa5;
  font: inherit;
  text-decoration: underline;
  cursor: pointer;
}

main {
  padding: var(--gap);
}

/* The column minimum tracks the viewport so a phone gets three across
   (24vw ~= a third of the screen minus gaps) while a wide screen still tops
   out at the 150px card the desktop layout was tuned for. Floor of 90px is
   what keeps the narrowest phones at three rather than one. */
.recipe-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(clamp(90px, 24vw, 150px), 1fr));
  gap: calc(var(--gap) * 2.2) calc(var(--gap) * 1.6);
  padding: var(--gap) calc(var(--gap) * 1.5);
}

.recipe-card {
  display: block;
  /* So the title can size off the card instead of the viewport - see
     .recipe-card .recipe-title. */
  container-type: inline-size;
  text-decoration: none;
  color: inherit;
  background: #fffdf8;
  padding: 10px 10px 0;
  box-shadow: 0 3px 6px rgba(0, 0, 0, 0.25), 0 1px 2px rgba(0, 0, 0, 0.2);
  transform: rotate(-2deg);
  transition: transform 0.15s ease, box-shadow 0.15s ease;
  font-family: var(--content-font);
  font-size-adjust: var(--content-font-size-adjust);
  font-weight: var(--content-font-weight);
}

.recipe-card:nth-child(4n+2) {
  transform: rotate(1.5deg);
}

.recipe-card:nth-child(4n+3) {
  transform: rotate(-1deg);
}

.recipe-card:nth-child(4n+4) {
  transform: rotate(2deg);
}

.recipe-card:hover,
.recipe-card:focus-visible {
  transform: rotate(0deg) scale(1.05);
  box-shadow: 0 8px 14px rgba(0, 0, 0, 0.3);
  position: relative;
  z-index: 2;
}

.recipe-card img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  background: #eee;
}

/* Sized off the card, so a three-across phone card gets a title that fits it
   rather than one wider than its own photo; em padding keeps the polaroid
   border in proportion at every size. The line clamp is what actually holds a
   long title under the photo's height - at three narrow columns even the
   smallest legible type wraps a full recipe name past it otherwise. */
.recipe-card .recipe-title {
  padding: 0.52em 0.2em 1.04em;
  font-size: clamp(0.72rem, 11cqw, 1.2rem);
  line-height: 1.25;
  font-weight: var(--content-font-weight-bold);
  text-align: center;
  color: #2b2b2b;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow: hidden;
}

/* On a phone the grid's own side padding is what costs it the third column,
   so main's padding is the whole margin here and the gaps shrink with it. */
@media (max-width: 480px) {
  .recipe-grid {
    gap: calc(var(--gap) * 1.6) calc(var(--gap) * 0.75);
    padding: var(--gap) 0;
  }
}

.recipe-photo {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: 12px;
  background: #eee;
}

.recipe-description {
  margin: 12px 0 0;
  color: #555;
  font-family: var(--content-font);
  font-size-adjust: var(--content-font-size-adjust);
  font-weight: var(--content-font-weight);
}

.recipe-description.is-clamped {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 5;
  line-clamp: 5;
  overflow: hidden;
}

.recipe-description-toggle {
  display: block;
  margin-top: 2px;
  margin-left: auto;
  padding: 0;
  border: none;
  background: none;
  color: #6d4c41;
  font: inherit;
  font-size: 0.85rem;
  text-decoration: underline;
  cursor: pointer;
}

/* display: block above outranks the UA stylesheet's [hidden] rule, so the
   hidden state has to be restated. */
.recipe-description-toggle[hidden] {
  display: none;
}

.recipe-source {
  display: block;
  margin-top: 4px;
  font-size: 0.85rem;
}

.allergy-diet-info {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-top: 10px;
}

.allergy-info-label {
  font-size: 0.8rem;
  font-weight: 600;
  color: #8a6d3b;
}

.allergy-chip {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 0.72rem;
  white-space: nowrap;
  background: #fff3cd;
  color: #8a6d3b;
}

.diet-chip {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 0.72rem;
  white-space: nowrap;
  background: #e2f3e5;
  color: #3c7a4b;
}

.allergy-diet-chip {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 0.72rem;
  white-space: nowrap;
  /* 135deg is a fixed visual angle regardless of the pill's own aspect ratio,
     unlike a percentage-based clip-path - always top-left to bottom-right. */
  background: linear-gradient(135deg, #fff3cd 50%, #e2f3e5 50%);
  color: #5c5330;
}

.recipe-info-card {
  margin: 16px 4px 0;
  padding: 16px 18px 4px;
  border-radius: 10px;
  color: #2a2a2a;
  background-color: #fffdf8;
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3), 0 1px 3px rgba(0, 0, 0, 0.2);
}

.config-section {
  margin-top: 12px;
}

.cook-controls {
  display: flex;
  gap: var(--gap);
  padding: 10px 0;
}

.cook-button {
  flex: 1;
  padding: 10px 14px;
  border-radius: 8px;
  border: 1px solid transparent;
  font: inherit;
  font-weight: var(--ui-font-weight-medium);
  cursor: pointer;
}

.cook-button-start,
.cook-button-resume,
.cook-button-pause {
  background: #6d4c41;
  color: #fff;
}

.cook-button-reset {
  background: transparent;
  border-color: #ccc;
  color: inherit;
}

.total-duration {
  padding: 10px 0;
  font-size: 0.95rem;
  font-weight: var(--ui-font-weight-medium);
}

.choice {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--gap);
  padding: 10px 0;
  border-bottom: 1px solid #eee;
}

.choice-label {
  font-size: 0.95rem;
}

.choice-select {
  margin-left: var(--gap);
  font: inherit;
}

.yield-controls {
  display: flex;
  align-items: center;
}

.yield-stepper {
  display: inline-flex;
  margin-left: var(--gap);
}

.yield-step {
  width: 2.2em;
  height: 2.2em;
  padding: 0;
  border: 1px solid #ccc;
  background: transparent;
  color: inherit;
  font: inherit;
  font-weight: var(--ui-font-weight-medium);
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}

.yield-step-minus {
  border-right: none;
  border-radius: 6px 0 0 6px;
}

.yield-step-plus {
  border-left: none;
  border-radius: 0 6px 6px 0;
}

.yield-input {
  width: 3em;
  height: 2.2em;
  padding: 0;
  border: 1px solid #ccc;
  border-radius: 0;
  text-align: center;
  font: inherit;
  /* The stepper buttons replace the native up/down arrows entirely. */
  -moz-appearance: textfield;
  appearance: textfield;
}

.yield-input::-webkit-inner-spin-button,
.yield-input::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

.yield-unit {
  margin-left: 6px;
}

.ingredient-note {
  margin: 24px 4px 16px;
  padding: 30px 18px 22px 60px;
  font-family: var(--content-font);
  font-size-adjust: var(--content-font-size-adjust);
  font-weight: var(--content-font-weight);
  background-color: #fffef6;
  background-image:
    linear-gradient(to right, transparent 44px, #d98a8a 44px, #d98a8a 46px, transparent 46px),
    repeating-linear-gradient(to bottom, transparent 0, transparent 27px, #bcd4e8 27px, #bcd4e8 28px);
  background-repeat: no-repeat, repeat;
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3), 0 1px 3px rgba(0, 0, 0, 0.2);
  clip-path: polygon(
    0% 8px, 4% 1px, 8% 9px, 12% 2px, 16% 10px, 20% 1px,
    24% 8px, 28% 2px, 32% 9px, 36% 1px, 40% 10px, 44% 2px,
    48% 8px, 52% 1px, 56% 9px, 60% 2px, 64% 10px, 68% 1px,
    72% 8px, 76% 2px, 80% 9px, 84% 1px, 88% 10px, 92% 2px,
    96% 8px, 100% 1px,
    100% 100%, 0% 100%
  );
  -webkit-mask-image: radial-gradient(circle 7px at 24px 34px, transparent 99%, black 100%);
  -webkit-mask-size: 100% 90px;
  -webkit-mask-repeat: repeat-y;
  -webkit-mask-position: top left;
  mask-image: radial-gradient(circle 7px at 24px 34px, transparent 99%, black 100%);
  mask-size: 100% 90px;
  mask-repeat: repeat-y;
  mask-position: top left;
}

.ingredient-note-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--gap);
  margin: 0 0 14px;
}

.ingredient-note-title {
  display: inline-block;
  margin: 0;
  font-size: 1.4rem;
  font-weight: var(--content-font-weight-bold);
  color: #24344d;
  border-bottom: 2px dashed rgba(36, 52, 77, 0.4);
  padding-bottom: 4px;
}

.ingredient-list-copy {
  flex-shrink: 0;
  width: 1.8em;
  height: 1.8em;
  font-size: 1rem;
  transition: transform 0.1s ease, background-color 0.1s ease;
}

.ingredient-list-copy:active {
  background-color: rgba(36, 52, 77, 0.15);
  transform: scale(0.88);
}

.toast {
  position: fixed;
  left: 50%;
  bottom: 24px;
  transform: translateX(-50%) translateY(20px);
  max-width: min(90vw, 320px);
  padding: 10px 18px;
  border-radius: 8px;
  background: #24344d;
  color: #fff;
  font-family: var(--content-font);
  font-size: 0.95rem;
  text-align: center;
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.25);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease, transform 0.2s ease;
  z-index: 1000;
}

.toast-visible {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

.ingredient-list {
  list-style: none;
  margin: 0;
  padding: 0;
  color: #2a2a2a;
}

.ingredient-list li {
  display: flex;
  align-items: center;
  gap: var(--gap);
  padding: 4px 0;
}

.ingredient-list img {
  width: 40px;
  height: 40px;
  border-radius: 8px;
  flex-shrink: 0;
}

.ingredient-amount {
  margin-left: auto;
  color: #5c7386;
  font-size: 0.9rem;
  white-space: nowrap;
}

/* Only present when the ingredient has a known density (see
   renderIngredientList/app.units.alternate_form) - a <button> instead of a
   <span> so tapping swaps between volume and weight for that one line. The
   dotted underline is the only hint it's interactive, since it still needs
   to read as a plain amount at a glance. */
.ingredient-amount-toggleable {
  border: none;
  background: none;
  padding: 0;
  margin-left: auto;
  font: inherit;
  color: inherit;
  cursor: pointer;
  text-decoration: underline dotted;
  text-underline-offset: 3px;
}

.ingredient-name-wrap {
  display: flex;
  flex-direction: column;
  min-width: 0;
  flex: 1;
}

/* Wraps a plain ingredient name + its note in one real <button> (see
   renderIngredientList) - reset to read as plain stacked text, not an
   obvious control, but tap-friendly and keyboard-reachable across both
   lines rather than just the note's own. */
.ingredient-name-wrap-button {
  display: flex;
  flex-direction: column;
  width: 100%;
  min-width: 0;
  border: none;
  background: none;
  padding: 0;
  margin: 0;
  font: inherit;
  text-align: left;
  color: inherit;
  cursor: pointer;
}

/* Best-effort aside scraped/authored alongside an ingredient (e.g. "or to
   taste") - clipped to one line with an ellipsis rather than wrapping,
   since the full text is a tap away in #ingredient-note-dialog. A plain
   <span> when it's already inside .ingredient-name-wrap-button (the tap
   target is the whole button there); still its own <button> for a choice
   ingredient, whose name is already spoken for by the alternatives select. */
.ingredient-line-note {
  display: block;
  width: 100%;
  border: none;
  background: none;
  padding: 0;
  margin: 0;
  font: inherit;
  text-align: left;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: #888;
  font-size: 0.8rem;
  cursor: pointer;
}

.ingredient-note-dialog {
  width: min(320px, 90vw);
  border: none;
  border-radius: 12px;
  padding: var(--gap);
  color: inherit;
  background: #fff;
}

.ingredient-note-dialog::backdrop {
  background: rgba(0, 0, 0, 0.4);
}

.ingredient-note-dialog h3 {
  margin: 0 0 8px;
}

.ingredient-note-dialog p {
  margin: 0;
  white-space: pre-wrap;
}

/* Sits where a plain ingredient name would, for a line with an
   author-sanctioned alternative to pick between. A native <select>'s own
   rendering of its closed-state value clips the last character against
   its box edge for some fonts (confirmed with Caveat - a font-specific
   quirk in how the browser paints/measures that internal text, not
   something any amount of author CSS padding or width fixes) - ordinary
   inline text never has this problem, since browsers never clip overflow
   for a plain span. So the select itself is made fully invisible
   (opacity: 0 - still completely focusable/clickable/keyboard-operable;
   its native options popup is a separate overlay, unaffected and fully
   visible when opened) and .ingredient-choice-label, a plain span stacked
   exactly on top of it (same CSS grid cell), is the only thing actually
   seen. Its dashed underline (echoing .ingredient-note-title) and the
   purple chevron badge - deliberately eye-catching, borrowing the same
   circular-icon-button language as .icon-button/.editor-remove-button
   elsewhere - live on the label, not the select, for the same reason. */
.ingredient-choice-wrap {
  display: inline-grid;
  align-items: center;
}

.ingredient-choice-select,
.ingredient-choice-label {
  grid-area: 1 / 1;
}

.ingredient-choice-select {
  width: 100%;
  height: 100%;
  font: inherit;
  opacity: 0;
  border: none;
  padding: 0;
  cursor: pointer;
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
}

.ingredient-choice-label {
  pointer-events: none;
  white-space: pre;
  font: inherit;
  color: inherit;
  border-bottom: 1px dashed currentColor;
  padding: 0 26px 0 2px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Ccircle cx='10' cy='10' r='9' fill='%236a1b9a'/%3E%3Cpath d='M6.5 8.5l3.5 3.5 3.5-3.5' stroke='white' stroke-width='2' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right center;
  background-size: 19px;
}

.ingredient-include {
  flex-shrink: 0;
  width: 18px;
  height: 18px;
}

.timeline-wrapper {
  position: relative;
  margin-top: 12px;
  padding: 20px 16px;
  border-radius: 14px;
  background-color: #fbf8f2;
  background-image:
    linear-gradient(90deg, rgba(74, 111, 165, 0.16) 50%, transparent 50%),
    linear-gradient(rgba(74, 111, 165, 0.16) 50%, transparent 50%);
  background-size: 24px 24px;
  background-blend-mode: multiply;
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3), 0 1px 3px rgba(0, 0, 0, 0.2);
  /* Keep native single-finger scroll, but hand the pinch gesture to our
     own zoom handler instead of letting the browser page-zoom on it. */
  touch-action: pan-y;
}

.timeline-wrapper::before {
  content: "";
  position: absolute;
  inset: 8px;
  border: 2px dashed rgba(255, 255, 255, 0.85);
  border-radius: 8px;
  pointer-events: none;
}

.timeline-scale {
  display: flex;
  gap: 8px;
}

.timeline-axis {
  position: relative;
  width: 34px;
  flex-shrink: 0;
}

.timeline-tick {
  position: absolute;
  left: 0;
  transform: translateY(-50%);
  white-space: nowrap;
  font-size: 0.7rem;
  color: #757575;
}

.timeline-lanes {
  display: flex;
  flex: 1;
  min-width: 0;
  gap: 6px;
  position: relative;
}

.timeline-gridlines {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.timeline-gridline {
  position: absolute;
  left: 0;
  right: 0;
  border-top: 1px dashed rgba(40, 40, 40, 0.2);
}

.timeline-lane {
  position: relative;
  flex: 1;
  min-width: 0;
}

.timeline-step {
  position: absolute;
  left: 0;
  right: 0;
  box-sizing: border-box;
  padding: 3px 6px;
  border-radius: 6px;
  background: #6d4c41;
  color: #fff;
  font-size: 0.78rem;
  line-height: 1.25;
  overflow: hidden;
  outline: 1px solid rgba(255, 255, 255, 0.4);
  outline-offset: -1px;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 4;
  line-clamp: 4;
  /* -webkit-line-clamp adds a "…" at the cut point by default; clip it
     instead so an overflowing step just ends mid-line with no ellipsis. */
  text-overflow: clip;
}

.timeline-step.is-tappable {
  cursor: pointer;
}

/* A card cascaded rightward within a shared-equipment lane (see
   timeline.js's packSubLanes) - a visible left edge sells the "stacked on
   top of the one behind it" look a bare position change wouldn't. */
.timeline-step-stacked {
  box-shadow: -3px 0 6px rgba(0, 0, 0, 0.3);
}

.timeline-step-text {
  display: inline;
}

.timeline-step-badge {
  position: absolute;
  top: 2px;
  right: 4px;
  padding: 0 4px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.85);
  color: #4e342e;
  font-size: 0.62rem;
  line-height: 1.3;
  font-weight: var(--ui-font-weight-bold, bold);
}

.timeline-step.is-current {
  outline: 2px solid #ffca28;
  outline-offset: -2px;
}

.timeline-step.is-done {
  background: #9e9e9e;
  opacity: 0.6;
}

.timeline-now-line {
  position: absolute;
  left: 0;
  right: 0;
  height: 0;
  border-top: 2px solid #e53935;
  z-index: 1;
}

.step-card {
  margin-top: 12px;
  padding: 16px 18px;
  border-radius: 10px;
  color: #2a2a2a;
  background-color: #fdf8ec;
  background-image: repeating-linear-gradient(
    to bottom,
    transparent 0,
    transparent 30px,
    rgba(93, 64, 55, 0.14) 30px,
    rgba(93, 64, 55, 0.14) 31px
  );
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3), 0 1px 3px rgba(0, 0, 0, 0.2);
}

.step-card-title {
  display: inline-block;
  margin: 0 0 4px;
  font-size: 1.4rem;
  font-weight: var(--ui-font-weight-bold);
  color: #4e342e;
  border-bottom: 2px dashed rgba(78, 52, 46, 0.4);
  padding-bottom: 4px;
}

.step-list {
  margin: 12px 0 0;
  padding-left: 1.25em;
}

.step-list li {
  padding: 10px 0;
  border-bottom: 1px solid #eee;
}

.step-list li.is-tappable {
  cursor: pointer;
}

.step-list li.is-current {
  background: rgba(255, 202, 44, 0.15);
}

.step-list li.is-done .step-text {
  text-decoration: line-through;
  opacity: 0.6;
}

.step-text {
  display: block;
}

.step-timing {
  display: block;
  margin-top: 4px;
  color: #757575;
  font-size: 0.85rem;
}

/* -- Recipe editor -- */

/* An app shell rather than a scrolling document, at every width: the page
   itself never scrolls, and whichever box holds the sections does. That's
   what gives each pane its own scroll position, so switching panes (or
   columns) and coming back lands where you left off instead of at the top
   - the whole point of splitting them up. */
.editor-page {
  height: 100dvh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

.editor-main {
  max-width: 640px;
  /* Explicit, because the auto margins below are cross-axis margins on a
     flex item, and those switch off the `stretch` that would otherwise
     size this box - leaving it to shrink-wrap the three-pane track inside
     it, i.e. three viewports wide. */
  width: 100%;
  margin: 0 auto;
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  /* The two panes waiting their turn sit to the right of this box. `clip`
     rather than `hidden` because `hidden` on one axis makes the other one
     `auto` - which would hand the scrolling back to this element, exactly
     what .editor-pane below is for. */
  overflow-x: clip;
  /* No padding of its own, on any side. Clipping happens at the padding
     box, so side padding would be a strip the waiting panes show through
     (the nearest one's scrollbar lands in it); padding below would be a
     gap under a save bar that should sit on the edge; and above, the first
     section's own margin already provides the spacing. Whoever needs an
     inset carries it - the panes on a tall viewport, .editor-columns on a
     wide one. */
  padding: 0;
}

/* A row of the header, not a strip below it - so it takes the space the
   heading was using rather than adding to it. See editor.html's note. */
.editor-pane-tabs {
  display: flex;
  flex: 1;
  min-width: 0;
  gap: 6px;
}

.editor-pane-tab {
  flex: 1;
  min-width: 0;
  padding: 8px 6px;
  border: 1px solid #ddd;
  border-radius: 999px;
  background: #fff;
  font: inherit;
  font-size: 0.85rem;
  color: #5c7386;
  cursor: pointer;
  /* The count badge rides along inside the button. */
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}

.editor-pane-tab[aria-selected="true"] {
  border-color: #6d4c41;
  background: #6d4c41;
  color: #fff;
}

/* Same number the ingredients section's own counter shows, hoisted onto
   the tab so it's still readable from the other two panes - the one piece
   of cross-pane state worth carrying, and it costs no width to carry it
   here. */
.editor-pane-tab-badge {
  padding: 0 7px;
  border-radius: 999px;
  background: #fff3cd;
  color: #8a6d3b;
  font-size: 0.72rem;
}

.editor-pane-tab-badge[hidden] {
  display: none;
}

/* One pane wide, three panes long: the track slides sideways by whole
   pane widths, so a switch reads as moving along a row rather than as the
   content being swapped out underneath you. Going overview -> steps
   travels past ingredients for the same reason. */
.editor-columns {
  flex: 1;
  min-height: 0;
  display: flex;
  width: 300%;
  transform: translateX(calc(var(--editor-pane-index, 0) * -33.3333%));
  transition: transform 260ms ease;
}

/* The column wrappers are a wide-viewport concept - here they'd only get
   between the track and its panes. */
.editor-col {
  display: contents;
}

.editor-pane {
  flex: 0 0 33.3333%;
  min-width: 0;
  height: 100%;
  overflow-y: auto;
  padding: 0 var(--gap);
}

@media (prefers-reduced-motion: reduce) {
  .editor-columns {
    transition: none;
  }
}

/* Negated rather than written as base rules the wide layout then undoes:
   each of these only makes sense while the panes are taking turns, and
   spelling that out once here beats three separate overrides further
   down. */
@media not all and (min-aspect-ratio: 9/10) {
  /* Tighter than .app-header's own 16px, so that swallowing the tab strip
     costs the header almost nothing: 8 + 43 + 8 comes to about what the
     heading row measured on its own. */
  .editor-header {
    padding: 8px 16px;
  }

  /* The tabs stand in for it here, and document.title carries the same
     name - but a page with no heading at all in the accessibility tree is
     a needless regression, so it's hidden rather than removed. */
  .editor-header h1 {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  /* Typing is when the shell is at its shortest - the keyboard has taken
     the bottom half of the viewport - and it's also when saving is the one
     thing you're definitely not doing. Dropping the bar hands those 74px
     to the pane exactly when they're scarcest, and it settles what the
     keyboard used to decide by accident: Save is unavailable mid-edit
     because that's the intent, not because something happened to be
     covering it.
     Both halves are needed. The focus half alone keeps hiding the bar
     after the keyboard is dismissed with a back or Done key, which blurs
     nothing (see editor-keyboard.js). The keyboard half alone would hide
     it on any viewport that got a fifth shorter, including a desktop
     window dragged down by its bottom edge. Together they say "typing,
     with the keyboard actually up".
     Fields are listed by type rather than as a blanket input:focus - the
     Optional checkboxes are inputs too, and don't summon a keyboard. */
  .editor-page.editor-keyboard-up:has(.editor-pane
      :is(input[type="text"], input[type="number"], input[type="url"], textarea):focus)
    .editor-save-bar {
    display: none;
  }
}

.editor-section {
  margin: 16px 4px;
  padding: 16px 18px;
  border-radius: 10px;
  background-color: #fffdf8;
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3), 0 1px 3px rgba(0, 0, 0, 0.2);
}

.editor-section h2 {
  margin: 0 0 10px;
  font-size: 1.15rem;
}

.editor-section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.editor-section-header h2,
.editor-section-header h3 {
  margin: 0;
}

/* Groups a section's heading with anything that qualifies it (the
   ingredients section's "N not in a step" counter), so the header's own
   space-between only ever pushes the trailing "+" button away from the
   pair, never the counter away from the heading it belongs to. */
.editor-section-header-main {
  display: flex;
  align-items: center;
  gap: 8px;
}

.editor-label {
  display: block;
  margin: 10px 0 4px;
  font-size: 0.9rem;
  color: #5c7386;
}

.editor-input,
.editor-textarea {
  width: 100%;
  padding: 8px 10px;
  border: 1px solid #ccc;
  border-radius: 8px;
  font: inherit;
  background: #fff;
  color: inherit;
}

.editor-textarea {
  min-height: 4.5em;
  resize: vertical;
}

.editor-yield-row {
  display: flex;
  flex-wrap: nowrap;
  gap: var(--gap);
}

.editor-yield-amount {
  flex: 0 0 4.5em;
  min-width: 0;
}

.editor-yield-unit {
  flex: 1 1 0;
  min-width: 0;
}

.editor-photo-preview {
  display: flex;
  justify-content: center;
  margin-bottom: 10px;
}

.editor-photo-frame {
  position: relative;
  width: 100%;
  max-width: 320px;
}

.editor-photo-thumb {
  width: 100%;
  max-width: 320px;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: 8px;
  background: #eee;
}

/* Only once there's something to pan: photo-focus.js adds the class when the
   photo overflows this box, and a photo that fits it exactly must go on
   scrolling the pane under a finger like any other image would. Also only
   while unlocked - photoFocusLocked (editor-state.js) forces this off even
   when there's slack to pan, so the frame stays plain-scrollable until the
   reviewer taps the lock button. */
.editor-photo-thumb.is-draggable {
  cursor: grab;
  touch-action: none;
}

.editor-photo-thumb.is-dragging {
  cursor: grabbing;
}

.editor-photo-lock-icon {
  width: 1.8em;
  height: 1.8em;
  font-size: 1rem;
  flex-shrink: 0;
}

/* Hidden until photo-focus.js's is-croppable says there's actually
   something worth locking/unlocking - same "follows the affordance rather
   than needing its own toggle in JS" trick used elsewhere in the editor.
   Keyed off is-croppable rather than is-draggable so this - the lock
   toggle included, now that the whole hint line *is* the toggle rather
   than a small button next to it - shows up as soon as there's something
   to unlock, not only after the reviewer has already found and tapped it.

   Tinted unconditionally rather than only on :hover/:focus, same reasoning
   as .editor-ingredient-row-linkable: hover never fires on touch, this
   app's primary input, so the tint is what has to carry the "this whole
   line is tappable" signal - not just the small icon inside it. */
.editor-photo-drag-hint {
  display: none;
  align-items: center;
  justify-content: center;
  gap: 6px;
  width: 100%;
  box-sizing: border-box;
  padding: 6px 10px;
  border: none;
  border-radius: 8px;
  background: rgba(106, 27, 154, 0.08);
  font: inherit;
  text-align: center;
  cursor: pointer;
}

.editor-photo-drag-hint:hover,
.editor-photo-drag-hint:focus-visible {
  background: rgba(106, 27, 154, 0.16);
  outline: 1px solid #ce93d8;
  outline-offset: -1px;
}

.editor-photo-preview:has(.is-croppable:not([hidden])) + .editor-photo-drag-hint {
  display: flex;
}

.editor-photo-actions {
  display: flex;
  gap: 8px;
  margin-bottom: 4px;
}

/* Author-stylesheet display:flex above otherwise beats the UA stylesheet's
   [hidden] { display: none } - same cascade origin/specificity tie broken
   by source order, so the flex layout would win and the row stayed put
   even with the hidden attribute set. */
.editor-photo-actions[hidden] {
  display: none;
}

.editor-photo-button {
  flex: 1;
  padding: 8px 10px;
  border: 1px solid #ccc;
  border-radius: 8px;
  background: #fff;
  color: inherit;
  font: inherit;
  cursor: pointer;
}

.editor-photo-remove {
  color: #c62828;
  border-color: #c62828;
}

.editor-photo-url-row {
  display: flex;
  gap: 8px;
}

.editor-photo-url-row .editor-input {
  flex: 1;
  width: auto;
  min-width: 0;
}

.editor-photo-url-row .editor-photo-button {
  flex: 0 0 auto;
}

.editor-photo-button:disabled {
  opacity: 0.5;
  cursor: default;
}

.editor-warnings {
  margin: 0;
  padding-left: 1.25em;
  color: #8a6d3b;
  font-size: 0.9rem;
}

.editor-hint {
  margin: 6px 0;
  color: #757575;
  font-size: 0.85rem;
}

.editor-language-override {
  display: flex;
  align-items: center;
  gap: var(--gap);
  margin-top: 6px;
}

.editor-language-override select {
  flex: 1;
  min-width: 0;
  font: inherit;
  padding: 6px 8px;
  border: 1px solid #ccc;
  border-radius: 8px;
  background: #fff;
  color: inherit;
}

.editor-language-override .editor-photo-button {
  flex: 0 0 auto;
}

.editor-ingredient-list,
.editor-step-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* DAG gutter beside the step list - see editor.js's renderStepGraph. The
   svg's own pixel width/height are set from JS (lane count and measured
   row positions), not CSS - flex only decides the *layout*, not the
   drawing's own coordinate space.
   `isolation: isolate` contains .editor-step-graph's z-index (below) to a
   stacking context scoped to just this wrap - without it, that z-index
   competes directly in the *page's* stacking context against the sticky
   header/fixed save bar (both z-index 1 or "auto"/0), and during a scroll
   that overlaps them wins outright or ties-and-loses-to-DOM-order, letting
   the graph paint over page chrome it was never meant to compete with. */
.editor-step-graph-wrap {
  display: flex;
  align-items: stretch;
  isolation: isolate;
  /* Containing block for .editor-step-swap-button below, which is placed at
     the same lane x the svg draws circles at - so it has to measure from the
     same left edge the svg's own coordinate space starts at. renderStepGraph
     reads row positions as a rect-delta against this element rather than via
     offsetTop, so making it positioned doesn't disturb that. */
  position: relative;
}

.editor-step-graph-wrap .editor-step-list {
  flex: 1 1 auto;
  min-width: 0;
}

.editor-step-graph {
  flex: 0 0 auto;
  overflow: visible;
  /* A left-swipe (reattach) drags a row's own translateX toward/into the
     gutter's space (see step-swipe.js) - without this, the row (painted
     after the svg in DOM order) would slide on top of it, covering the
     very merge-into-trunk animation the drag is meant to show. z-index
     works directly here with no `position` needed - both this and
     .editor-step-list are flex items of .editor-step-graph-wrap, and flex
     items respect z-index without establishing position first. */
  z-index: 1;
}

.editor-step-graph-edge {
  fill: none;
  stroke: #8d6e63;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.editor-step-graph-node {
  fill: #6d4c41;
  cursor: pointer;
}

.editor-step-graph-node-armed {
  fill: #6a1b9a;
}

.editor-step-graph-node-dimmed {
  opacity: 0.35;
}

.editor-step-graph-hit {
  cursor: pointer;
}

/* "Trade these two rows' places" - one per divider, drawn in the gutter on
   the line it reverses (see editor-steps-list.js's renderStepSwapControls,
   which sets the width and top).

   The button is the *target*, and it spans the gutter's entire width: a
   divider only ever gets one of these, since a step has at most one lane to
   continue into, so nothing else in the gutter competes for that band and all
   of it can belong to this control. The visible pill inside is a separate
   element precisely so it can stay small enough to sit on one 18px lane
   without covering the lines either side - target size and drawn size are
   different questions here, and only one of them is constrained by the lane.

   29px tall is the divider's own margin box (its 1px line plus the 14px
   margins either side), so the target reaches the full height available to
   it without straying into the row bands that .editor-step-graph-hit owns
   for arming. */
.editor-step-swap-button {
  position: absolute;
  left: 0;
  transform: translateY(-50%);
  height: 29px;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  /* Above .editor-step-graph's own z-index: 1 - the svg is a later flex
     sibling with a real z-index, so a merely-positioned button (z-index
     auto) would be painted under the gutter it's meant to sit on. */
  z-index: 2;
}

/* A pill rather than the circular language .editor-insert-divider-button
   uses: the different shape keeps it from reading as a sibling of that +
   button, which does something unrelated and only happens to be nearby.
   `left` comes from JS - the lane the swap acts on, the same laneX the
   gutter draws that lane's circles at. */
.editor-step-swap-pill {
  position: absolute;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 14px;
  height: 22px;
  display: grid;
  place-items: center;
  border: 1px solid #ccc;
  /* Width-independent, so the pill's size lives in exactly one place. */
  border-radius: 999px;
  /* Opaque, matching the rows either side - the edge passes behind the pill
     rather than visibly crossing through it, same as
     .editor-insert-divider-button does with the divider line. */
  background: #fffdf8;
  color: #8d6e63;
}

/* On the pill, not the button - the button is an invisible full-gutter
   target, so an outline around it would frame empty gutter instead of the
   thing being focused. */
.editor-step-swap-button:focus-visible {
  outline: none;
}

.editor-step-swap-button:focus-visible .editor-step-swap-pill {
  outline: 2px solid #6a1b9a;
  outline-offset: 2px;
}

.editor-step-swap-pill svg {
  display: block;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Two fixed columns: everything wrappable (name, badges, amount/unit, the
   raw import line) on the left, remove/link on the right. Only
   editor-ingredient-main wraps - the row itself never does - so those
   buttons stay put regardless of how much the left side wraps. */
.editor-ingredient-row {
  display: flex;
  align-items: center;
  gap: 8px;
  /* No border-bottom here - every row (and alt-group box) now has its own
     .editor-list-divider after it (see editor-ingredients.js's
     renderIngredients), same as .editor-step-row. A row border here would
     sit right on top of that divider's line, doubling it up. */
  touch-action: pan-y;
}

.editor-ingredient-main {
  display: flex;
  flex: 1 1 auto;
  min-width: 0;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}

/* Enlarges the click target while a link is pending - see buildIngredientRow's
   delegated click listener - so picking the other alternative doesn't
   require hitting the small link button itself. Tinted unconditionally, not
   just on :hover - the hint text explaining this often scrolls out of view
   on a small screen, and hover never fires on touch anyway, so the tint
   itself has to carry the "these are tappable" signal. */
.editor-ingredient-row-linkable {
  cursor: pointer;
  background: rgba(106, 27, 154, 0.08);
}

.editor-ingredient-row-linkable:hover {
  background: rgba(106, 27, 154, 0.16);
  outline: 1px solid #ce93d8;
  outline-offset: -1px;
  border-radius: 6px;
}

.editor-ingredient-name {
  border: none;
  background: none;
  color: inherit;
  font: inherit;
  font-weight: var(--ui-font-weight-medium);
  text-align: left;
  padding: 0;
  cursor: pointer;
  text-decoration: underline dotted;
}

.editor-ingredient-amount-unit {
  display: flex;
  flex-wrap: nowrap;
  gap: 8px;
}

.editor-ingredient-amount {
  width: 5em;
}

.editor-ingredient-unit {
  width: 6em;
}

.editor-ingredient-raw {
  flex-basis: 100%;
  color: #757575;
  font-size: 0.8rem;
  font-style: italic;
}

.editor-ingredient-note-row {
  flex-basis: 100%;
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  max-width: 100%;
}

.editor-ingredient-note-row .editor-ingredient-optional-row {
  flex-shrink: 0;
  padding: 0;
}

/* Sized to fill whatever's left of the row next to the optional checkbox,
   and never wider than that - min-width: 0 lets it actually shrink there
   instead of forcing the row (and everything after it) wider than the
   ingredient row's own box, which used to push it out past the row's
   fixed-width remove/link buttons on narrow screens. */
.editor-ingredient-note-button {
  flex: 1;
  min-width: 0;
  max-width: 100%;
  display: flex;
  align-items: center;
  gap: 8px;
  border: 1px solid #ccc;
  border-radius: 8px;
  background: #fff;
  padding: 6px 10px;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}

/* No note yet - "Add a note" is short and fixed, so the button only needs
   to be as wide as that reads, not stretched out to the same width a real
   (up to 2-line) note would use. */
.editor-ingredient-note-button-empty {
  flex: 0 0 auto;
}

.editor-ingredient-note-icon {
  flex-shrink: 0;
  font-size: 1.1rem;
  line-height: 1;
}

/* Clipped to 2 lines with an ellipsis rather than wrapping further - the
   full text is a tap away in the note dialog (see openIngredientNoteDialog). */
.editor-ingredient-note-text {
  flex: 1;
  min-width: 0;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  text-overflow: ellipsis;
  font-family: var(--content-font);
  font-size-adjust: var(--content-font-size-adjust);
  font-weight: var(--content-font-weight);
  font-size: 0.85rem;
  line-height: 1.3;
}

.editor-ingredient-note-text-empty {
  color: #888;
  font-style: italic;
}

.editor-ingredient-prep {
  background: transparent;
  color: inherit;
  font-weight: var(--ui-font-weight-bold, bold);
  font-style: normal;
  text-decoration: underline;
  text-decoration-color: currentColor;
  text-decoration-thickness: 2px;
  text-underline-offset: 2px;
}

/* An alternative group's members all repeat the same raw description text
   (see editor-ingredients.js's ownNameSpan/ownAmountSpan) - this marks the
   slice(s) that are actually about this particular member, distinct from
   .editor-ingredient-prep (a bold currentColor underline) so the two read
   as separate signals when both land in the same row. A background tint
   read fine in the light theme but sat too close in luminance to the dark
   theme's own background to stay legible, so this uses a colored underline
   instead - readable against either - with the color itself swapped for
   dark mode below, the same way .editor-alt-group-header does. */
.editor-ingredient-own-name {
  background: transparent;
  color: inherit;
  font-weight: inherit;
  font-style: normal;
  text-decoration: underline;
  text-decoration-color: #6a1b9a;
  text-decoration-style: dashed;
  text-decoration-thickness: 2px;
  text-underline-offset: 2px;
}

.editor-badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 0.72rem;
  white-space: nowrap;
}

/* The display above outranks the UA stylesheet's [hidden] rule - same
   cascade origin, higher specificity - so a hidden badge needs saying
   twice (see .editor-photo-actions[hidden] for the same tie). The
   ingredients section's counter is hidden whenever nothing is left to
   place, and would otherwise sit in the header as an empty pill. */
.editor-badge[hidden] {
  display: none;
}

.editor-badge-matched {
  background: #e0f2e9;
  color: #2e7d32;
}

.editor-badge-new {
  background: #fdecea;
  color: #b71c1c;
}

.editor-badge-warning {
  background: #fff3cd;
  color: #8a6d3b;
}

.editor-badge-synthesized {
  background: #e3f2fd;
  color: #1565c0;
}

/* A whole ingredient-list section (e.g. "For the dressing" - see
   draft.ingredientPurposeGroups), offered in the step dialog's "Add from
   ingredients list" as a one-tap "add every not-yet-used ingredient in this
   section" shortcut - distinct from a single ingredient/alternative-choice
   badge both in color and via the "Section:" prefix, so it doesn't read as
   just another ingredient name in the same row. */
.editor-badge-purpose {
  background: #d9f2f0;
  color: #00695c;
  font-weight: 600;
}

.editor-badge-purpose::before {
  content: "Section: ";
  font-weight: 400;
  opacity: 0.75;
}

/* The step currently introducing an ingredient/alternative group, shown in
   the ingredients list where "put in a step…" sits for one nothing uses yet
   (see editor-ingredients.js's buildUsedInStepBadge). Deliberately the
   quietest badge here - it's steady-state information, not something
   needing attention like the amber "add a step" it replaces, so it carries
   only the jump glyph and none of .editor-badge-action's underline. */
.editor-badge-step {
  display: inline-grid;
  place-items: center;
  /* Taller than .editor-badge's own padding, so a 10px glyph still makes a
     pill the same height as the text badges beside it. */
  padding: 5px 8px;
  background: #ececec;
  color: #5f5f5f;
}

/* Outranks .editor-badge-action's dotted underline, which it would
   otherwise inherit by being later in the file - there's no text under it
   to underline, only the glyph. */
.editor-badge-step.editor-badge-action {
  text-decoration: none;
}

.editor-badge-step svg {
  display: block;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* A badge that's actually a button, rather than a label that only names a
   problem - the ingredient list's "put in a step…" picker and its section
   counter, and the before-you-save queue's jump links. Only resets the
   <button> defaults; color/background still come from whichever
   .editor-badge-* it's paired with, same pill either way. */
.editor-badge-action {
  border: none;
  font: inherit;
  cursor: pointer;
  text-decoration: underline dotted;
}

.editor-ingredient-actions {
  display: flex;
  flex-direction: column;
  flex: 0 0 auto;
  align-items: center;
  gap: 4px;
}

.editor-ingredient-link-wrap {
  position: relative;
  display: flex;
  align-items: center;
}

.editor-ingredient-link-button {
  width: 1.8em;
  height: 1.8em;
  padding: 0;
  border-radius: 50%;
  border: 1px solid #ccc;
  background: transparent;
  color: inherit;
  font: inherit;
  line-height: 1;
  cursor: pointer;
}

/* Slides out from behind the link button rather than reserving its own
   space - .editor-ingredient-link-wrap sizes to the link button alone, so
   this never shifts the remove/link column or the row's own gap. */
.editor-ingredient-alt-button {
  position: absolute;
  top: 50%;
  right: 100%;
  margin-right: 4px;
  width: 1.8em;
  height: 1.8em;
  padding: 0;
  border-radius: 50%;
  border: 1px solid #6a1b9a;
  background: #fff;
  color: #6a1b9a;
  font: inherit;
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  transform: translate(8px, -50%) scale(0.6);
  transition: transform 0.18s ease, opacity 0.18s ease;
}

.editor-ingredient-alt-button-visible {
  opacity: 1;
  pointer-events: auto;
  transform: translate(0, -50%) scale(1);
}

.icon-button-active {
  background: #6a1b9a;
  border-color: #6a1b9a;
  color: #fff;
}

/* Box around an alternatives group - members are indented (padding-left on
   editor-alt-group-members) and each member's unlink button lives in the
   gutter that indent leaves free, to the left of its row. */
.editor-alt-group {
  padding: 8px 10px;
  border: 1px solid #ce93d8;
  border-radius: 8px;
  background: #f3e5f5;
}

.editor-alt-group-header {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.8rem;
  font-weight: var(--ui-font-weight-medium);
  color: #6a1b9a;
  padding-bottom: 4px;
}

/* Everything but the link controls - wraps internally onto as many lines
   as the text/checkboxes need; flex: 1 1 auto (against the link controls'
   flex: 0 0 auto below) is what reserves the link button's space on the
   right, the same growing-main/fixed-actions split .editor-ingredient-row
   and .editor-ingredient-main/.editor-ingredient-actions already use. */
.editor-alt-group-header-main {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  flex: 1 1 auto;
  min-width: 0;
}

.editor-alt-group-header .editor-ingredient-link-wrap {
  flex: 0 0 auto;
}

.editor-alt-group-members {
  list-style: none;
  margin: 0;
  padding: 0;
}

.editor-alt-group-member {
  display: flex;
  align-items: flex-start;
  gap: 4px;
}

.editor-alt-group-member .editor-ingredient-row {
  flex: 1;
  min-width: 0;
  border-bottom: none;
  padding: 4px 0;
}

.editor-alt-group-member + .editor-alt-group-member .editor-ingredient-row {
  border-top: 1px dashed #ce93d8;
}

.editor-alt-unlink {
  flex-shrink: 0;
  width: 1.8em;
  height: 1.8em;
  margin-top: 4px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: rgba(106, 27, 154, 0.15);
  color: inherit;
  font: inherit;
  line-height: 1;
  cursor: pointer;
}

.editor-remove-button {
  /* .editor-step-row's align-items: stretch only affects items with no
     explicit cross-size - this button's fixed height opts it out of that,
     which otherwise leaves it pinned to the row's top edge instead of
     centered against the full row (description + meta line beneath it). */
  align-self: center;
  width: 1.8em;
  height: 1.8em;
  border-radius: 50%;
  border: 1px solid #ccc;
  background: transparent;
  color: inherit;
  font: inherit;
  line-height: 1;
  cursor: pointer;
}

/* A gap between list rows with a "+" to insert/add right there - the step
   list's between-every-row dividers (editor-steps-list.js's buildDivider)
   and the ingredient list's single trailing one (editor-ingredients.js's
   buildIngredientDivider) share this same look and markup shape. A thin
   horizontal line (this element itself, via background instead of border so
   the button below can center on it with a plain top/left/transform) with
   the + button sitting right on top of it - not floating in the gap below,
   which read as detached from the line. Solid, full-strength button styling
   (same as .editor-remove-button) since inserting/adding a row is just as
   first-class an action as removing one, not a secondary/muted one. */
.editor-list-divider {
  position: relative;
  height: 1px;
  background: #eee;
  margin: 14px 0;
}

.editor-insert-divider-button {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 1.8em;
  height: 1.8em;
  border-radius: 50%;
  border: 1px solid #ccc;
  /* Opaque, matching the row background either side - the line passes
     behind the button rather than visibly crossing through it. */
  background: #fffdf8;
  color: inherit;
  font: inherit;
  line-height: 1;
  cursor: pointer;
}

.editor-step-row {
  display: flex;
  align-items: stretch;
  gap: 8px;
  padding: 4px 0;
  /* No border-bottom here - it would swipe away with the row (see
     step-swipe.js's transform: translateX on the row itself), leaving a
     stray second line next to the dedicated .editor-list-divider below it,
     which stays put. That divider is the only line between rows now. */
  background: #fffdf8;
  touch-action: pan-y;
}

.editor-step-row-error {
  outline: 2px solid #c62828;
  outline-offset: 2px;
  border-radius: 6px;
}

/* Same outline box as .editor-step-row-error (same element, same offset/
   radius) rather than a separate one on .editor-step-main - a row that's
   both armed and mid-error would otherwise show two non-overlapping
   outlines at once. Armed takes precedence: renderValidation skips adding
   -row-error to the armed step, so only this purple one ever shows. */
.editor-step-row-armed {
  outline: 2px solid #6a1b9a;
  outline-offset: 2px;
  border-radius: 6px;
}

.editor-step-main {
  flex: 1;
  min-width: 0;
  padding: 8px 4px;
  cursor: pointer;
}

.editor-step-main-dimmed {
  opacity: 0.5;
}

.editor-step-text {
  display: block;
}

.editor-step-main .editor-badge-synthesized {
  margin-top: 4px;
}

.editor-step-meta {
  display: block;
  margin-top: 4px;
  color: #757575;
  font-size: 0.82rem;
}

.editor-validation-list {
  margin: 0;
  padding-left: 1.25em;
}

.editor-validation-error {
  color: #c62828;
}

/* Something worth fixing before saving that still saves fine - the
   unplaced-ingredients queue (see editor-steps-list.js's
   buildUnplacedIngredientsWarning). Amber rather than the errors' red, and
   no ::marker recolouring, since unlike them it never blocks the save
   button. Its jump links are inline-block badges, so they wrap on their own
   and only need spacing - which is also why this stays a plain list item
   rather than a flex row, so it keeps the same bullet the errors have. */
.editor-validation-warning {
  color: #8a6d3b;
  /* Roomier than the body's fixed 25px, which the badges below - taller
     than the text they sit in - would otherwise crowd into the line above
     once the list wraps. A px value for the same reason `body` uses one. */
  line-height: 30px;
}

.editor-validation-warning .editor-badge {
  margin: 2px 4px 2px 0;
}

/* Where revealIngredient lands - long enough to catch the eye after a
   smooth scroll finishes, then out of the way. */
.editor-ingredient-flash {
  animation: editor-ingredient-flash 1.4s ease-out;
}

@keyframes editor-ingredient-flash {
  0%,
  40% {
    background: #fff3cd;
  }
  100% {
    background: transparent;
  }
}

@media (prefers-reduced-motion: reduce) {
  .editor-ingredient-flash {
    animation-duration: 0.01ms;
  }
}

/* The shell's bottom row, in flow - not pinned to the viewport. Fixed
   positioning anchors to the layout viewport, which .editor-page's own
   100dvh height does not, so while a mobile URL bar is retracting the two
   disagree and the bar flickers or leaves a gap under itself. As a flex
   row there's only the one box tracking the viewport, and it resizes with
   everything else.
   Costs nothing to do it this way: the bar is shorter than the 90px the
   panes used to reserve to scroll clear of it, and content no longer
   passes underneath it - including a save error, which grows this box
   rather than silently reaching further over the pane behind it. */
.editor-save-bar {
  flex: none;
  padding: 10px var(--gap);
  background: #fff;
  border-top: 1px solid #eee;
  box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.15);
}

.editor-save-bar .cook-button {
  width: 100%;
}

.editor-save-error {
  margin: 6px 0 0;
  color: #c62828;
  font-size: 0.85rem;
  text-align: center;
}

.editor-step-dialog {
  width: min(560px, 92vw);
  max-height: 88vh;
  overflow: hidden;
  border: none;
  border-radius: 12px;
  padding: 0;
  color: inherit;
  background: #fff;
}

/* Scoped to [open] rather than the bare .editor-step-dialog selector -
   `display` must stay entirely unset while closed, so the UA stylesheet's
   own `dialog:not([open]) { display: none }` is what hides it. An
   unconditional `display: flex` here would out-rank that UA rule (author
   origin always wins over user-agent origin, regardless of specificity)
   and keep the closed dialog rendered - still `display: flex`, just no
   longer `:modal` so it falls out of its fixed/centered position into
   whatever the page's normal flow computes for it. */
.editor-step-dialog[open] {
  display: flex;
  flex-direction: column;
}

.editor-step-dialog::backdrop {
  background: rgba(0, 0, 0, 0.4);
}

/* Scrolls on its own; the footer below (delete/done) stays put regardless
   of scroll position, since it sits outside this element entirely rather
   than just being visually pinned within it. `overscroll-behavior: contain`
   stops scroll *chaining* - without it, reaching the top/bottom edge here
   hands remaining wheel/touch scroll off to the page behind the modal
   dialog, scrolling it too, even though that page is meant to be inert
   while the dialog is open. */
.editor-dialog-content {
  overflow-y: auto;
  overscroll-behavior: contain;
  flex: 1 1 auto;
  min-height: 0;
  padding: var(--gap);
}

.editor-dialog-footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  flex: 0 0 auto;
  padding: 12px var(--gap);
  border-top: 1px solid #eee;
}

.editor-dialog-close {
  width: auto;
  padding: 6px 14px;
}

.editor-dialog-section {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid #eee;
}

.editor-dialog-section h3 {
  margin: 0 0 8px;
  font-size: 1rem;
}

.editor-dialog-delete {
  color: #c62828;
  border-color: #c62828;
}

.editor-stepper-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 6px 0;
}

.editor-duration-source {
  display: block;
  margin-bottom: 8px;
  background: #eef2f7;
  color: #4a6fa5;
}

.editor-checkbox-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 0;
  font-size: 0.92rem;
}

.editor-advanced-deps {
  margin-top: 6px;
}

.editor-advanced-deps summary {
  cursor: pointer;
  color: #4a6fa5;
  font-size: 0.9rem;
}

.editor-chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 8px;
}

.editor-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 6px;
  border-radius: 999px;
  background: #eef2f7;
  font-size: 0.85rem;
}

.editor-chip-amount {
  width: 3.2em;
  border: none;
  border-radius: 4px;
  background: #fff;
  font: inherit;
  font-size: 0.85rem;
  padding: 2px 4px;
}

.editor-chip-unit {
  width: 3.6em;
  border: none;
  border-radius: 4px;
  background: #fff;
  font: inherit;
  font-size: 0.85rem;
  padding: 2px 4px;
}

.editor-chip-remove {
  border: none;
  background: none;
  color: inherit;
  font: inherit;
  line-height: 1;
  cursor: pointer;
}

.editor-chip-label-button {
  border: none;
  background: none;
  color: inherit;
  font: inherit;
  padding: 0;
  cursor: pointer;
}

.editor-chip-label-button:hover,
.editor-chip-label-button:focus-visible {
  text-decoration: underline;
}

.editor-chip-recipe-default {
  font-weight: 600;
}

.editor-chip-suggestion {
  border: 1px dashed #999;
  background: transparent;
  cursor: pointer;
  color: inherit;
  font: inherit;
}

.editor-ingredient-optional-row {
  padding: 2px 0;
  font-size: 0.85rem;
  font-weight: var(--ui-font-weight);
  color: #555;
}

/* -- Equipment needs + their sanctioned alternatives -- */

.editor-equipment-need-card {
  margin-top: 8px;
  padding: 8px 10px;
  border: 1px solid #ccd7e0;
  border-radius: 8px;
  background: #f7f9fb;
}

.editor-equipment-need-card .editor-chip-row {
  margin-top: 0;
}

.editor-equipment-quantity-row,
.editor-equipment-settings-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
  flex-wrap: wrap;
}

.editor-equipment-quantity-row .editor-input,
.editor-equipment-settings-row .editor-input {
  width: auto;
  flex: 1;
  min-width: 0;
}

/* The unit the value is being typed in - a fixed pick beside the number,
   not a second field competing for the row's width. */
.editor-equipment-settings-row .editor-equipment-settings-unit {
  flex: 0 0 auto;
  min-width: 4.5em;
}

/* What the number will actually be stored as, when that differs from the
   unit it's typed in - its own line, so it never squeezes the field. */
.editor-equipment-settings-stored {
  flex: 1 0 100%;
  margin: 0;
}

.editor-equipment-settings-stored:empty {
  display: none;
}

.editor-inline-label {
  flex: 0 0 auto;
  font-size: 0.9rem;
  color: #5c7386;
  white-space: nowrap;
}

.editor-equipment-alt-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 8px;
  padding-left: 14px;
  border-left: 2px solid #ccd7e0;
}

.editor-equipment-alt-chip {
  align-self: flex-start;
}

.editor-equipment-alt-note {
  width: 12em;
  border: none;
  border-radius: 4px;
  background: #fff;
  font: inherit;
  font-size: 0.82rem;
  padding: 2px 4px;
}

/* -- Kitchen equipment ownership + physical units (kitchens.html) -- */

.kitchen-equipment-row {
  margin-top: 12px;
  padding: 10px 12px;
  border: 1px solid #ccd7e0;
  border-radius: 8px;
  background: #f7f9fb;
}

.kitchen-equipment-row-header {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.kitchen-number-input {
  width: 6.5em;
}

.kitchen-unit-section {
  margin-top: 10px;
  padding-left: 14px;
  border-left: 2px solid #ccd7e0;
}

.kitchen-unit-card {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
  padding: 6px 8px;
  border: 1px solid #ccd7e0;
  border-radius: 8px;
  background: #fff;
}

.kitchen-unit-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
}

.kitchen-unit-label {
  width: 12em;
}

.kitchen-holder-select {
  width: 14em;
}

.editor-equipment-add-alt {
  display: inline-block;
  margin-top: 8px;
  padding: 4px 10px;
  border: 1px dashed #999;
  border-radius: 999px;
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: 0.82rem;
  cursor: pointer;
}

/* -- Step type toggle + recipe_ref picker -- */

.editor-step-type-toggle {
  display: flex;
  gap: 8px;
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}

.editor-step-type-button {
  flex: 1;
  padding: 8px 10px;
  border: 1px solid #ccc;
  border-radius: 8px;
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: 0.88rem;
  cursor: pointer;
}

.editor-step-type-button-active {
  background: #4a6fa5;
  border-color: #4a6fa5;
  color: #fff;
}

.editor-badge-recipe-ref {
  background: #ede7f6;
  color: #4527a0;
}

/* -- Collections page -- */

.collection-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.collection-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 12px 4px;
  border-bottom: 1px solid #eee;
  cursor: pointer;
}

.collection-row:last-child {
  border-bottom: none;
}

.collection-row-count {
  color: #757575;
  font-size: 0.85rem;
  white-space: nowrap;
}

.collection-detail-dialog {
  width: min(480px, 92vw);
  max-height: 85vh;
  overflow-y: auto;
  border: none;
  border-radius: 12px;
  padding: var(--gap);
  color: inherit;
  background: #fff;
}

.collection-detail-dialog::backdrop {
  background: rgba(0, 0, 0, 0.4);
}

.collection-member-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.collection-member-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 0;
}

.collection-member-icon {
  width: 2.2em;
  height: 2.2em;
  border-radius: 6px;
  object-fit: cover;
  background: #eee;
}

.collection-member-title {
  flex: 1 1 auto;
  min-width: 0;
}

/* -- Ingredient catalog page -- */

.ingredient-search-input {
  margin-bottom: 10px;
}

.ingredient-detail-dialog {
  width: min(560px, 92vw);
}

.ingredient-inline-row {
  display: flex;
  gap: 8px;
  align-items: center;
}

.ingredient-inline-row .editor-input {
  flex: 1;
  width: auto;
  min-width: 0;
}

.ingredient-inline-row .editor-photo-button {
  flex: 0 0 auto;
}

.ingredient-group-button {
  flex: 1;
  min-width: 0;
  padding: 8px 10px;
  border: 1px solid #ccc;
  border-radius: 8px;
  background: #fff;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.ingredient-toggle-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 6px 0 12px;
}

.ingredient-toggle-chip {
  padding: 4px 10px;
  border-radius: 999px;
  border: 1px solid #ccc;
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: 0.85rem;
  cursor: pointer;
}

.ingredient-toggle-chip-active {
  background: #4a6fa5;
  border-color: #4a6fa5;
  color: #fff;
}

/* -- Delete recipe -- */

.editor-danger-section {
  border: 1px solid #c62828;
}

.editor-danger-section h2 {
  color: #c62828;
}

.editor-delete-open {
  color: #c62828;
  border-color: #c62828;
}

.editor-delete-dialog {
  width: min(420px, 90vw);
  border: none;
  border-radius: 12px;
  padding: var(--gap);
  color: inherit;
  background: #fff;
}

.editor-delete-dialog::backdrop {
  background: rgba(0, 0, 0, 0.4);
}

.editor-delete-actions {
  display: flex;
  justify-content: center;
  margin-top: var(--gap);
}

/* -- Ingredient note dialog -- */

.editor-ingredient-note-dialog {
  width: min(420px, 90vw);
  border: none;
  border-radius: 12px;
  padding: var(--gap);
  color: inherit;
  background: #fff;
}

.editor-ingredient-note-dialog::backdrop {
  background: rgba(0, 0, 0, 0.4);
}

.editor-ingredient-note-dialog h2 {
  margin: 0 0 8px;
}

.editor-ingredient-note-dialog .editor-textarea {
  font-family: var(--content-font);
  font-size-adjust: var(--content-font-size-adjust);
  font-weight: var(--content-font-weight);
  min-height: 5em;
}

.editor-ingredient-note-dialog-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: var(--gap);
}

/* Drag-to-track-end destructive confirm - see slide-to-delete.js. */
.slide-to-confirm-track {
  position: relative;
  height: 3em;
  margin-top: var(--gap);
  border-radius: 999px;
  background: #fdecea;
  overflow: hidden;
  touch-action: none;
}

.slide-to-confirm-hint {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #c62828;
  font-size: 0.85rem;
  font-weight: var(--ui-font-weight-medium);
  pointer-events: none;
}

.slide-to-confirm-handle {
  position: absolute;
  top: 0.2em;
  left: 0.2em;
  width: 4.4em;
  height: 2.6em;
  border-radius: 999px;
  background: #c62828;
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: grab;
  touch-action: none;
}

.slide-to-confirm-handle::after {
  content: "→";
  font-size: 2.2em;
  font-weight: 900;
  line-height: 1;
  -webkit-text-stroke: 2px currentColor;
  transform: translateY(-0.13em);
}

/* -- Ingredient/equipment search sheet -- */

.search-sheet {
  width: 100%;
  height: 100%;
  max-width: 100%;
  max-height: 100%;
  margin: 0;
  border: none;
  padding: var(--gap);
  color: inherit;
  background: #fff;
}

.search-sheet::backdrop {
  background: rgba(0, 0, 0, 0.4);
}

.search-sheet-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 10px;
}

.search-sheet-title {
  margin: 0;
  font-size: 1.1rem;
}

.search-sheet-close {
  padding: 6px 14px;
  border: 1px solid #ccc;
  border-radius: 8px;
  background: transparent;
  color: inherit;
  font: inherit;
  cursor: pointer;
}

.search-sheet-input {
  width: 100%;
  padding: 10px;
  border: 1px solid #ccc;
  border-radius: 8px;
  font: inherit;
}

.search-sheet-list {
  list-style: none;
  margin: 10px 0 0;
  padding: 0;
  max-height: calc(100vh - 140px);
  overflow-y: auto;
  /* Same scroll-chaining fix as .editor-dialog-content - this list is also
     a modal dialog's own scroll region, sometimes nested inside the step
     editor's own dialog. */
  overscroll-behavior: contain;
}

.search-sheet-row {
  padding: 10px 4px;
  border-bottom: 1px solid #eee;
  cursor: pointer;
}

.search-sheet-row-content {
  display: flex;
  align-items: center;
  gap: 8px;
}

.search-sheet-row-content img {
  width: 28px;
  height: 28px;
  border-radius: 6px;
}

.search-sheet-synonym-hint {
  color: #888;
  font-size: 0.85rem;
}

.search-sheet-badge {
  margin-left: auto;
  padding: 2px 8px;
  border-radius: 999px;
  background: #fff3cd;
  color: #8a6d3b;
  font-size: 0.72rem;
}

.search-sheet-create,
.search-sheet-action {
  color: #4a6fa5;
  font-weight: var(--ui-font-weight-medium);
}

@media (min-aspect-ratio: 9/10) {
  .recipe-page {
    height: 100dvh;
    overflow: hidden;
    display: flex;
    flex-direction: column;
  }

  .recipe-page main {
    flex: 1;
    min-height: 0;
  }

  .recipe-columns {
    display: flex;
    gap: calc(var(--gap) * 2);
    height: 100%;
  }

  .recipe-col {
    flex: 1;
    min-width: 0;
    height: 100%;
    overflow-y: auto;
  }

  /* The editor splits the same way the recipe page above does, at the same
     breakpoint: what a recipe *is* on the left (overview, photo, import
     notes, ingredients, deleting it), what it *does* on the right (the
     step graph, the before-you-save list). Editing an ingredient's step
     assignment is the one thing that constantly crosses that line, and in
     two columns both sides of it are on screen at once - no scrolling
     between them, and no losing your place in either. */
  /* Nothing takes turns here, so there's nothing to switch between. */
  .editor-pane-tabs {
    display: none;
  }

  .editor-main {
    /* The one-column reading measure, which two columns don't want. */
    max-width: none;
    /* Nothing parked off to the side to clip any more. */
    overflow-x: visible;
  }

  /* Back to two side-by-side columns from one three-pane track: the
     wrappers become the columns and the panes inside them go back to being
     plain stacked blocks, so overview, photo and ingredients read as the
     single scroll they always did on a wide screen. */
  .editor-columns {
    width: auto;
    transform: none;
    gap: calc(var(--gap) * 2);
    /* The columns' own inset, kept off .editor-main so the save bar below
       them still reaches both edges - the same way the panes carry it on a
       tall viewport. */
    padding: 0 var(--gap);
  }

  .editor-col {
    display: block;
    flex: 1;
    min-width: 0;
    height: 100%;
    overflow-y: auto;
  }

  .editor-pane {
    flex: none;
    height: auto;
    overflow-y: visible;
    padding: 0;
  }
}

@media (prefers-color-scheme: dark) {
  body {
    background-color: #121212;
    color: #eee;
  }

  .app-header {
    background: #1c1c1c;
    border-color: #2a2a2a;
  }

  .cook-button-reset,
  .icon-button,
  #import-url,
  .import-mode-button,
  .import-textarea {
    border-color: #444;
  }

  .import-view-source {
    border-color: #a1887f;
    background: #2b2320;
  }

  .timeline-step.is-done {
    background: #424242;
  }

  .import-dialog {
    background: #1c1c1c;
    color: #eee;
  }

  #import-url,
  .import-textarea {
    background: #121212;
    color: inherit;
  }

  .import-result {
    background: #262626;
  }

  .import-result-error {
    color: #ef9a9a;
  }

  .editor-section,
  .editor-step-row {
    background: #1c1c1c;
  }

  .editor-step-graph-edge {
    stroke: #a1887f;
  }

  .editor-step-graph-node {
    fill: #bcaaa4;
  }

  .editor-step-graph-node-armed {
    fill: #ce93d8;
  }

  .editor-step-row-armed {
    outline-color: #ce93d8;
  }

  .editor-input,
  .editor-textarea,
  .editor-chip-amount,
  .editor-chip-unit,
  .editor-photo-button,
  .ingredient-group-button,
  .editor-ingredient-note-button,
  .editor-language-override select {
    background: #121212;
    border-color: #444;
    color: inherit;
  }

  .editor-ingredient-note-text-empty {
    color: #999;
  }

  .editor-photo-thumb {
    background: #262626;
  }

  .editor-photo-drag-hint {
    background: rgba(206, 147, 216, 0.08);
  }

  .editor-photo-drag-hint:hover,
  .editor-photo-drag-hint:focus-visible {
    background: rgba(206, 147, 216, 0.16);
    outline-color: #6a1b9a;
  }

  .editor-photo-remove {
    color: #ef9a9a;
    border-color: #ef9a9a;
  }

  .editor-ingredient-row,
  .editor-dialog-section,
  .editor-dialog-footer {
    border-color: #333;
  }

  .editor-remove-button {
    border-color: #444;
  }

  .editor-list-divider {
    background: #333;
  }

  .editor-insert-divider-button {
    border-color: #444;
    background: #1c1c1c;
  }

  .editor-step-swap-pill {
    border-color: #444;
    background: #1c1c1c;
    /* Matching .editor-step-graph-edge's own dark-mode stroke, since the
       pill sits on that line and reads as part of it. */
    color: #a1887f;
  }

  .editor-badge-matched {
    background: #1b3a1e;
    color: #a5d6a7;
  }

  .editor-badge-new {
    background: #3a1f1f;
    color: #ef9a9a;
  }

  .editor-badge-warning {
    background: #3a331a;
    color: #ffe082;
  }

  .editor-pane-tab {
    border-color: #444;
    background: #1c1c1c;
    color: #b0aaa2;
  }

  .editor-pane-tab[aria-selected="true"] {
    border-color: #8d6e63;
    background: #8d6e63;
    color: #fff;
  }

  .editor-pane-tab-badge {
    background: #3a331a;
    color: #ffe082;
  }

  .editor-validation-warning {
    color: #ffe082;
  }

  @keyframes editor-ingredient-flash {
    0%,
    40% {
      background: #3a331a;
    }
    100% {
      background: transparent;
    }
  }

  .allergy-info-label {
    color: #ffe082;
  }

  .allergy-chip {
    background: #3a331a;
    color: #ffe082;
  }

  .diet-chip {
    background: #1b3a1e;
    color: #a5d6a7;
  }

  .allergy-diet-chip {
    color: #f0ead6;
    background: linear-gradient(135deg, #3a331a 50%, #1b3a1e 50%);
  }

  .editor-badge-synthesized {
    background: #142a3d;
    color: #90caf9;
  }

  .editor-badge-step {
    background: #33302c;
    color: #b0aaa2;
  }

  .editor-badge-purpose {
    background: #123a38;
    color: #4dd0e1;
  }

  .editor-alt-group {
    background: #2a1f3d;
    border-color: #6a1b9a;
  }

  .editor-alt-group-header {
    color: #ce93d8;
  }

  .editor-ingredient-own-name {
    text-decoration-color: #ce93d8;
  }

  .editor-alt-group-member + .editor-alt-group-member .editor-ingredient-row {
    border-top-color: #6a1b9a;
  }

  .editor-alt-unlink {
    background: rgba(206, 147, 216, 0.2);
  }

  .editor-ingredient-link-button {
    border-color: #444;
  }

  .editor-ingredient-alt-button {
    background: #1c1c1c;
    border-color: #ce93d8;
    color: #ce93d8;
  }

  .icon-button-active {
    background: #ce93d8;
    border-color: #ce93d8;
    color: #1c1c1c;
  }

  .editor-ingredient-row-linkable {
    background: rgba(206, 147, 216, 0.08);
  }

  .editor-ingredient-row-linkable:hover {
    background: rgba(206, 147, 216, 0.16);
    outline-color: #6a1b9a;
  }

  .editor-duration-source {
    background: #202b38;
    color: #90caf9;
  }

  .editor-chip {
    background: #202b38;
  }

  .editor-save-bar {
    background: #1c1c1c;
    border-color: #2a2a2a;
  }

  .editor-step-dialog,
  .search-sheet {
    background: #1c1c1c;
    color: #eee;
  }

  .search-sheet-input,
  .search-sheet-close {
    background: #121212;
    border-color: #444;
    color: inherit;
  }

  .search-sheet-row {
    border-color: #333;
  }

  .editor-ingredient-optional-row {
    color: #aaa;
  }

  .editor-equipment-need-card {
    background: #202b38;
    border-color: #34495e;
  }

  .editor-equipment-alt-list {
    border-color: #34495e;
  }

  .editor-equipment-alt-note {
    background: #121212;
    color: inherit;
  }

  .editor-equipment-add-alt {
    border-color: #666;
  }

  .kitchen-equipment-row {
    background: #202b38;
    border-color: #34495e;
  }

  .kitchen-unit-section {
    border-color: #34495e;
  }

  .kitchen-unit-card {
    background: #121212;
    border-color: #34495e;
  }

  .editor-step-type-button {
    border-color: #444;
  }

  .editor-badge-recipe-ref {
    background: #2a1f3d;
    color: #d1c4e9;
  }

  .editor-danger-section {
    border-color: #ef5350;
  }

  .editor-delete-open {
    color: #ef9a9a;
    border-color: #ef9a9a;
  }

  .editor-delete-dialog {
    background: #1c1c1c;
    color: #eee;
  }

  .editor-ingredient-note-dialog {
    background: #1c1c1c;
    color: #eee;
  }

  .slide-to-confirm-track {
    background: #3a1f1f;
  }

  .collection-row {
    border-color: #333;
  }

  .collection-detail-dialog {
    background: #1c1c1c;
    color: #eee;
  }

  .ingredient-note-dialog {
    background: #1c1c1c;
    color: #eee;
  }

  .collection-member-icon {
    background: #262626;
  }

  .ingredient-toggle-chip {
    border-color: #444;
  }

  .ingredient-toggle-chip-active {
    background: #5c85c9;
    border-color: #5c85c9;
    color: #111;
  }
}
