/* ==========================================================================
   Foundations
   ==========================================================================
   The look is a gallery, not a newspaper: a cool near-white ground, near-black
   ink, and one warm metallic accent doing the work that a loud red used to.
   Michelin's own red survives on star badges only — it's their mark, and
   letting it double as the interface colour is what made everything shout.
   Type is sans-led with a serif kept for display, radii are generous, and
   depth comes from soft shadow rather than a hairline box around everything. */
:root {
  /* ground */
  --bg:        #fbfbfa;
  --bg-tint:   #f4f4f2;
  --panel:     #ffffff;
  --panel-2:   #f3f3f1;
  --line:      #e7e7e3;
  --line-soft: #f0f0ec;

  /* ink */
  --ink:       #16161a;
  --ink-mid:   #4a4a54;
  --ink-dim:   #82828d;

  /* accent — antique brass */
  --accent:      #a07d3c;
  --accent-dark: #7d6130;
  --accent-soft: #f7f2e6;
  --accent-line: #e4d8bd;

  /* semantic */
  --michelin:  #b21d2c;
  --good:      #2c6b4f;
  --warn:      #9c7a1f;

  --radius:    9px;
  --radius-lg: 16px;
  --radius-pill: 999px;

  --shadow-sm: 0 1px 2px rgba(22,22,26,.04), 0 2px 8px rgba(22,22,26,.04);
  --shadow-md: 0 2px 6px rgba(22,22,26,.05), 0 12px 28px rgba(22,22,26,.07);
  --shadow-lg: 0 30px 70px rgba(22,22,26,.18);

  /* Iowan/Palatino read as contemporary book type; Georgia reads as 2005. */
  --serif: "Iowan Old Style", "Palatino Linotype", Palatino, "Book Antiqua",
           ui-serif, Georgia, serif;
  /* system-ui picks up Segoe UI Variable / SF Pro — genuinely modern faces. */
  --sans: system-ui, -apple-system, "Segoe UI Variable Text", "Segoe UI",
          Inter, Roboto, "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, "Cascadia Mono", Consolas, monospace;
  --topbar-h: 62px; /* rendered height of .topbar; sticky panels offset from this */
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 15px/1.6 var(--sans);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
::selection { background: var(--accent-soft); }

/* ---------- top bar ---------- */
/* Light and translucent rather than a heavy black slab with a red rule under
   it — that bar was the single loudest dated thing on the page. It now
   dissolves into the content and only earns a shadow once you scroll. */
.topbar {
  display: flex; align-items: center; gap: 40px;
  padding: 14px 30px;
  background: rgba(251,251,250,.82);
  backdrop-filter: saturate(180%) blur(14px);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  border-bottom: 1px solid transparent;
  position: sticky; top: 0; z-index: 20;
  transition: border-color .25s ease, box-shadow .25s ease;
}
.topbar.is-scrolled {
  border-bottom-color: var(--line);
  box-shadow: 0 1px 24px rgba(22,22,26,.06);
}
.tabs { display: flex; gap: 26px; position: relative; }
.tab {
  background: none; border: 0;
  color: var(--ink-dim); padding: 7px 1px; cursor: pointer;
  font-family: var(--sans); font-size: 13px; font-weight: 550;
  letter-spacing: .005em;
  transition: color .15s ease;
}
.tab:hover { color: var(--ink); }
.tab.is-active { color: var(--ink); font-weight: 650; }
.tab-indicator {
  position: absolute; left: 0; bottom: 0; height: 2px; width: 0;
  background: var(--accent); border-radius: 2px;
  transition: transform .35s cubic-bezier(.65,0,.35,1), width .35s cubic-bezier(.65,0,.35,1);
}
.topbar-right { margin-left: auto; display: flex; align-items: center; gap: 18px; }
.linkbtn {
  color: var(--ink-dim); text-decoration: none; font-size: 12.5px;
  border-bottom: 1px solid transparent; transition: color .15s ease;
}
.linkbtn:hover { color: var(--accent); border-bottom-color: var(--accent-line); }
.admin-toggle {
  margin-left: auto; background: none; border: 0; padding: 0;
  font-family: var(--sans); cursor: pointer;
}

/* Visitors can browse vocabulary but not edit progress (status/star) —
 * visibility:hidden keeps grid cells reserved so row alignment doesn't
 * shift, display:none is safe for the non-grid controls. */
body:not(.is-admin) .wrow-check { visibility: hidden; }
body:not(.is-admin) .star-mark { display: none; }
body:not(.is-admin) .selectall { display: none; }
body:not(.is-admin) #bulkActions { display: none !important; }
body:not(.is-admin) .statusrow { display: none; }

/* ---------- layout ---------- */
main { padding: 26px; max-width: 1500px; margin: 0 auto; }
.hidden { display: none !important; }
.split { display: grid; grid-template-columns: minmax(0, 1fr) 360px; gap: 30px; }
@media (max-width: 1020px) { .split { grid-template-columns: 1fr; } }
.col { min-width: 0; }
.col-side {
  border-left: 1px solid var(--line); padding-left: 26px;
  position: sticky; top: calc(var(--topbar-h) + 16px); align-self: start;
  max-height: calc(100vh - var(--topbar-h) - 32px); overflow-y: auto;
}
@media (max-width: 1020px) {
  .col-side { border-left: 0; padding-left: 0; position: static; max-height: none; overflow-y: visible; }
}

h2 {
  font: 700 20px/1.2 var(--serif); margin: 0 0 16px; color: var(--ink);
}
.rowline { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; }
.rowline.wrap { flex-wrap: wrap; }
.rowline h2 { margin: 0; }
.muted { color: var(--ink-dim); }
.hint { color: var(--ink-dim); font-size: 12.5px; margin: 4px 0 0; }

/* ---------- forms ---------- */
.stack { display: flex; flex-direction: column; gap: 13px; }
label {
  display: flex; flex-direction: column; gap: 6px; font-size: 12.5px;
  color: var(--ink-mid); font-weight: 550; letter-spacing: .005em;
}
input, select, textarea, button { font-family: var(--sans); font-size: 14px; }
input, select, textarea {
  background: var(--panel); border: 1px solid var(--line);
  color: var(--ink); padding: 10px 12px; border-radius: var(--radius); width: 100%;
  transition: border-color .15s ease, box-shadow .15s ease;
}
textarea { resize: vertical; font-family: var(--sans); font-size: 14px; line-height: 1.6; }
input:focus, select:focus, textarea:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
input::placeholder, textarea::placeholder { color: #b4b4bc; }
.inline { width: auto; min-width: 140px; }
/* Sentence case, not uppercase-with-tracking: shouting at the reader in
   small caps is most of what made the old interface feel dated. */
button.primary {
  background: var(--ink); color: #fff; border: 0; font-weight: 600;
  padding: 10px 20px; border-radius: var(--radius-pill); cursor: pointer;
  font-size: 13px; letter-spacing: .005em;
  transition: background-color .18s ease, transform .18s ease, box-shadow .18s ease;
}
button.primary:hover {
  background: var(--accent-dark);
  box-shadow: 0 6px 18px rgba(125,97,48,.28); transform: translateY(-1px);
}
button.primary:active { transform: translateY(0); }
button.primary:disabled { opacity: .45; cursor: progress; box-shadow: none; transform: none; }
button.ghost {
  background: var(--panel); border: 1px solid var(--line); color: var(--ink-mid);
  padding: 7px 14px; border-radius: var(--radius-pill); cursor: pointer;
  font-size: 12.5px; letter-spacing: .005em;
  transition: color .15s ease, border-color .15s ease, background-color .15s ease;
}
button.ghost:hover { color: var(--ink); border-color: #d6d6d0; background: var(--bg-tint); }
button.ghost:disabled { opacity: .4; cursor: default; }
button.ghost.on { background: var(--ink); color: #fff; border-color: var(--ink); }
.checkline {
  flex-direction: row; align-items: center; gap: 7px;
  text-transform: none; letter-spacing: normal; font-size: 13px;
}
.checkline input { width: auto; }
.filedrop input { padding: 7px; }
details summary { cursor: pointer; color: var(--ink-dim); font-size: 13px; }

/* ==========================================================================
   The blog: restaurant gallery, restaurant page, visits
   ==========================================================================
   Photo-forward and quiet. The palette is unchanged — warm paper, one red —
   but the furniture gets out of the way: no permanent sidebar form, no boxes
   around boxes, and every card is mostly picture. Typography carries the
   hierarchy instead of borders. */

.adminonly { display: none !important; }
body.is-admin .adminonly { display: inline-flex !important; }
body.is-admin .visit-actions.adminonly { display: flex !important; }

.page-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 20px 28px; flex-wrap: wrap; margin: 14px 0 34px;
}
.page-title {
  font: 400 46px/1.02 var(--serif); margin: 0; letter-spacing: -.028em;
}
.page-sub {
  margin: 9px 0 0; color: var(--ink-dim); font-size: 12.5px;
  letter-spacing: .01em;
}
.page-tools { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.page-tools .inline {
  min-width: 0; font-size: 13px; padding: 9px 13px;
  border-radius: var(--radius-pill); background: var(--panel);
}
#placeSearch { min-width: 180px; }

/* ---------- listing tags ---------- */
/* The guide filters, as a row of toggles with their counts, where a dropdown
   used to be. The count is the reason they're tags: five numbers visible at
   once is the thing a <select> can't do. Michelin tags carry the star itself,
   so the row reads as marks rather than as words about marks. */
.tagfilter { display: flex; flex-wrap: wrap; gap: 8px; margin: -6px 0 22px; }
.tagchip {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--panel); border: 1px solid var(--line); color: var(--ink-mid);
  border-radius: var(--radius-pill); padding: 6px 13px; cursor: pointer;
  font-family: var(--sans); font-size: 12.5px; letter-spacing: .005em;
  transition: border-color .15s ease, background-color .15s ease, color .15s ease;
}
.tagchip:hover { border-color: #d6d6d0; background: var(--bg-tint); }
.tagchip.on { background: var(--ink); border-color: var(--ink); color: #fff; }
.tagchip-n {
  font-family: var(--mono); font-size: 11px; color: var(--ink-dim);
  background: var(--panel-2); border-radius: var(--radius-pill); padding: 1px 7px;
}
.tagchip.on .tagchip-n { background: rgba(255,255,255,.18); color: #fff; }
/* Michelin red on the stars, until the tag is picked and goes dark — at
   which point the mark inherits white and stays legible. */
.tagchip-star { color: var(--michelin); gap: 3px; }
.tagchip-star .tagchip-n { margin-left: 4px; }

/* ---------- gallery ---------- */
.place-grid {
  display: grid; gap: 34px 24px;
  grid-template-columns: repeat(auto-fill, minmax(262px, 1fr));
}
.place-card { cursor: pointer; outline: none; }
.place-card-img {
  position: relative; aspect-ratio: 4 / 3; overflow: hidden;
  border-radius: var(--radius-lg); background: var(--panel-2);
  border: 1px solid var(--line-soft);
  box-shadow: var(--shadow-sm);
  transition: box-shadow .3s ease, transform .3s ease;
}
.place-card:hover .place-card-img,
.place-card:focus-visible .place-card-img {
  box-shadow: var(--shadow-md); transform: translateY(-3px);
}
.place-card-img img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  transition: transform .5s cubic-bezier(.2,.6,.2,1);
}
.place-card:hover .place-card-img img,
.place-card:focus-visible .place-card-img img { transform: scale(1.045); }
.place-card:focus-visible .place-card-img { border-color: var(--accent); }
/* A menu photo standing in for a dish photo: anchored to the top so the
   menu's own printed header shows, and eased back slightly so a grid of
   them reads as paper rather than as a wall of scanned documents. */
.place-card-img img.is-menu, .place-hero-img img.is-menu {
  object-position: top center; filter: saturate(.78) contrast(.96);
}
.hero-kind {
  position: absolute; top: 9px; right: 9px; z-index: 2;
  background: #ffffffdd; color: var(--ink-dim); border-radius: 999px;
  padding: 2px 9px; font-size: 9.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .1em;
}
/* A restaurant with no photo — or one whose file has gone missing — still
   needs to look deliberate, so it gets its initial set in the display serif
   on a soft wash rather than an empty box or a broken-image icon. It sits
   behind the photo, so removing a failed <img> simply reveals it. */
.place-card-blank {
  position: absolute; inset: 0; z-index: 0; display: flex; align-items: center;
  justify-content: center; font: 400 64px var(--serif); color: #cfcfc6;
  background: linear-gradient(150deg, var(--panel-2), var(--bg-tint));
}
/* The photo has to sit above the placeholder it's covering: an absolutely
   positioned element outranks a static one, so without this the letter
   paints straight over a perfectly good picture. */
.place-card-img img, .place-hero-img img { position: relative; z-index: 1; }
.place-card-badges {
  position: absolute; left: 10px; bottom: 10px; z-index: 2; display: flex; gap: 5px;
  flex-wrap: wrap; max-width: calc(100% - 20px);
}
.place-card-badges .tag {
  background: #fffffff2; border-color: #ffffff; backdrop-filter: blur(4px);
  box-shadow: 0 1px 4px rgba(26,24,21,.16);
}
.place-card-body { padding: 15px 3px 0; }
.place-card-name {
  font: 500 20px/1.22 var(--serif); margin: 0; letter-spacing: -.017em;
  transition: color .15s ease;
}
.place-card:hover .place-card-name { color: var(--accent-dark); }
.place-card-where {
  margin: 5px 0 0; font-size: 12.5px; color: var(--ink-mid);
}
.place-card-stat { margin: 3px 0 0; font-size: 12.5px; color: var(--ink-dim); }

/* ---------- pager ---------- */
.pager {
  display: flex; align-items: center; justify-content: center; gap: 18px;
  margin: 48px 0 8px; padding-top: 28px; border-top: 1px solid var(--line-soft);
}
.pager-label { font-size: 12.5px; color: var(--ink-dim); min-width: 130px; text-align: center; }

/* ---------- one restaurant ---------- */
.backlink {
  background: none; border: 0; padding: 0; margin: 10px 0 26px; cursor: pointer;
  color: var(--ink-dim); font: 500 13px var(--sans); letter-spacing: .005em;
  transition: color .15s ease;
}
.backlink:hover { color: var(--accent); }

/* "Find a picture" sits on the hero image itself and only surfaces on hover,
   so it never competes with the photograph it's offering to replace. */
.img-swap {
  position: absolute; left: 12px; bottom: 12px; z-index: 2;
  background: rgba(255,255,255,.94); color: var(--ink);
  border: 0; border-radius: var(--radius-pill); cursor: pointer;
  padding: 8px 15px; font: 550 12.5px var(--sans);
  box-shadow: var(--shadow-sm); opacity: 0; transform: translateY(4px);
  transition: opacity .2s ease, transform .2s ease, color .15s ease;
}
.place-hero-img:hover .img-swap, .img-swap:focus-visible { opacity: 1; transform: none; }
.img-swap:hover { color: var(--accent-dark); }
/* With no picture at all the button is the only affordance there is, so it
   stays visible rather than hiding behind a hover on an empty box. */
.place-hero-img.is-blank .img-swap { opacity: 1; transform: none; }
.place-hero-img.is-blank { display: flex; align-items: center; justify-content: center; }
.place-hero {
  display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
  gap: 34px; align-items: start; margin-bottom: 40px;
}
@media (max-width: 900px) { .place-hero { grid-template-columns: 1fr; gap: 22px; } }
/* No photo yet — one column, so the text doesn't sit in half a grid with an
   empty gap beside it. */
.place-hero-textonly { grid-template-columns: 1fr; max-width: 74ch; }
/* Capped rather than purely proportional: on a wide screen a 4:3 hero grows
   to half the viewport and pushes the visits — the part you wrote — off the
   bottom, while leaving dead space beside a short header. */
.place-hero-img {
  position: relative; aspect-ratio: 3 / 2; max-height: 400px; overflow: hidden;
  border-radius: var(--radius-lg); border: 1px solid var(--line-soft);
  background: var(--panel-2); box-shadow: var(--shadow-sm);
}
.place-hero-img img { width: 100%; height: 100%; object-fit: cover; cursor: zoom-in; }
.place-hero-city {
  margin: 0; color: var(--accent); font-size: 12px; font-weight: 600;
  text-transform: uppercase; letter-spacing: .13em;
}
.place-hero-name {
  font: 400 52px/1.0 var(--serif); margin: 10px 0 16px; letter-spacing: -.03em;
}
.place-hero-tags { display: flex; gap: 6px; flex-wrap: wrap; }
.place-hero-stat {
  margin: 14px 0 0; color: var(--ink-dim); font-size: 12.5px;
}
/* The guide's write-up runs long. Four lines is enough to place the
   restaurant; the rest is a click away rather than pushing the visits — the
   part you wrote — below the fold. */
.place-hero-blurb {
  margin: 16px 0 0; font: italic 14.5px/1.62 var(--serif); color: #4a4438;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 4;
  line-clamp: 4; overflow: hidden;
}
.place-hero-blurb.is-open { -webkit-line-clamp: unset; line-clamp: unset; }
.blurb-more {
  background: none; border: 0; padding: 0; margin-top: 4px; cursor: pointer;
  color: var(--ink-dim); font: 600 11px var(--sans);
  text-transform: uppercase; letter-spacing: .08em;
}
.blurb-more:hover { color: var(--accent); }
.place-hero-note {
  margin: 12px 0 0; font-size: 13.5px; color: var(--ink-dim);
  padding-left: 12px; border-left: 2px solid var(--line);
}
.place-hero-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 20px; }
.section-title {
  font: 600 11.5px var(--sans); text-transform: uppercase; letter-spacing: .16em;
  color: var(--ink-dim); margin: 46px 0 24px; padding-bottom: 11px;
  border-bottom: 1px solid var(--line-soft);
}

/* ---------- a visit ---------- */
.visit {
  padding: 0 0 30px; margin-bottom: 30px; border-bottom: 1px solid var(--line);
  border-radius: var(--radius-lg); transition: background-color .15s ease;
}
.visit:last-child { border-bottom: 0; margin-bottom: 0; }
.visit.is-dropping { background: var(--accent-soft); box-shadow: 0 0 0 10px var(--accent-soft); }
.visit-head { display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap; }
.visit-date {
  font: 600 11.5px var(--sans); text-transform: uppercase; letter-spacing: .12em;
  color: var(--accent);
}
.visit-title { font: 500 23px/1.22 var(--serif); margin: 0; letter-spacing: -.018em; }
.visit-actions { margin-left: auto; display: flex; gap: 7px; }
.visit-notes {
  margin: 14px 0 0; font: 16.5px/1.75 var(--serif); color: var(--ink-mid);
  max-width: 68ch; white-space: pre-wrap; letter-spacing: -.003em;
}
.visit-empty { margin: 12px 0 0; color: var(--ink-dim); font-size: 13px; }

/* The photo strips on a visit — dish photos, then the menu shots below them.
   One shape and one column rhythm across both, so the two grids read as a
   single wall rather than two unrelated ones: same 3:4 portrait, same track
   width. Dish photos used to be square and menus two-thirds their width,
   which left the rows misaligned wherever they met. Menus keep only a slight
   de-emphasis, since they're reference material rather than the point. */
.shots {
  display: grid; gap: 12px; margin-top: 18px;
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
}
.shots-menus { margin-top: 12px; }
.shot { margin: 0; position: relative; }
.shot img {
  width: 100%; aspect-ratio: 3 / 4; object-fit: cover; display: block;
  border-radius: var(--radius-lg); border: 1px solid var(--line-soft);
  background: var(--panel-2); cursor: zoom-in; box-shadow: var(--shadow-sm);
  transition: transform .25s ease, box-shadow .25s ease;
}
.shot img:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); }
.shot-menu img { opacity: .92; }
.shot-menu img:hover { opacity: 1; }
.shot figcaption {
  margin-top: 6px; font-size: 12px; color: var(--ink-dim); line-height: 1.4;
}
.shot-edit {
  position: absolute; top: 7px; right: 7px; width: 26px; height: 26px;
  border-radius: 50%; border: 0; cursor: pointer; line-height: 1;
  background: #ffffffe0; color: var(--ink); font-size: 13px;
  align-items: center; justify-content: center; opacity: 0;
  transition: opacity .15s ease;
}
.shot:hover .shot-edit { opacity: 1; }
.shot-edit:hover { background: #fff; color: var(--accent); }

/* ---------- modal form ---------- */
.modal-host { cursor: default; align-items: flex-start; padding: 8vh 16px; overflow-y: auto; }
.modal {
  background: var(--panel); border-radius: var(--radius-lg);
  padding: 30px 32px; width: min(540px, 100%);
  display: flex; flex-direction: column; gap: 16px;
  box-shadow: var(--shadow-lg);
}
.modal-title { font: 400 30px/1.12 var(--serif); margin: 0; letter-spacing: -.022em; }
.modal-field { gap: 7px; }
.modal-field textarea { font-family: var(--serif); font-size: 15.5px; line-height: 1.65; }
.modal-actions { display: flex; justify-content: flex-end; gap: 9px; margin-top: 6px; }
.modal-hint { color: var(--ink-dim); font-size: 13px; line-height: 1.5; margin: -4px 0 0; }
/* Typeahead under a modal field — the guide-name suggestions on Add a
   restaurant. Absolutely positioned so the modal doesn't resize as you type. */
.suggest {
  position: absolute; top: 100%; left: 0; right: 0; z-index: 5;
  margin-top: 4px; max-height: 260px; overflow-y: auto;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow-md);
}
.suggest-row {
  display: flex; flex-direction: column; gap: 2px; width: 100%; text-align: left;
  background: none; border: 0; padding: 8px 12px; cursor: pointer;
  font-family: var(--sans);
}
.suggest-row:hover, .suggest-row.on { background: var(--panel-2); }
.suggest-name { font-size: 14px; color: var(--ink); }
.suggest-note {
  font: 11px/1.35 var(--mono); color: var(--ink-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Picking which guide entry a restaurant is, when the matcher found several
   and refused to guess. Each row leads with the name and sets its location
   underneath, because the location is the thing telling them apart. */
.modal-choices { display: flex; flex-direction: column; gap: 7px; }
.modal-choice {
  display: flex; flex-direction: column; gap: 3px; text-align: left;
  background: var(--panel-2); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 10px 12px; cursor: pointer;
  transition: border-color .15s ease, background .15s ease;
}
.modal-choice:hover { border-color: var(--ink-dim); background: var(--panel); }
.modal-choice-label { font-size: 14.5px; color: var(--ink); }
.modal-choice-note { font: 11.5px/1.35 var(--mono); color: var(--ink-dim); }
/* Names the list an OAD run belongs to — a restaurant can hold placings on
   several lists in the same year, so the years alone would be ambiguous. */
.rank-list {
  font: 10.5px/1.35 var(--mono); color: var(--ink-dim);
  text-transform: uppercase; letter-spacing: .06em;
  align-self: center; margin-right: 3px;
}

/* ---------- cards ---------- */
.cards { display: flex; flex-direction: column; gap: 12px; }
.card {
  background: var(--panel); border: 1px solid var(--line-soft);
  border-radius: var(--radius-lg); padding: 18px 20px;
  box-shadow: var(--shadow-sm);
  transition: box-shadow .2s ease, transform .2s ease;
}
.card:hover { box-shadow: var(--shadow-md); transform: translateY(-2px); }
.card-clickable { cursor: pointer; }
.card-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.tag {
  font-size: 11px; padding: 3px 10px; border-radius: var(--radius-pill);
  background: var(--panel-2); border: 1px solid transparent; color: var(--ink-mid);
  letter-spacing: .005em; white-space: nowrap;
}
.menu-group { display: flex; flex-direction: column; gap: 12px; }
.menu-group-title {
  font: 700 12px var(--sans); margin: 4px 0 -2px; padding-bottom: 6px;
  text-transform: uppercase; letter-spacing: .09em; color: var(--ink-dim);
  border-bottom: 1px solid var(--line);
}
.place-thumb {
  width: 56px; height: 56px; object-fit: cover; border-radius: var(--radius);
  border: 1px solid var(--line); cursor: zoom-in; background: var(--panel-2);
  flex-shrink: 0; align-self: center;
}
.card-title { font: 700 17px/1.3 var(--serif); }
.card-sub {
  color: var(--ink-dim); font-size: 11px; text-transform: uppercase;
  letter-spacing: .08em;
}
.card-actions { margin-left: auto; display: flex; gap: 7px; }
.card-meta { color: var(--ink-dim); font-size: 12.5px; margin-top: 8px; }

/* ---------- Michelin Guide facts on a restaurant card ---------- */
/* Stars keep Michelin's own red — it's their mark, and it's the one place on
   the page that colour still belongs. The Bib and the green star rank below
   a star, so they stay quieter. */
.tag-star {
  background: #fdeff0; border-color: #f4d3d6; color: var(--michelin);
  font-size: 12.5px; padding: 3px 9px; display: inline-flex; align-items: center;
  gap: 2px; vertical-align: middle;
}
/* The Michelin star (the <symbol> in index.html). Sized in em so it tracks
   whatever text it sits beside, and inheriting colour so the same glyph works
   on the red badge and anywhere darker. The box matches the artwork's own
   916:1000 ratio, so the stars sit evenly spaced instead of each carrying a
   sliver of letterboxing. */
.mstar { width: .96em; height: 1.05em; display: block; flex: none; }
/* Present to a screen reader, invisible on screen — the rosette carries no
   text, so the award name rides along behind it. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.tag-bib { background: #fdeff0; border-color: #f4d3d6; color: var(--michelin); }
.tag-green { color: var(--good); border-color: #cbe0d3; background: #eff6f2; }
/* World's 50 Best sits alongside a Michelin tag without competing: a rank is
   a different kind of claim from a star, so it takes the interface brass. */
.tag-rank { color: var(--accent-dark); border-color: var(--accent-line); background: var(--accent-soft); }
/* The ranking run: each year a small block, newest first, reading left to
   right as a trajectory rather than a sentence. */
.rank-run { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 7px; }
.rank-year {
  font: 10.5px/1.35 var(--mono); color: var(--ink-dim);
  background: var(--panel-2); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 2px 6px; white-space: nowrap;
}
.rank-year b { color: var(--ink); margin-left: 4px; font-weight: 700; }
/* The inspector's write-up is a paragraph; three lines is enough to place
   the restaurant, and the whole thing is in the element's tooltip. */
.card-blurb {
  color: var(--ink-dim); font: italic 13px/1.5 var(--serif); margin-top: 8px;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3;
  line-clamp: 3; overflow: hidden;
}
.card-links { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 10px; }
.card-links a {
  color: var(--ink-dim); font-size: 11.5px; text-decoration: none;
  text-transform: uppercase; letter-spacing: .07em;
  border-bottom: 1px solid var(--line);
}
.card-links a:hover { color: var(--accent); border-bottom-color: var(--accent); }
.menugrid { display: grid; grid-template-columns: 132px minmax(0,1fr); gap: 16px; }
.menugrid img {
  width: 132px; height: 132px; object-fit: cover; border-radius: var(--radius);
  border: 1px solid var(--line); cursor: zoom-in; background: var(--panel-2);
}
.ocrbox {
  font-family: var(--mono); font-size: 12px; line-height: 1.5;
  white-space: pre-wrap; max-height: 160px; overflow: auto;
  background: var(--panel-2); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 9px; color: #4a4438;
}
.chips { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 10px; }
.chip {
  font-size: 11.5px; padding: 3px 10px; border-radius: 999px;
  background: var(--panel-2); border: 1px solid var(--line);
  color: var(--ink); cursor: pointer;
}
.chip:hover { border-color: var(--accent); color: var(--accent); }
.chip.phrase { font-style: italic; font-family: var(--serif); }

/* ---------- a menu's transcription, on the restaurant page ---------- */
/* Opened from the ··· on a menu shot. Set apart from the write-up around it
   — this is the raw material the vocabulary came from, not part of the story
   of the meal — so it sits on a tinted slab rather than in the page's flow. */
.menu-work {
  margin-top: 12px; padding: 14px 16px;
  background: var(--bg-tint); border: 1px solid var(--line);
  border-radius: var(--radius-lg);
}
.menu-work-head {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  margin-bottom: 10px;
}
.menu-work-title { font: 500 15px var(--serif); color: var(--ink); }
.menu-work-meta { font-size: 12px; color: var(--ink-dim); }
.menu-work-actions { margin-left: auto; display: flex; gap: 7px; flex-wrap: wrap; }
.menu-work .ocrbox { background: var(--panel); max-height: 280px; }

/* ---------- source separation (Vocabulary) ---------- */
/* How vocab from different sources stays separate once there's more than one:
 * a segmented control, with the same word-table underneath regardless of
 * which source is selected. Menu vocabulary is currently the only source, so
 * the bar renders empty (see renderSourceTabs) and these rules are dormant
 * until a second one is added. */
.source-tabs { display: flex; gap: 7px; margin: -6px 0 16px; flex-wrap: wrap; }
.source-tab {
  background: var(--panel); border: 1px solid var(--line); color: var(--ink-dim);
  padding: 7px 16px; border-radius: 999px; cursor: pointer;
  font-family: var(--sans); font-size: 12px; font-weight: 600;
  text-transform: uppercase; letter-spacing: .07em;
  transition: background-color .15s ease, color .15s ease, border-color .15s ease;
}
.source-tab:hover { border-color: var(--ink); color: var(--ink); }
.source-tab.is-active { background: var(--ink); color: #fff; border-color: var(--ink); }

/* ---------- vocabulary overview ---------- */
.vocab-stats { margin-bottom: 12px; }
.stats-row {
  display: flex; gap: 16px; align-items: baseline; flex-wrap: wrap;
  font-size: 12.5px; color: var(--ink-dim); margin-bottom: 6px;
}
.stats-row strong { color: var(--ink); font: 700 16px var(--serif); }
.stat-star { color: var(--accent); }

.filters-extra { display: flex; gap: 12px; flex-wrap: wrap; margin: -6px 0 16px; }
.vocab-toolbar { display: flex; align-items: center; gap: 18px; margin-bottom: 10px; flex-wrap: wrap; }
.selectall {
  flex-direction: row; align-items: center; gap: 6px; font-size: 12px;
  color: var(--ink-dim); text-transform: none; letter-spacing: normal; cursor: pointer;
}
.selectall input { width: auto; }
.bulk-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.pagination { display: flex; align-items: center; gap: 10px; margin-left: auto; font-size: 12.5px; }
.pagination button:disabled { opacity: .35; cursor: default; }

kbd {
  font-family: var(--sans); font-size: 10.5px; border: 1px solid var(--line);
  border-radius: 3px; padding: 1px 5px; margin: 0 1px; color: var(--ink-dim);
}

/* ---------- word table ---------- */
.wordtable { display: flex; flex-direction: column; gap: 1px; }
.wrow {
  display: grid; grid-template-columns: 18px 30px minmax(0,1fr) 30px 74px;
  gap: 10px; align-items: center; padding: 9px 14px;
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  cursor: pointer; border-left: 3px solid transparent;
}
.wrow:hover { border-left-color: var(--accent); }
.wrow.is-sel { border-left-color: var(--accent); background: var(--panel-2); }
.wrow.row-checked { background: var(--accent-soft); }
.wrow-check { width: auto; margin: 0; cursor: pointer; }
.wrow-thumb {
  width: 30px; height: 30px; object-fit: cover; border-radius: var(--radius);
  border: 1px solid var(--line); background: var(--panel-2);
}
.wrow-thumb-empty { border: none; background: transparent; }
.wrow .term { font: 600 15.5px/1.3 var(--serif); }
.wrow .term.phrase { font-style: italic; }
.wrow .term small {
  color: var(--ink-dim); font-size: 10.5px; margin-left: 7px;
  text-transform: uppercase; letter-spacing: .06em; font-family: var(--sans);
}
.ai-dot {
  display: inline-block; width: 6px; height: 6px; border-radius: 50%;
  background: var(--accent); margin-left: 6px; vertical-align: middle;
}
.starcell { text-align: center; }
.seen { font-family: var(--mono); font-size: 11.5px; color: var(--ink-dim); text-align: right; }
.star-mark {
  display: inline-block; width: 19px; height: 19px;
  line-height: 18px; text-align: center; border-radius: 50%;
  background: var(--panel); border: 1px solid var(--line);
  font-size: 11px; color: var(--ink-dim); cursor: pointer;
  opacity: 0; transition: opacity .12s ease;
}
.wrow:hover .star-mark, .star-mark.on { opacity: 1; }
.star-mark.on { color: var(--accent); border-color: var(--accent); }

/* ---------- detail + tips ---------- */
.detail { display: flex; flex-direction: column; gap: 14px; }
.detail.fade-in { animation: detailFade .18s ease; }
@keyframes detailFade { from { opacity: .3; } to { opacity: 1; } }
.detail-nav {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: -6px; font-size: 12px;
}
.detail-nav button:disabled { opacity: .35; cursor: default; }
.term-row { display: flex; align-items: center; gap: 10px; }
.detail h3 { margin: 0; font: 700 24px/1.25 var(--serif); }
.detail .sub {
  color: var(--ink-dim); font-size: 11px; margin-top: -9px;
  text-transform: uppercase; letter-spacing: .08em;
}
.speak-btn {
  display: inline-flex; align-items: center; justify-content: center;
  flex-shrink: 0; width: 30px; height: 30px; padding: 0;
  border-radius: 50%; background: var(--panel); border: 1px solid var(--line);
  color: var(--ink-dim); cursor: pointer; transition: color .15s ease, border-color .15s ease;
}
.speak-btn:hover { color: var(--accent); border-color: var(--accent); }
.speak-btn.priming { color: var(--accent); animation: speakPulse .9s ease infinite; }
@keyframes speakPulse { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }
.tip {
  background: var(--panel); border: 1px solid var(--line);
  border-left: 3px solid var(--accent); border-radius: var(--radius); padding: 11px 13px;
}
.tip-title {
  font-size: 10.5px; text-transform: uppercase; letter-spacing: .1em;
  color: var(--accent); margin-bottom: 5px; font-weight: 600;
}
.tip-body { font-size: 13.5px; white-space: pre-wrap; }
.tip-body strong { color: var(--ink); }
.tip-more-btn {
  display: block; margin-top: 7px; background: none; border: 0; padding: 0;
  color: var(--accent); font-size: 12px; font-weight: 600; cursor: pointer;
}
.tip-more-btn:hover { text-decoration: underline; }
.tip-more { margin-top: 6px; font-size: 13.5px; white-space: pre-wrap; }
.statusrow { display: flex; gap: 7px; }

.word-photo {
  display: block; width: 100%; max-height: 260px; object-fit: cover;
  border-radius: var(--radius-lg); border: 1px solid var(--line); cursor: zoom-in;
  background: var(--panel-2); margin-bottom: 8px;
}
.ainotes { margin: 14px 0; }
.ainotes-origin {
  font-size: 10.5px; text-transform: uppercase; letter-spacing: .09em;
  color: var(--accent); margin-top: 12px; font-weight: 600;
}
.ainotes-zh { font-size: 13.5px; line-height: 1.65; margin: 7px 0 9px; }
.vocab-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.vocab-chip {
  font-size: 11.5px; padding: 4px 10px; border-radius: 999px;
  background: var(--panel-2); border: 1px solid var(--line); color: var(--ink-dim);
}
.native-script { color: var(--accent); }
.vocab-chip b { color: var(--ink); font-weight: 600; }

.deffield { margin-top: 14px; }

/* ---------- misc ---------- */
.toast {
  position: fixed; bottom: 22px; left: 50%; transform: translateX(-50%);
  background: var(--ink); border: 1px solid var(--ink);
  color: #fff; padding: 12px 20px; border-radius: var(--radius);
  z-index: 60; max-width: 80vw; font-size: 13.5px;
}
.toast.err { background: var(--accent); border-color: var(--accent); }
.lightbox {
  position: fixed; inset: 0; background: #0a0a0aee; z-index: 80;
  display: flex; align-items: center; justify-content: center; cursor: zoom-out;
}
.lightbox img { max-width: 94vw; max-height: 94vh; border-radius: var(--radius); }
.img-picker-panel {
  background: var(--panel); border-radius: var(--radius-lg);
  padding: 26px 28px; width: min(680px, 92vw); max-height: 84vh; overflow: auto;
  cursor: default; box-shadow: var(--shadow-lg);
  display: flex; flex-direction: column; gap: 14px;
}
.img-picker-panel .rowline { margin: 0; }
.img-picker-panel .hint { margin: 0; }
.img-picker-grid {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px;
}
.img-picker-grid img {
  width: 100%; aspect-ratio: 1; object-fit: contain; border-radius: var(--radius);
  border: 1px solid var(--line); cursor: pointer; background: var(--panel-2);
  padding: 4px; transition: border-color .15s ease, transform .15s ease;
}
.img-picker-grid img:hover {
  border-color: var(--accent); transform: translateY(-2px);
}
/* Uploading is the primary path in the picture dialog, so it gets the large
   target and the search sits under it as the fallback. */
.dropzone {
  border: 1.5px dashed var(--line); border-radius: var(--radius-lg);
  padding: 26px 20px; text-align: center; background: var(--bg-tint);
  transition: border-color .15s ease, background-color .15s ease;
}
.dropzone.is-dropping { border-color: var(--accent); background: var(--accent-soft); }
.dropzone-lead { margin: 0 0 4px; font-size: 14.5px; color: var(--ink); }
.dropzone .hint { margin: 0; }
.linklike {
  background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--accent); font: inherit; text-decoration: underline;
  text-underline-offset: 2px;
}
.linklike:hover { color: var(--accent-dark); }
.empty {
  border: 1px dashed var(--line); border-radius: var(--radius-lg);
  padding: 44px 28px; text-align: center; color: var(--ink-dim);
  grid-column: 1 / -1; background: var(--panel);
}

/* ---------- mobile ---------- */
@media (max-width: 640px) {
  .topbar { flex-wrap: wrap; gap: 10px 16px; padding: 14px 16px; }
  /* Sized to content (not forced full-width) so a visitor's one visible tab
     shares a row with Admin login instead of forcing it onto its own line;
     max-width + overflow-x keeps the full admin tab set scrollable in place
     of wrapping when there isn't room for all of them plus the button. */
  .tabs { flex-shrink: 0; max-width: 100%; gap: 20px; overflow-x: auto; }
  .tab { font-size: 11.5px; letter-spacing: .08em; white-space: nowrap; }

  main { padding: 16px; }
  .split { gap: 20px; }

  .rowline { flex-wrap: wrap; }
  .inline { min-width: 0; flex: 1 1 140px; }

  button.ghost { padding: 8px 13px; }

  .card { padding: 14px; }
  .menugrid { grid-template-columns: 1fr; }
  .menugrid img { width: 100%; height: 200px; }

  /* Thumbnail and seen-count are still one tap away in the detail panel —
     drop them from the compact row so the term has room to breathe. */
  .wrow { grid-template-columns: 18px minmax(0,1fr) 30px; gap: 8px; padding: 11px 10px; }
  .wrow-thumb, .wrow-thumb-empty, .seen { display: none; }

  .statusrow { flex-wrap: wrap; }

  .img-picker-grid { grid-template-columns: repeat(3, 1fr); }
}
