:root {
  --color-leaf: #34a373;
  --color-leaf-light: #9fe6c0;
  --color-leaf-pale: #eafaf1;
  --color-tomato: #ff7a59;
  --color-cream: #f7fbf9;
  --color-text: #2c3b34;
  --color-card: rgba(255, 255, 255, 0.6);
  --glass-border: rgba(255, 255, 255, 0.6);
  --glass-shadow: rgba(52, 163, 115, 0.16);
  --radius: 14px;
  --tab-bar-height: 64px;
  --tab-rail-width: 84px;
}

* {
  box-sizing: border-box;
}

/* Without !important, any class below that also sets `display` (flex,
   block, etc.) ties with this attribute selector on specificity, and the
   author rule wins -- silently keeping the element visible regardless of
   its `hidden` state. */
[hidden] {
  display: none !important;
}

/* Soft mint/peach mesh, pinned to the viewport (not the document) so the
   frosted-glass panels below always have the same colorful backdrop to
   blur, however far the page is scrolled. */
body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  color: var(--color-text);
  background-color: var(--color-cream);
  background-image:
    radial-gradient(circle at 12% 8%, rgba(159, 230, 192, 0.45) 0%, transparent 55%),
    radial-gradient(circle at 88% 14%, rgba(255, 184, 153, 0.28) 0%, transparent 50%),
    radial-gradient(circle at 50% 100%, rgba(159, 230, 192, 0.32) 0%, transparent 60%);
  background-attachment: fixed;
  min-height: 100vh;
}

/* Toggled by main.js's setActiveTab while on a tab that fits one phone
   screen (Search/Favorites/Account) -- pins the page so there's nothing
   to scroll, instead of leaving a few px of scrollable space for iOS
   Safari's address-bar collapse to expose (see setActiveTab's comment). */
body.scroll-locked {
  position: fixed;
  inset: 0;
  overflow: hidden;
}

/* Falls back to scrolling within main itself, rather than the (now
   fixed) document -- in case content on a locked tab ever doesn't fit a
   particular screen (larger Dynamic Type, a short landscape viewport). */
body.scroll-locked main {
  height: 100%;
  overflow-y: auto;
}

/* "My language" lives inline in the Account tab's signed-in panel now
   (see index.html) -- just a label+select row, no popover of its own. */
.language-menu-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 6px 0;
}

.language-menu-row label {
  font-size: 0.9rem;
  font-weight: 600;
}

.language-select {
  padding: 6px 8px;
  border-radius: 8px;
  border: 2px solid rgba(52, 163, 115, 0.25);
  background: rgba(255, 255, 255, 0.7);
  color: var(--color-text);
  font-size: 0.9rem;
}

/* Shared frosted-glass card look -- used for the language-settings
   dropdown, the Account tab's content, and the Favorites placeholder, so
   they read as one family of "panel" surfaces. */
.panel-card {
  background: rgba(255, 255, 255, 0.7);
  backdrop-filter: blur(20px) saturate(180%);
  -webkit-backdrop-filter: blur(20px) saturate(180%);
  border: 1px solid var(--glass-border);
  color: var(--color-text);
  border-radius: var(--radius);
  padding: 18px 16px;
  text-align: left;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.1);
}

.account-tabs {
  display: flex;
  gap: 8px;
  margin-bottom: 12px;
}

.account-tab {
  flex: 1;
  padding: 10px;
  background: transparent;
  border: 2px solid rgba(52, 163, 115, 0.2);
  border-radius: 10px;
  color: var(--color-text);
  font-size: 0.9rem;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
}

.account-tab.is-active {
  background: rgba(52, 163, 115, 0.15);
  border-color: var(--color-leaf-light);
  color: var(--color-leaf);
}

.account-form {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.account-input {
  width: 100%;
  padding: 13px 14px;
  font-size: 1rem;
  border: 2px solid rgba(52, 163, 115, 0.2);
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.6);
  color: var(--color-text);
  font-family: inherit;
}

.account-input:focus {
  outline: none;
  border-color: var(--color-leaf-light);
}

.account-submit-button {
  margin-top: 2px;
  padding: 13px;
  font-size: 1rem;
  font-weight: 700;
  color: #ffffff;
  background: linear-gradient(135deg, var(--color-leaf) 0%, var(--color-leaf-light) 100%);
  border: none;
  border-radius: 999px;
  cursor: pointer;
  box-shadow: 0 6px 16px var(--glass-shadow);
  transition: opacity 0.15s ease, transform 0.1s ease;
}

.account-submit-button:active {
  opacity: 0.85;
  transform: scale(0.98);
}

.account-submit-button:disabled {
  opacity: 0.6;
  cursor: default;
  box-shadow: none;
}

.account-preference-actions {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 14px 0 12px;
}

.account-preference-actions .account-submit-button {
  width: 100%;
}

.account-preference-status {
  min-height: 1.1em;
  margin: 0;
  text-align: center;
  color: var(--color-leaf);
  font-size: 0.85rem;
}

.account-preference-status.error {
  color: var(--color-tomato);
}

.account-secondary-button {
  width: 100%;
  padding: 13px;
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--color-leaf);
  background: rgba(52, 163, 115, 0.12);
  border: 2px solid rgba(52, 163, 115, 0.3);
  border-radius: 999px;
  cursor: pointer;
  font-family: inherit;
}

.account-secondary-button:hover {
  background: rgba(52, 163, 115, 0.2);
}

.account-email {
  font-size: 0.95rem;
  font-weight: 600;
  margin: 0 0 12px;
  word-break: break-all;
}

.account-error {
  margin: 0;
  color: var(--color-tomato);
  font-size: 0.85rem;
}

main {
  max-width: 600px;
  margin: 0 auto;
  padding: 20px 16px calc(var(--tab-bar-height) + env(safe-area-inset-bottom, 0px) + 20px);
}

/* Bottom tab bar -- the app's only nav chrome now that there's no header.
   Fixed below everything, frosted-glass like the rest of the UI's panels,
   z-index above .autocomplete-suggestions (30) and .results content. */
.tab-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 45;
  display: flex;
  justify-content: space-around;
  height: var(--tab-bar-height);
  padding-bottom: env(safe-area-inset-bottom, 0px);
  background: rgba(255, 255, 255, 0.7);
  backdrop-filter: blur(20px) saturate(180%);
  -webkit-backdrop-filter: blur(20px) saturate(180%);
  border-top: 1px solid var(--glass-border);
  box-shadow: 0 -4px 14px var(--glass-shadow);
}

.tab-button {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-width: 44px;
  min-height: 44px;
  border: none;
  background: none;
  color: #8a9a90;
  font-family: inherit;
  cursor: pointer;
}

.tab-button.is-active {
  color: var(--color-leaf);
}

.tab-icon-wrap {
  position: relative;
  display: inline-flex;
}

.tab-icon {
  width: 25px;
  height: 25px;
}

.tab-label {
  font-size: 0.7rem;
  font-weight: 600;
}

.tab-panel {
  padding-top: 4px;
}

#panel-search {
  padding-bottom: 88px;
}

/* Search-tab-only header -- same frosted background and height as
   .tab-bar (the footer) so the two read as a matching pair. On phone,
   flush against the top/left/right edges exactly like the footer is
   against the bottom/left/right (the negative margin cancels out
   main/.tab-panel's own inset -- see those rules); restored back to
   inset and rounded at >=768px (see that breakpoint below), where main
   already opens with its own gap above everything, so flush wouldn't
   read the same way there. */
.search-header {
  display: flex;
  align-items: center;
  justify-content: center;
  height: var(--tab-bar-height);
  padding: env(safe-area-inset-top, 0px) 16px 0;
  margin: -24px -16px 14px;
  background: rgba(255, 255, 255, 0.7);
  backdrop-filter: blur(20px) saturate(180%);
  -webkit-backdrop-filter: blur(20px) saturate(180%);
  border-bottom: 1px solid var(--glass-border);
  box-shadow: 0 4px 14px var(--glass-shadow);
}

.search-header-title {
  margin: 0;
  font-family: "Lobster", cursive;
  font-size: 1.7rem;
  font-weight: 400;
  color: var(--color-leaf);
}

.favorites-placeholder {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  margin-top: 40px;
  padding: 36px 20px;
  text-align: center;
  color: var(--color-text);
}

.favorites-placeholder-icon {
  width: 48px;
  height: 48px;
  color: var(--color-leaf-light);
}

.ingredient-form {
  display: flex;
  flex-direction: column;
  gap: 10px;
  background-color: var(--color-card);
  backdrop-filter: blur(20px) saturate(180%);
  -webkit-backdrop-filter: blur(20px) saturate(180%);
  border: 1px solid var(--glass-border);
  border-radius: var(--radius);
  box-shadow: 0 4px 14px var(--glass-shadow);
  padding: 18px;
}

.form-label {
  color: var(--color-text);
  font-weight: 600;
  font-size: 1rem;
  margin-bottom: 4px;
}

.ingredient-input {
  width: 100%;
  padding: 12px 44px 12px 14px;
  font-size: 1rem;
  border: 2px solid rgba(52, 163, 115, 0.2);
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.6);
  color: var(--color-text);
}

.ingredient-input:focus {
  outline: none;
  border-color: var(--color-leaf-light);
}

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

.input-wrapper {
  position: relative;
  flex: 1;
  min-width: 0;
  display: flex;
}

.input-clear-button {
  position: absolute;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 44px;
  height: 44px;
  border: none;
  background: transparent;
  color: #9a9a92;
  font-size: 1.2rem;
  line-height: 1;
  cursor: pointer;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
}

.input-clear-button:hover {
  background: #e6e6e0;
  color: var(--color-text);
}

.input-clear-button:disabled {
  cursor: default;
  color: #c7c7c0;
}

.ingredient-unrecognized-note {
  margin: -2px 0 0;
  padding: 0 4px;
  font-size: 0.8rem;
  color: #9a9a92;
}

.autocomplete-suggestions {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  z-index: 30;
  margin: 0;
  padding: 6px;
  max-height: 250px;
  overflow-y: auto;
  list-style: none;
  background: rgba(255, 255, 255, 0.82);
  backdrop-filter: blur(20px) saturate(180%);
  -webkit-backdrop-filter: blur(20px) saturate(180%);
  border: 1px solid rgba(0, 0, 0, 0.06);
  border-radius: 16px;
  box-shadow: 0 12px 28px var(--glass-shadow), 0 2px 8px rgba(0, 0, 0, 0.06);
  animation: suggestions-in 0.14s ease-out;
}

.autocomplete-suggestions[hidden] {
  display: none;
}

@keyframes suggestions-in {
  from {
    opacity: 0;
    transform: translateY(-4px) scale(0.98);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

.autocomplete-suggestion {
  display: block;
  width: 100%;
  padding: 11px 14px;
  margin: 1px 0;
  border: none;
  background: none;
  text-align: left;
  font-family: inherit;
  font-size: 0.95rem;
  color: var(--color-text);
  border-radius: 11px;
  cursor: pointer;
  transition: background-color 0.1s ease;
}

.autocomplete-suggestion mark {
  background: none;
  color: var(--color-leaf);
  font-weight: 700;
}

.autocomplete-suggestion:hover,
.autocomplete-suggestion.is-active {
  background: var(--color-leaf-pale);
}

.input-language-button {
  flex: 0 0 auto;
  width: 36px;
  height: 36px;
  font-size: 1.2rem;
  line-height: 1;
  background: rgba(159, 230, 192, 0.25);
  border: 2px solid var(--color-leaf-light);
  border-radius: 50%;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}

.input-language-button:hover:not(:disabled) {
  background: rgba(159, 230, 192, 0.4);
}

.language-flag-stack {
  position: relative;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Half-stacked flag pair -- the language currently in the box sits in
   front, nearly full size and just barely off-center toward the lower
   left, while the other side of the known pairing shrinks to a tiny
   flag tucked into the farthest upper-right corner, peeking out from
   behind it -- a visual cue on the button itself that there's a
   translation to switch to, not just a plain icon. No background of
   their own -- see-through, so the behind flag actually shows through
   rather than being covered by an opaque disc. */
.language-flag-front,
.language-flag-behind {
  position: absolute;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  line-height: 1;
}

.language-flag-behind {
  top: 0;
  right: 0;
  width: 16px;
  height: 16px;
  font-size: 0.6rem;
  z-index: 1;
}

.language-flag-front {
  top: 50%;
  left: 50%;
  transform: translate(calc(-50% - 3px), calc(-50% + 3px));
  width: 26px;
  height: 26px;
  font-size: 1rem;
  z-index: 2;
}

/* No known pairing to stack behind it (see main.js's
   updateIngredientLanguageButtonState/updateInputLanguageButtonState,
   which leave this empty in that case) -- falls back to a single
   centered flag at its original size, the same as before this button
   could ever show two. */
.language-flag-behind:empty {
  display: none;
}

.language-flag-behind:empty + .language-flag-front {
  position: static;
  transform: none;
  width: auto;
  height: auto;
  font-size: 1.2rem;
}

/* A box's language button only does anything once its current text is a
   term tagTranslationIndex has a known other-language pairing for (see
   main.js's lookupTagAlternate/toggleIngredientLanguage). Genuinely
   disabled, not just dimmed via a class, so it's inert without needing
   its own click-handler guard, and reads as a native "can't tap this"
   control on iPhone/iPad, where there's no hover state to otherwise hint
   at it. Only the circle grays out, not the whole button via opacity --
   the flag emoji inside stays full color, since it's not itself what's
   "disabled" here. Contrast against this is also what makes the
   *enabled* state -- unchanged, still the full green border/background
   -- actually read as tappable. */
.input-language-button:disabled {
  background: rgba(240, 240, 236, 0.6);
  border-color: #d8d8d2;
  cursor: default;
}

/* Browsers dim *all* of a disabled button's content by default --
   Safari/WebKit via opacity/filter, Chrome by forcing color to its
   built-in GrayText (which some platforms' flag-emoji fallback rendering
   actually respects) -- override all of it back to full strength on the
   flag itself so only the circle behind it reads as inactive. */
.input-language-button:disabled span {
  opacity: 1;
  filter: none;
  color: var(--color-text);
  -webkit-text-fill-color: var(--color-text);
}

@keyframes spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

.spinning {
  display: inline-block;
  animation: spin 1s linear infinite;
}

.site-filter-clear,
.site-picker-toggle {
  min-height: 36px;
  border: 1px solid rgba(52, 163, 115, 0.25);
  background: rgba(255, 255, 255, 0.65);
  color: var(--color-leaf);
  font: inherit;
  cursor: pointer;
}

.site-filter-clear {
  padding: 0 10px;
  border-radius: 999px;
  font-size: 0.82rem;
  font-weight: 700;
  color: var(--color-leaf);
}

.site-picker {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.site-picker-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.site-picker-toggle {
  padding: 0 12px;
  border-radius: 999px;
  font-size: 0.9rem;
  font-weight: 700;
}

.site-picker-toggle::after {
  content: "›";
  display: inline-block;
  margin-left: 8px;
  font-size: 1.6rem;
  font-weight: 700;
  line-height: 1;
  vertical-align: middle;
  transition: transform 0.2s ease;
}

.site-picker-toggle[aria-expanded="true"]::after {
  transform: rotate(90deg);
}

.site-picker-panel {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 12px;
  border-radius: 10px;
}

.site-picker-continent-group {
  display: flex;
  flex-direction: column;
}

.site-picker-continent-row {
  display: flex;
  align-items: center;
  gap: 4px;
}

.site-picker-continent {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 12px;
  border: 1px solid rgba(52, 163, 115, 0.25);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.65);
  font-size: 0.92rem;
  font-weight: 800;
  color: var(--color-leaf);
  cursor: pointer;
}

.site-picker-continent.is-selected,
.site-picker-continent.is-partial {
  border-color: transparent;
  background: linear-gradient(135deg, var(--color-leaf) 0%, var(--color-leaf-light) 100%);
  color: #ffffff;
  box-shadow: 0 4px 14px rgba(52, 163, 115, 0.4);
}

.site-picker-continent-expand {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border: none;
  background: transparent;
  color: var(--color-leaf);
  cursor: pointer;
}

.site-picker-continent-expand::after {
  content: "›";
  display: inline-block;
  font-size: 1.6rem;
  font-weight: 700;
  line-height: 1;
  transition: transform 0.2s ease;
}

.site-picker-continent-expand[aria-expanded="true"]::after {
  transform: rotate(90deg);
}

.site-picker-countries {
  display: none;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 8px;
  margin: 4px 0 0 8px;
  padding-left: 10px;
  border-left: 2px solid rgba(52, 163, 115, 0.15);
}

.site-picker-countries.is-visible {
  display: flex;
}

.site-picker-countries .site-picker-group {
  display: inline-flex;
}

.site-picker-country {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  min-height: 44px;
  margin: 0 0 6px;
  padding: 0 12px;
  border: 1px solid rgba(52, 163, 115, 0.25);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.65);
  font-size: 0.85rem;
  font-weight: 800;
  color: var(--color-leaf);
  cursor: pointer;
}

.site-picker-country.is-selected {
  border-color: transparent;
  background: linear-gradient(135deg, var(--color-leaf) 0%, var(--color-leaf-light) 100%);
  color: #ffffff;
  box-shadow: 0 4px 14px rgba(52, 163, 115, 0.4);
}

.site-picker-countries .site-picker-country {
  min-height: 38px;
  margin: 0;
  padding: 0 10px;
  font-size: 0.82rem;
  white-space: nowrap;
}

.search-button {
  min-width: 154px;
  min-height: 52px;
  padding: 0 22px;
  font-size: 1.05rem;
  font-weight: 700;
  color: #ffffff;
  background: linear-gradient(135deg, #ff5500 0%, #ff8c00 100%);
  border: none;
  border-radius: 999px;
  cursor: pointer;
  box-shadow: 0 6px 18px rgba(255, 85, 0, 0.45);
  transition: background 0.2s ease, box-shadow 0.2s ease, opacity 0.15s ease, transform 0.1s ease;
}

.search-action-bar {
  position: fixed;
  left: 16px;
  right: 16px;
  bottom: calc(var(--tab-bar-height) + env(safe-area-inset-bottom, 0px) + 12px);
  z-index: 44;
  display: flex;
  justify-content: flex-end;
  pointer-events: none;
}

.search-action-bar .search-button {
  pointer-events: auto;
}

.search-button:active {
  opacity: 0.85;
  transform: scale(0.98);
}

.search-button:disabled {
  background: #c7c7cc;
  cursor: default;
  box-shadow: none;
}

/* Notification-style badge -- how many of the 5 ingredient boxes have
   text in them, plus any active site filters (see main.js's
   updateSearchButtonBadge). Sits on the Search tab's icon (see
   .tab-icon-wrap, its positioned ancestor) so it stays visible regardless
   of which tab is actually active. */
.search-button-badge {
  position: absolute;
  top: -4px;
  right: -8px;
  min-width: 20px;
  height: 20px;
  padding: 0 5px;
  border-radius: 999px;
  background: #e53935;
  color: #ffffff;
  font-size: 0.7rem;
  font-weight: 700;
  line-height: 20px;
  text-align: center;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
}

/* Re-played on every search-input change (see main.js's
   pulseSearchButtonBadge), even ones that don't change the displayed
   count -- e.g. swapping one ingredient for another while already at the
   same total -- so the badge is the one thing that reliably draws the
   eye back to itself whenever something in the (possibly off-tab) form
   changed. */
@keyframes badge-pulse {
  0% {
    transform: scale(1);
  }
  35% {
    transform: scale(1.45);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), 0 0 0 6px rgba(229, 57, 53, 0.25);
  }
  70% {
    transform: scale(0.95);
  }
  100% {
    transform: scale(1);
  }
}

.search-button-badge.pulse {
  animation: badge-pulse 0.5s ease-out;
}

.status-message {
  text-align: center;
  margin: 18px 0;
  font-size: 0.95rem;
  color: var(--color-leaf);
}

.status-message.error {
  color: var(--color-tomato);
}

.results {
  display: flex;
  flex-direction: column;
  gap: 16px;
  margin-top: 18px;
}

.results-sentinel {
  height: 1px;
}

.recipe-card {
  position: relative;
  background: var(--color-card);
  backdrop-filter: blur(20px) saturate(180%);
  -webkit-backdrop-filter: blur(20px) saturate(180%);
  border: 1px solid var(--glass-border);
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: 0 4px 14px var(--glass-shadow);
}

/* Overlaid top-right on every card (over the photo when there is one) --
   see main.js's buildFavoriteButton, shared by the Recipes tab's search
   results and the Favorites tab. 44x44 hit target per Apple HIG even
   though the visible heart is smaller, same reasoning as
   .input-clear-button. */
.recipe-favorite-button {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 44px;
  height: 44px;
  border: none;
  background: rgba(0, 0, 0, 0.35);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  color: #fff;
  cursor: pointer;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform 0.1s ease;
}

.recipe-favorite-button svg {
  width: 22px;
  height: 22px;
  filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.25));
}

.recipe-favorite-button:active {
  transform: scale(0.9);
}

.recipe-favorite-button.is-favorited svg path {
  fill: var(--color-tomato);
  stroke: var(--color-tomato);
}

.recipe-card img {
  width: 100%;
  height: 180px;
  object-fit: cover;
  display: block;
}

.recipe-image-button {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
  text-align: inherit;
}

.recipe-card-body {
  padding: 14px 16px;
}

.recipe-card h2 {
  margin: 0 0 2px;
  font-size: 1.15rem;
}

.recipe-title-button {
  display: inline-block;
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  font-weight: inherit;
  text-align: inherit;
  cursor: pointer;
}

.recipe-rating {
  margin: 0 0 4px;
  font-size: 0.85rem;
  font-weight: 700;
  color: #d4942c;
}

/* Holds the source (flag+sourceSite) tag and the cuisine text side by
   side, source first -- see main.js's buildSourceRow. margin-top (not
   just relying on the title's own margin-bottom) keeps this same small
   gap whether or not a rating line falls in between. */
.recipe-source-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 6px;
  margin-bottom: 10px;
}

.recipe-cuisine {
  font-size: 0.8rem;
  font-weight: 600;
  color: #9a9a92;
}

.recipe-tags {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  margin-bottom: 10px;
}

.recipe-tag {
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--color-leaf);
  background: var(--color-leaf-pale);
  padding: 3px 9px;
  border-radius: 999px;
}

.recipe-tag-button {
  font-size: 0.9rem;
  font-weight: 600;
  font-family: inherit;
  color: var(--color-leaf);
  background: var(--color-leaf-pale);
  padding: 2.5px 8.5px;
  border-radius: 999px;
  border: 1.5px solid var(--color-leaf-light);
  cursor: pointer;
  box-shadow: 0 2px 3px rgba(52, 163, 115, 0.2);
  transition: box-shadow 0.1s ease, transform 0.1s ease;
}

.recipe-tag-button:hover {
  background: rgba(159, 230, 192, 0.45);
}

/* Shadow flattens and the pill nudges down a touch on press -- reads as
   the chip actually depressing, not just changing color. */
.recipe-tag-button:active {
  background: rgba(159, 230, 192, 0.6);
  box-shadow: 0 0.5px 1px rgba(52, 163, 115, 0.2);
  transform: translateY(1px);
}

/* Recipe-level language toggle (see main.js's
   buildRecipeLanguageController) -- both flags sit side by side with the
   selected language highlighted. In "both" mode, both flags are active. */
.recipe-tags-toggle {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 2px 5px;
  background: rgba(255, 255, 255, 0.6);
  border: 1.5px solid var(--color-leaf-light);
  border-radius: 999px;
  cursor: pointer;
  /* Lives in .recipe-source-row (a flex row) alongside the site tag --
     this pushes it to the row's right edge regardless of how many tags
     precede it. */
  margin-left: auto;
}

.recipe-tags-toggle:hover {
  background: rgba(159, 230, 192, 0.3);
}

.recipe-tags-toggle:disabled {
  cursor: wait;
  opacity: 0.8;
}

.recipe-tags-toggle-flag {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  font-size: 0.9rem;
  background: #e2e2dc;
  transition: background 0.2s ease;
}

.recipe-tags-toggle-flag.active {
  background: var(--color-leaf-light);
}

.recipe-tags-toggle-arrow {
  font-size: 0.8rem;
  color: var(--color-leaf);
  line-height: 1;
}

.recipe-tags-toggle-spinner {
  width: 13px;
  height: 13px;
  border: 2px solid rgba(52, 163, 115, 0.25);
  border-top-color: var(--color-leaf);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

.recipe-tag-original-inline {
  color: #7d8a83;
  font-size: 0.85em;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

/* Ghost copy of a clicked tag/title, flown to the search button (see
   main.js's flyToSearchButton) as visual confirmation that the click
   actually added something -- the one piece of feedback for this that
   doesn't depend on an input box being on screen, since the form itself
   may be collapsed or scrolled out of view at the time. position: fixed
   (not absolute) so its left/top, set from getBoundingClientRect(),
   don't need any scroll-offset math. JS sets the transform/opacity end
   state on the next frame so this transition actually has a starting
   point to animate from. */
.flying-tag {
  position: fixed;
  display: flex;
  align-items: center;
  justify-content: center;
  max-width: 220px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--color-leaf);
  background: var(--color-leaf-pale);
  border: 1.5px solid var(--color-leaf-light);
  border-radius: 999px;
  padding: 2.5px 8.5px;
  pointer-events: none;
  z-index: 200;
  transition: transform 0.5s cubic-bezier(0.4, 0, 0.7, 1), opacity 0.5s ease-in;
}

.recipe-card details {
  margin-top: 8px;
}

.recipe-card summary {
  cursor: pointer;
  font-weight: 600;
  color: var(--color-leaf);
  font-size: 0.9rem;
}

.recipe-card .detail-summary-with-action {
  display: list-item;
}

.unit-convert-button {
  margin-left: 10px;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--color-tomato);
  cursor: pointer;
  display: inline;
  vertical-align: middle;
  font: inherit;
  font-size: 0.86em;
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.unit-convert-button:hover {
  color: var(--color-leaf);
}

.unit-convert-button:disabled {
  cursor: progress;
  opacity: 0.72;
}

.unit-convert-button.is-loading {
  text-decoration-style: dotted;
}

.recipe-card ul,
.recipe-card ol {
  margin: 8px 0 0;
  padding-left: 20px;
  font-size: 0.9rem;
  line-height: 1.5;
}

.line-original {
  display: none;
  margin-top: 2px;
  font-size: 0.85em;
  font-style: italic;
  color: #9a9a92;
}

.line-localized-unit {
  display: inline;
  margin-left: 4px;
  font-size: 0.86em;
  font-weight: 600;
  color: var(--color-leaf);
}

.show-original-lines .line-original {
  display: block;
}

.recipe-link {
  display: inline-block;
  margin-top: 10px;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--color-tomato);
  text-decoration: none;
}

.recipe-title-current {
  display: inline;
}

.recipe-title-original {
  font-weight: 500;
}

/* Full-screen single-recipe page (see main.js's openRecipeDetail) --
   fixed above absolutely everything, including the tab bar (z-index 45),
   so it reads as its own native screen rather than another panel sharing
   space with the rest of the app's chrome. Same backdrop mesh as body so
   it still feels like part of the same app once it's covering it. */
.recipe-overlay {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  flex-direction: column;
  background-color: var(--color-cream);
  background-image:
    radial-gradient(circle at 12% 8%, rgba(159, 230, 192, 0.45) 0%, transparent 55%),
    radial-gradient(circle at 88% 14%, rgba(255, 184, 153, 0.28) 0%, transparent 50%),
    radial-gradient(circle at 50% 100%, rgba(159, 230, 192, 0.32) 0%, transparent 60%);
}

/* Same frosted-glass treatment as .search-header/.tab-bar, so this reads
   as one more piece of the same app's chrome rather than a different
   surface bolted on. */
.recipe-overlay-header {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  height: var(--tab-bar-height);
  padding: env(safe-area-inset-top, 0px) 8px 0;
  background: rgba(255, 255, 255, 0.7);
  backdrop-filter: blur(20px) saturate(180%);
  -webkit-backdrop-filter: blur(20px) saturate(180%);
  border-bottom: 1px solid var(--glass-border);
  box-shadow: 0 4px 14px var(--glass-shadow);
}

.recipe-overlay-back {
  width: 44px;
  height: 44px;
  border: none;
  background: transparent;
  color: var(--color-leaf);
  cursor: pointer;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
}

.recipe-overlay-back:hover {
  background: rgba(159, 230, 192, 0.25);
}

.recipe-overlay-back svg {
  width: 24px;
  height: 24px;
}

.recipe-overlay-body {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 16px 16px calc(env(safe-area-inset-bottom, 0px) + 24px);
}

/* Locks the background page in place while the overlay is open -- same
   reasoning as body.scroll-locked (see setActiveTab), just independent of
   it: the overlay scrolls on its own (.recipe-overlay-body), so whichever
   tab is "active" underneath never needs to, regardless of that tab's own
   scroll-locked state. */
body.recipe-overlay-active {
  position: fixed;
  inset: 0;
  overflow: hidden;
}

/* A taller hero image for the one recipe a full screen is dedicated to,
   versus a thumbnail competing for space with a dozen other cards in a
   results list. */
.recipe-card--detail img {
  height: 240px;
}

/* Two independently-scrolling columns instead of one tall single-column
   card. The overlay's own height is fixed (the viewport minus its header
   -- see .recipe-overlay's flex column layout), so centering a single
   block of photo+title+tags+Ingredients+Instructions inside it just
   stretched that block to the same fixed height (flex's own default
   align-items: stretch) and then clipped everything past it -- .recipe-
   card's overflow: hidden is normally just for its rounded corners, but
   with no inner scroll container of its own, that's also what was
   clipping Instructions clean off, with no way to reach it. Turning off
   the body's own scroll and letting .recipe-detail-media/-content (see
   main.js's renderRecipe "detail" mode) scroll independently instead is
   what actually fixes that, and matches a real two-pane app layout
   rather than a phone card just stretched wider. */
@media (min-width: 768px) {
  .recipe-overlay-body {
    display: flex;
    justify-content: center;
    overflow: hidden;
  }

  .recipe-overlay-body > .recipe-card {
    width: 100%;
    max-width: 900px;
    height: 100%;
    display: flex;
  }

  .recipe-detail-layout {
    display: flex;
    width: 100%;
    /* Flex items default to a min-height based on their content, which
       would otherwise let this (and the two columns below) grow taller
       than the card instead of respecting it -- exactly what's needed
       for "stretch to fill, then scroll the overflow" to actually work. */
    min-height: 0;
  }

  .recipe-detail-media,
  .recipe-detail-content {
    flex: 1 1 50%;
    min-width: 0;
    min-height: 0;
    overflow-y: auto;
  }

  /* Photo sits flush at the top of its own column (see .recipe-card img),
     so the divider/padding only need to account for the header content
     below it -- .recipe-card-body already carries its own padding. */
  .recipe-detail-media {
    border-right: 1px solid var(--glass-border);
  }

  .recipe-detail-content {
    padding: 14px 16px;
  }
}

@media (min-width: 1100px) {
  .recipe-overlay-body > .recipe-card {
    max-width: 1080px;
  }

  .recipe-card--detail img {
    height: 300px;
  }
}

/* Tablet/laptop: the bottom tab bar becomes a persistent left nav rail
   (see .tab-bar below), and main expands into a real two-pane layout --
   the Search panel pinned as a sidebar next to the Recipes panel's result
   grid, both shown at once (see main.js's setActiveTab merging the two at
   this width), with Favorites/Account instead taking the full content
   width when either is selected on its own. 768px is iPad-portrait width,
   the narrowest target this is meant to cover. Below it, everything above
   keeps its mobile single-column rules untouched. */
@media (min-width: 768px) {
  body {
    padding-left: var(--tab-rail-width);
    overflow: hidden;
  }

  .tab-bar {
    top: 0;
    left: 0;
    right: auto;
    bottom: 0;
    width: var(--tab-rail-width);
    height: auto;
    flex-direction: column;
    justify-content: flex-start;
    gap: 12px;
    padding: 32px 0 env(safe-area-inset-bottom, 0px);
    border-top: none;
    border-right: 1px solid var(--glass-border);
    box-shadow: 4px 0 14px var(--glass-shadow);
  }

  .tab-button {
    flex: 0 0 auto;
  }

  /* Search and Recipes are shown merged side by side at this width (see
     main.js's setActiveTab) rather than as separate panels, so a nav
     entry for "Recipes" would just be a second way to reach the same
     screen Search already shows -- drop it rather than leave a redundant
     button in the rail. */
  .tab-button[data-tab="recipes"] {
    display: none;
  }

  main {
    display: flex;
    align-items: stretch;
    gap: 28px;
    height: 100dvh;
    max-width: 1600px;
    margin: 0 auto;
    padding: 24px;
    overflow: hidden;
  }

  /* Search stays a fixed-width sidebar, same as the old .hero; every
     other panel (Recipes alongside it, or Favorites/Account filling
     Search's place when shown alone) takes the rest of the width. */
  #panel-search {
    display: flex;
    flex-direction: column;
    flex: 0 0 320px;
    max-width: 320px;
    min-height: 0;
    overflow-y: auto;
    padding-bottom: 0;
  }

  .search-action-bar {
    position: sticky;
    left: auto;
    right: auto;
    bottom: 0;
    margin-top: auto;
    padding-top: 12px;
    background: linear-gradient(to bottom, rgba(247, 251, 249, 0), var(--color-cream) 55%);
  }

  /* Undoes the phone-only flush treatment -- back to inset and rounded,
     matching every other card at this width. */
  .search-header {
    margin: 0 0 14px;
    padding: 0 16px;
    border-radius: var(--radius);
    border-bottom: none;
  }

  .tab-panel:not(#panel-search) {
    flex: 1;
    /* Without this, a long recipe title/tag list can force the grid
       track wider than the space actually available next to the
       sidebar -- a flex item's min-width defaults to its content's, not
       0, so it won't otherwise shrink below that. */
    min-width: 0;
    min-height: 0;
    overflow-y: auto;
  }

  /* auto-fill (not a fixed column count) is what makes the tile count
     responsive -- the grid just keeps fitting as many 260px+ tracks as
     the available width allows, growing each one to fill any leftover
     space, rather than the page picking a column count up front. */
  .results {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
    gap: 20px;
    margin-top: 20px;
  }

  /* Account/Favorites get the full content width above, but a sign-in
     form or empty state stretched edge-to-edge across a wide screen
     reads as a web form, not an app screen -- cap and center the actual
     card within that width instead. */
  #panel-account .panel-card,
  #panel-favorites .panel-card {
    max-width: 480px;
    margin: 0 auto;
  }
}

/* Laptop/desktop: more width to spare, so the sidebar and tile tracks
   both get a bit roomier rather than just adding more same-size tiles. */
@media (min-width: 1100px) {
  #panel-search {
    flex-basis: 360px;
    max-width: 360px;
  }

  .results {
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  }
}
