:root {
  /* Background unified to white per Charley's request — no more cream/tan
     page tone. --paper-alt stays an almost-imperceptible near-white, used
     only for subtle hover/placeholder fills, not as a competing background. */
  --paper: #ffffff;
  --paper-alt: #fafafa;
  --ink: #3a2e23;
  --ink-soft: #6b5c4c;
  /* Exact numeric match to the Opensauce logo's orange (#ea5413 / rgb
     234,84,19), per Charley's explicit pixel-sampled value — not an
     eyeballed approximation. */
  --accent: #ea5413;
  --accent-soft: #fcccb8;
  --line: #e3d7c3;
  --card-bg: #ffffff;
  --danger: #a3432f;
  --radius: 10px;
  font-family: "Hiragino Mincho ProN", "Yu Mincho", "Georgia", serif;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: "Hiragino Kaku Gothic ProN", "Yu Gothic", -apple-system, BlinkMacSystemFont, sans-serif;
  line-height: 1.6;
}

.hidden { display: none !important; }

.app-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 20px 28px;
  background: var(--card-bg);
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  z-index: 10;
}

.logo-home-btn {
  display: inline-block;
  border: none;
  background: none;
  padding: 0;
  margin: 0;
  cursor: pointer;
  line-height: 0;
}

.header-logo {
  /* 1.8x the original 44px (2026 rebrand: "Opensauce THE RECIPE SINCE
     2020" logo, wider banner shape) — see mobile override below for the
     matching 1.8x bump on the 32px breakpoint value. width: auto keeps
     the new file's native aspect ratio, no stretching. */
  height: 79.2px;
  width: auto;
  display: block;
}

.site-footer {
  text-align: center;
  padding: 24px 16px 32px;
  color: var(--ink-soft);
  font-size: 0.8rem;
  border-top: 1px solid var(--line);
  background: var(--paper);
}
.site-footer p { margin: 0; }
.footer-admin-link { color: var(--ink-soft); text-decoration: underline; }
.footer-admin-link:hover { color: var(--accent); }

main {
  max-width: 1080px;
  margin: 0 auto;
  padding: 24px 28px 80px;
}

.btn {
  font-size: 0.92rem;
  padding: 9px 18px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--card-bg);
  color: var(--ink);
  cursor: pointer;
  transition: all 0.15s ease;
}
.btn:hover { background: var(--paper-alt); }
.btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}
.btn-primary:hover { background: #bb430f; }
.btn-secondary { background: var(--paper-alt); }
.btn-danger {
  background: #fff;
  border-color: var(--danger);
  color: var(--danger);
}
.btn-danger:hover { background: #fbeae6; }
.btn-link {
  border: none;
  background: none;
  color: var(--accent);
  padding: 6px 0;
  margin-bottom: 12px;
  font-size: 0.95rem;
}
.file-label { display: inline-flex; align-items: center; }

/* Brand manifesto block — sits inside #view-grid only (below the logo,
   above the search/category toolbar), so it hides automatically when the
   SPA switches to the add/detail views via showView() in app.js. Serif
   family matches the existing "story" text convention elsewhere in the
   app (see .story-textarea) so this reads as the same editorial voice.
   max-width keeps line length readable on wide desktop screens instead of
   stretching the manifesto edge-to-edge across main's 1080px column. */
.brand-mission {
  max-width: 640px;
  margin: 4px auto 28px;
  text-align: left;
}
.brand-mission-tagline {
  font-family: "Hiragino Mincho ProN", "Yu Mincho", Georgia, serif;
  font-size: 1.3rem;
  font-weight: 700;
  color: var(--accent);
  line-height: 1.6;
  margin: 0 0 18px;
}
.brand-mission-body {
  font-family: "Hiragino Mincho ProN", "Yu Mincho", Georgia, serif;
  font-size: 0.92rem;
  color: var(--ink-soft);
  line-height: 1.95;
  margin: 0;
}

.toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  align-items: center;
  margin-bottom: 20px;
}

#search-input {
  flex: 0 1 240px;
  min-width: 160px;
  padding: 10px 16px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--card-bg);
  font-size: 0.95rem;
}

.tabs {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  /* Reserves one tab-row's worth of height immediately, before the
     categories fetch resolves and buttons actually render (see
     loadCategories() in app.js) — otherwise this div sits at 0 height/width
     right next to the search box for a beat, then suddenly grows into 9
     buttons and gets pushed onto a new line, visibly snapping the whole
     toolbar sideways. min-height alone isn't enough on narrow viewports
     (see the mobile media query below, which removes the width-dependent
     wrap entirely). */
  min-height: 37px;
}
.tab-btn {
  padding: 7px 14px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--card-bg);
  cursor: pointer;
  font-size: 0.85rem;
  color: var(--ink-soft);
}
.tab-btn.active {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 20px;
}

.recipe-card {
  background: var(--card-bg);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  cursor: pointer;
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.recipe-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 6px 18px rgba(58, 46, 35, 0.08);
}
.recipe-card-thumb {
  width: 100%;
  aspect-ratio: 1 / 1;
  background: var(--paper-alt);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--ink-soft);
  font-size: 0.85rem;
  overflow: hidden;
}
.recipe-card-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.recipe-card-body { padding: 12px 14px 16px; }
.recipe-card-title {
  font-weight: 600;
  font-size: 1rem;
  margin: 0 0 8px;
  color: var(--ink);
}
.badge {
  display: inline-block;
  font-size: 0.72rem;
  padding: 3px 10px;
  border-radius: 999px;
  background: var(--accent-soft);
  color: #8c320b;
}

.empty-msg, .error-msg, .loading-msg {
  color: var(--ink-soft);
  font-size: 0.9rem;
  margin-top: 16px;
}
.error-msg { color: var(--danger); }

.paste-box, .recipe-form {
  background: var(--card-bg);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 20px;
  margin-bottom: 20px;
}
.paste-box label {
  display: block;
  font-size: 0.9rem;
  color: var(--ink-soft);
  margin-bottom: 8px;
}
#raw-text {
  width: 100%;
  padding: 12px;
  border-radius: 6px;
  border: 1px solid var(--line);
  font-size: 0.95rem;
  font-family: inherit;
  resize: vertical;
}
.paste-actions { margin-top: 12px; display: flex; gap: 10px; }

.form-row { margin-bottom: 16px; }
.form-row label {
  display: block;
  font-size: 0.85rem;
  color: var(--ink-soft);
  margin-bottom: 6px;
}
.form-row input[type="text"],
.form-row select,
.form-row textarea {
  width: 100%;
  padding: 9px 12px;
  border-radius: 6px;
  border: 1px solid var(--line);
  font-size: 0.95rem;
  font-family: inherit;
}

.story-textarea {
  font-family: "Hiragino Mincho ProN", "Yu Mincho", Georgia, serif;
  font-style: italic;
  color: var(--ink-soft);
  background: var(--paper);
  border-left: 3px solid var(--accent-soft) !important;
  /* Auto-grows to fit the full story text (see autoResizeTextarea in
     app.js) — never a fixed-height scroll box or a clamp/read-more toggle. */
  resize: none;
  overflow: hidden;
  min-height: 60px;
}

.list-editor { border: 1px solid var(--line); border-radius: 8px; padding: 12px; background: var(--paper); }
.list-row {
  display: flex;
  gap: 8px;
  margin-bottom: 8px;
  align-items: center;
}
.list-row input { flex: 1; padding: 7px 10px; border-radius: 6px; border: 1px solid var(--line); font-size: 0.9rem; }
.list-row .ing-name { flex: 2; }
.list-row .ing-qty { flex: 1; }
.list-row .ing-unit { flex: 1; }
/* Step text is a textarea (not an <input>) so a long imported step wraps
   onto multiple lines and always reads in full — never a single-line box
   that scrolls sideways or clips the text. It auto-grows with the content
   via autoResizeTextarea(), same pattern as the story box. */
.step-row { align-items: flex-start; }
.list-row .step-text {
  flex: 1;
  padding: 7px 10px;
  border-radius: 6px;
  border: 1px solid var(--line);
  font-size: 0.9rem;
  font-family: inherit;
  line-height: 1.5;
  white-space: pre-wrap;
  overflow-wrap: break-word;
  resize: none;
  overflow: hidden;
  min-height: 34px;
}
.step-num { color: var(--ink-soft); font-size: 0.85rem; width: 22px; text-align: right; margin-top: 8px; }
.step-thumb { width: 40px; height: 40px; object-fit: cover; border-radius: 6px; flex-shrink: 0; }
.step-row .icon-btn { margin-top: 2px; }
.attribution-line { color: var(--ink-soft); font-size: 0.85rem; margin: -4px 0 14px; }
.icon-btn {
  border: none;
  background: none;
  cursor: pointer;
  font-size: 0.95rem;
  color: var(--ink-soft);
  padding: 4px 6px;
}
.icon-btn:hover { color: var(--danger); }
.icon-btn.move:hover { color: var(--accent); }
.list-add-btn { margin-top: 4px; }

.form-actions { display: flex; gap: 10px; margin-top: 16px; }

.image-section { margin-bottom: 20px; }
.detail-image-wrap {
  width: 240px;
  aspect-ratio: 1 / 1;
  border-radius: var(--radius);
  background: var(--paper-alt);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  border: 1px solid var(--line);
}
.detail-image { width: 100%; height: 100%; object-fit: cover; }
.image-placeholder { color: var(--ink-soft); font-size: 0.85rem; }
.image-actions { display: flex; gap: 10px; margin-top: 10px; }

.translate-section {
  background: var(--card-bg);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 20px;
  margin-bottom: 40px;
}
.translate-section h3 {
  margin: 0 0 14px;
  font-size: 1.05rem;
  color: var(--accent);
}
.translate-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
}
.translate-controls select,
.translate-controls input[type="text"] {
  padding: 8px 12px;
  border-radius: 6px;
  border: 1px solid var(--line);
  font-size: 0.9rem;
  font-family: inherit;
}
.translate-toggle {
  margin-top: 14px;
  display: flex;
  gap: 4px;
  border-bottom: 1px solid var(--line);
}
.translate-toggle .btn-link {
  margin-bottom: 0;
  padding: 8px 14px;
  border-bottom: 2px solid transparent;
  border-radius: 0;
}
.translate-toggle .btn-link.active {
  border-bottom-color: var(--accent);
  font-weight: 600;
}
.translate-display {
  margin-top: 16px;
}
.translate-display h4 {
  margin: 0 0 10px;
  font-size: 1.1rem;
}
.translate-display .t-ingredients,
.translate-display .t-steps {
  padding-left: 20px;
  margin: 0 0 16px;
}
.translate-display .t-ingredients li,
.translate-display .t-steps li {
  margin-bottom: 6px;
  line-height: 1.7;
}
.translate-display .t-tags {
  margin-top: 6px;
}
.translate-display .t-tags .badge {
  margin-right: 6px;
}

@media (max-width: 640px) {
  main { padding: 16px; }
  .app-header { padding: 14px 16px; }
  .header-logo { height: 57.6px; } /* 1.8x the original 32px */

  /* At this width, the search box (240px) plus a full category-tabs row
     never fits on one line once the tabs actually render (9 buttons —
     see lib/categories.js) — the un-stacked layout only ever "fit" for the
     split second before the categories fetch resolves, at which point
     flex-wrap yanks the whole tabs block down onto a new row and sideways
     to the left edge. Stacking unconditionally makes that row structure
     fixed from first paint, independent of whether the tabs have 0 or 9
     children yet, so there's nothing left to snap into place later. */
  .toolbar { flex-direction: column; align-items: stretch; }
  #search-input { flex: none; width: 100%; }

  .brand-mission { margin: 0 auto 20px; }
  .brand-mission-tagline { font-size: 1.1rem; margin-bottom: 14px; }
  .brand-mission-body { font-size: 0.86rem; line-height: 1.85; }
}

/* Read-only public view (open.therecipe.jp) — server.js's sendHtml adds
   this class to <body> when the request is flagged read-only (see
   isReadOnlyRequest in server.js). Hides every write-capable control;
   browsing/search/detail viewing is untouched. This is a UI convenience
   only — the real enforcement is server-side Basic Auth on the routes
   themselves, not this class. */
body.read-only .write-only {
  display: none !important;
}

/* The detail view reuses the same <input>/<textarea>/<select> markup as
   the editable add/edit form (see disableFormFields in app.js, which sets
   .disabled = true on every field in read-only mode). That's fine for
   genuine non-editability, but the browser's native disabled-field style
   (dim gray text, often a gray-tinted background) is meant to read as "this
   field is temporarily unavailable" inside an editing UI — not as the
   primary way to present recipe text on a read-only *viewing* site. Charley
   reported title/category/ingredients/steps all rendering washed-out and
   hard to read on open.therecipe.jp. Restore full body-text contrast here
   while keeping the fields genuinely inert: no pointer events, no focus
   ring, default cursor, so they still don't look or behave editable. */
body.read-only .recipe-form input:disabled,
body.read-only .recipe-form textarea:disabled,
body.read-only .recipe-form select:disabled {
  color: var(--ink) !important;
  -webkit-text-fill-color: var(--ink) !important;
  opacity: 1 !important;
  background: var(--paper) !important;
  border-color: var(--line) !important;
  cursor: default;
  pointer-events: none;
}
/* The story/caption textarea intentionally keeps its own softer
   --ink-soft italic styling (see .story-textarea) even in read-only mode —
   that's a deliberate editorial voice, not a contrast bug, so it's left
   out of the full-contrast override above. */
body.read-only .recipe-form .story-textarea:disabled {
  color: var(--ink-soft) !important;
  -webkit-text-fill-color: var(--ink-soft) !important;
}

/* Per-row reorder (↑/↓) and delete (✕) icon buttons are meaningless in a
   read-only view — disabling them (see disableFormFields in app.js) still
   leaves them visible, which falsely implies the recipe can be reordered
   or its ingredients/steps deleted. Hide them outright instead of just
   graying them out. The ＋ add-ingredient/add-step buttons are equally
   editing-only, so they go too. */
body.read-only .icon-btn,
body.read-only .list-add-btn {
  display: none !important;
}
