@import url('https://fonts.googleapis.com/css2?family=Alegreya:ital,wght@0,400..900;1,400..900&display=swap');

/*
    App-wide root styling: tokens, themes, the page shell and the shared
    `.input` / `.block` patterns. Backgrounds are named by role, and the
    rules for which sits on which are in ARCHITECTURE.md's "Page layering
    and depth".
*/

:root {
  color-scheme: light dark;

  --font-size: 1.3rem;
  --font: "Alegreya", serif;

  /* The page: pastel green. Content sits on it in surface blocks. */
  --canvas: #dcead9;
  /* Cards, list items, the nav: soft white. */
  --surface: #fbfaf6;
  /* Inputs, chips and wells inside a surface. Never the canvas color. */
  --inset: #ebf2e7;
  /* Dialogs and sheets. */
  --overlay: #fbfaf6;
  /* Behind an overlay. */
  --scrim: rgb(20 28 18 / 0.45);
  /* Body text: warm dark gray. */
  --text: #433f3c;
  /* Hairlines and input edges. */
  --border: color-mix(in srgb, var(--text) 16%, transparent);
  /* Accent: the red from icon.svg's berry. */
  --accent: #c70049;
  /* Text and icons on an accent fill. */
  --on-accent: #fbfaf6;
  /* Negative/reverse actions (skip, restore): fjord blue, from the
     "Add to shopping list" design. */
  --negative: #1d4a66;
  --on-negative: #fbfaf6;
  /* Error text and rings. An alias, so errors can get their own color by
     changing this line; it can't be the accent, which is the focus ring. */
  --error: var(--negative);

  /* A shadow means height: resting surfaces, lifted (floating), dragged
     (picked up by the user), overlays. The `--drop-*` forms are for
     `filter`, on the unclipped parent of a `clip-path` card, which would
     cut off a box-shadow. A dragged item also grows by `--dragged-scale`
     (as the CSS `scale` property, so it adds to the drag's transform). */
  --shadow-rest: 0 1px 2px rgb(31 48 27 / 0.10), 0 2px 8px rgb(31 48 27 / 0.06);
  --shadow-lifted: 0 10px 24px rgb(31 48 27 / 0.22);
  --shadow-overlay: 0 20px 48px rgb(20 28 18 / 0.32);
  --drop-rest: drop-shadow(0 1px 2px rgb(31 48 27 / 0.10)) drop-shadow(0 2px 8px rgb(31 48 27 / 0.06));
  --drop-lifted: drop-shadow(0 10px 24px rgb(31 48 27 / 0.22));
  --shadow-dragged: 0 18px 36px rgb(31 48 27 / 0.32);
  --drop-dragged: drop-shadow(0 18px 36px rgb(31 48 27 / 0.32));
  --dragged-scale: 1.02;

  /* Inputs and chips, cards, dialogs and sheets, buttons. */
  --radius-s: 0.4rem;
  --radius-m: 0.75rem;
  --radius-l: 1.25rem;
  --radius-pill: 999px;
  /* Height of the running timers strip, 0 while it isn't shown. */
  --timer-strip: 0px;
}

/*
Theme: `prefers-color-scheme` decides by default. An explicit choice from
the Settings page (client/src/theme.rs) stamps `data-theme="light"` /
`"dark"` on <html> and wins in both directions — the media-query block is
guarded to stay out of the way once `data-theme="light"` is set, and the
`[data-theme="dark"]` block applies dark regardless of the OS setting.
The two dark blocks must stay identical.

In dark mode higher is lighter: canvas < surface < overlay, and inset sinks
below its surface. The accent is lighter than in light mode (with dark
text on it), because the berry red is too dark for icons and text there.
*/
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --canvas: #0e130d;
    --surface: #192217;
    --inset: #111810;
    --overlay: #232e20;
    --scrim: rgb(0 0 0 / 0.6);
    --text: #62ac52;
    --border: color-mix(in srgb, var(--text) 20%, transparent);
    --accent: #f0648f;
    --on-accent: #0e130d;
    --negative: #7fb3dd;
    --on-negative: #0e130d;
    --shadow-rest: 0 1px 2px rgb(0 0 0 / 0.5);
    --shadow-lifted: 0 10px 24px rgb(0 0 0 / 0.6);
    --shadow-overlay: 0 20px 48px rgb(0 0 0 / 0.7);
    --drop-rest: drop-shadow(0 1px 2px rgb(0 0 0 / 0.5));
    --drop-lifted: drop-shadow(0 10px 24px rgb(0 0 0 / 0.6));
    --shadow-dragged: 0 18px 36px rgb(0 0 0 / 0.75);
    --drop-dragged: drop-shadow(0 18px 36px rgb(0 0 0 / 0.75));
  }
}

:root[data-theme="dark"] {
  --canvas: #0e130d;
  --surface: #192217;
  --inset: #111810;
  --overlay: #232e20;
  --scrim: rgb(0 0 0 / 0.6);
  --text: #62ac52;
  --border: color-mix(in srgb, var(--text) 20%, transparent);
  --accent: #f0648f;
  --on-accent: #0e130d;
  --negative: #7fb3dd;
  --on-negative: #0e130d;
  --shadow-rest: 0 1px 2px rgb(0 0 0 / 0.5);
  --shadow-lifted: 0 10px 24px rgb(0 0 0 / 0.6);
  --shadow-overlay: 0 20px 48px rgb(0 0 0 / 0.7);
  --drop-rest: drop-shadow(0 1px 2px rgb(0 0 0 / 0.5));
  --drop-lifted: drop-shadow(0 10px 24px rgb(0 0 0 / 0.6));
  --shadow-dragged: 0 18px 36px rgb(0 0 0 / 0.75);
  --drop-dragged: drop-shadow(0 18px 36px rgb(0 0 0 / 0.75));
}

:root[data-theme="light"] {
  color-scheme: light;
}

:root[data-theme="dark"] {
  color-scheme: dark;
}

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

body {
  min-height: 100vh;
  margin: 0;
  padding: 0;
  padding-bottom: 5rem;
  background-color: var(--canvas);
  color: var(--text);
  font-family: var(--font);
  font-size: var(--font-size);
  font-optical-sizing: auto;
  font-weight: 400;
  font-style: normal;
  display: flex;
  flex-direction: column;
  align-items: center;
}

/*
Clearance for the fixed global nav (client/src/components/nav.rs / nav.css)
so page content never sits under it. These widths/heights must match the
rail width / bar height set there for each breakpoint.
*/
@media (max-width: 480px) {
  /* Bottom bar, and the running timers strip above it while it shows
     (components/running_timers.rs). Everything fixed to the bottom of the
     page adds `--timer-strip` to its offset. */
  :root.has-timer-strip {
    --timer-strip: 3.5rem;
  }

  body {
    padding-bottom: calc(6rem + var(--timer-strip));
  }
}

@media (min-width: 481px) and (max-width: 1280px) {
  /* Icon-only left rail. */
  body {
    padding-left: 4.5rem;
  }
}

@media (min-width: 1281px) {
  /* Icon+label left rail. */
  body {
    padding-left: 12rem;
  }
}

h1,
h2,
h3 {
  font-weight: 700;
}

.logo {
  display: block;
  width: 3.5rem;
  height: 3.5rem;
}

/* Shared pill/toggle look for the Settings page's language and theme
   switchers (client/src/components/locale_switcher.rs, theme_switcher.rs). */
.pref-switcher {
  display: inline-flex;
  background-color: var(--inset);
  border-radius: var(--radius-pill);
  padding: 0.25rem;
  gap: 0.15rem;
}

.pref-switcher button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: none;
  color: var(--text);
  font: inherit;
  font-size: 0.85rem;
  border-radius: var(--radius-pill);
  padding: 0.3rem 0.75rem;
  cursor: pointer;
}

.pref-switcher button.active {
  background-color: var(--accent);
  color: var(--on-accent);
}

/* The page container: width and padding only. The page itself is the
   canvas, and content sits on it in surface blocks. */
.card {
  /* Without an explicit width, a short-content page (e.g. Settings) shrinks
     to fit its content and recenters — visibly "jumping" as text length
     changes between languages. Filling the available width (still capped
     by `max-width` per breakpoint below) keeps it stable regardless of
     content length. */
  width: 100%;
  box-sizing: border-box;
  padding-bottom: 5rem;
}

/* A surface block on the canvas. */
.block {
  background-color: var(--surface);
  border-radius: var(--radius-m);
  box-shadow: var(--shadow-rest);
  padding: 1rem 1.25rem;
}

/* Toggle chips (the recipe editor's units and book choices, BookChips).
   Unselected chips look like the shared `.input`; selected ones are accent
   pills. */
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.chip {
  min-height: 44px;
  padding: 0 1rem;
  border-radius: var(--radius-pill);
  border: 0;
  background-color: var(--inset);
  box-shadow: inset 0 0 0 1px var(--border);
  color: var(--text);
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font: inherit;
  cursor: pointer;
}

.chip.chip-on {
  background-color: var(--accent);
  box-shadow: none;
  color: var(--on-accent);
  font-weight: 700;
}

.chip.chip-invalid {
  box-shadow: inset 0 0 0 2px var(--error);
}

/* Page heading of the book and recipe views (ARCHITECTURE.md "Page
   actions"): the title takes the free width and icon actions (Edit, later
   Share) sit at the right end of the row. The labelled main action (Borrow
   book, Add to library) follows in its own row, before the description. */
.page-heading {
  display: flex;
  align-items: center;
  gap: 0.25rem;
}

.page-heading-title {
  flex: 1;
  min-width: 0;
}

.page-main-action {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin: 0.5rem 0 0;
}

/* The shared buttons (ARCHITECTURE.md "Buttons and dialogs"; design:
   the "Buttons and dialogs" canvas linked from the Design consistency
   feature). Every action button is one of these; pages only add layout
   (width, margins), never their own colors or sizes. They style links
   (`<A>`) the same way. */
.button-primary,
.button-secondary,
.button-text,
.button-danger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  min-height: 44px;
  padding: 0 1.1rem;
  box-sizing: border-box;
  border: 0;
  border-radius: var(--radius-pill);
  background: none;
  font: inherit;
  font-size: 0.85em;
  font-weight: 700;
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
}

/* The main action: one per view or dialog. */
.button-primary {
  background-color: var(--accent);
  color: var(--on-accent);
}

/* A reverse action: discard, remove, cancel a timer, log out. */
.button-secondary {
  box-shadow: inset 0 0 0 2px var(--negative);
  color: var(--negative);
}

/* A way out that changes nothing: Cancel, Keep editing. */
.button-text {
  color: var(--text);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}

/* Confirms something that can't be undone. */
.button-danger {
  background-color: var(--negative);
  color: var(--on-negative);
}

:is(.button-primary, .button-danger):hover:not(:disabled) {
  opacity: 0.9;
}

:is(.button-secondary, .button-text):hover:not(:disabled) {
  background-color: color-mix(in srgb, var(--text) 8%, transparent);
}

:is(.button-primary, .button-secondary, .button-text, .button-danger):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

:is(.button-primary, .button-secondary, .button-text, .button-danger):disabled {
  cursor: default;
  opacity: 0.4;
}

/* Every icon-only button: round, 44px, a soft round fill on hover. */
.icon-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  flex-shrink: 0;
  padding: 0;
  border: 0;
  border-radius: var(--radius-pill);
  background: none;
  color: var(--text);
  cursor: pointer;
}

.icon-button:hover:not(:disabled) {
  background-color: color-mix(in srgb, var(--text) 10%, transparent);
}

.icon-button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 0;
}

.icon-button:disabled {
  cursor: default;
  opacity: 0.4;
}

/* What an empty list says instead ("No recipes found", "Nothing left to
   buy"): one muted, centered line. */
.empty-hint {
  margin: 0;
  text-align: center;
  font-size: 0.85em;
  font-style: italic;
  opacity: 0.75;
}

/* The quick-scale pill bar (components/quick_scale.rs) over a focused
   amount field: centered on it, moved sideways to stay on the screen
   (`--quick-scale-shift`), lifted like anything floating over the page. */
.quick-scale-anchor {
  position: relative;
  display: inline-flex;
}

.quick-scale {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 10px);
  translate: calc(-50% + var(--quick-scale-shift, 0px)) 0;
  z-index: 20;
  display: flex;
  gap: 2px;
  padding: 4px;
  border-radius: var(--radius-pill);
  background-color: var(--overlay);
  box-shadow: var(--shadow-lifted);
  white-space: nowrap;
  animation: quick-scale-in 140ms ease-out both;
}

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

@media (prefers-reduced-motion: reduce) {
  .quick-scale {
    animation: none;
  }
}

.quick-scale-pill {
  min-width: 44px;
  height: 40px;
  padding: 0 10px;
  border: 0;
  border-radius: var(--radius-pill);
  background: none;
  color: var(--accent);
  font: inherit;
  font-size: 0.9em;
  font-weight: 700;
  font-style: normal;
  cursor: pointer;
}

.quick-scale-pill:hover:not(:disabled) {
  background-color: var(--inset);
}

.quick-scale-pill:disabled {
  cursor: default;
  opacity: 0.4;
}

/* The one input style: inset fill and hairline edge, an accent ring on
   focus and an error ring when `aria-invalid="true"`. */
.input {
  font: inherit;
  color: var(--text);
  background-color: var(--inset);
  border: 0;
  border-radius: var(--radius-s);
  box-shadow: inset 0 0 0 1px var(--border);
  min-height: 44px;
  padding: 0.5rem 0.75rem;
  box-sizing: border-box;
}

.input:focus,
.input:focus-visible {
  outline: none;
  box-shadow: inset 0 0 0 2px var(--accent);
}

.input[aria-invalid="true"] {
  box-shadow: inset 0 0 0 2px var(--error);
}

/*
Responsive card width/padding, matching the recipe page's breakpoints:
mobile portrait, mobile landscape, tablet portrait, tablet landscape,
laptop and desktop.
*/
@media (min-width: 320px) and (max-width: 480px) {
  :root {
    --font-size: 1.05rem;
  }

  .card {
    max-width: 100%;
    padding-left: 1.25rem;
    padding-right: 1.25rem;
  }
}

@media (min-width: 481px) and (max-width: 767px) {
  :root {
    --font-size: 1.1rem;
  }

  .card {
    max-width: 700px;
    padding-left: 2rem;
    padding-right: 2rem;
    padding-top: 1rem;
  }
}

@media (min-width: 768px) and (max-width: 1024px) {
  .card {
    max-width: 700px;
    padding-left: 6rem;
    padding-right: 6rem;
  }
}

@media (min-width: 768px) and (max-width: 1024px) and (orientation: landscape) {
  .card {
    max-width: 900px;
    padding-left: 4rem;
    padding-right: 4rem;
  }
}

@media (min-width: 1025px) and (max-width: 1280px) {
  .card {
    max-width: 1000px;
    padding-left: 8rem;
    padding-right: 8rem;
  }
}

@media (min-width: 1281px) {
  :root {
    --font-size: 1.35rem;
  }

  .card {
    max-width: 1200px;
    padding-left: 10rem;
    padding-right: 10rem;
  }
}
