/* ExpoCards — mobile-first, no build step. Tested at 380px. */

:root {
  --accent: #2563eb;
  --accent-ink: #ffffff;

  --bg: #f1f5f9;
  --surface: #ffffff;
  --surface-2: #f8fafc;
  --border: #e2e8f0;
  --ink: #0f172a;
  --ink-2: #475569;
  /* Used for 12px metadata — capture dates, event tags, queue errors — which
     appear on both --surface and --bg, so it must clear 4.5:1 against each.
     The lighter slate this started as managed only 2.56:1 on white. */
  --ink-3: #61707f;
  --danger: #b91c1c;
  --danger-bg: #fef2f2;
  /* A fill under white text, in both themes (6.47:1). Dark mode's --danger is
     a light pink for text on dark, and white on it is 1.9:1. Deliberately not
     overridden below. */
  --danger-solid: #b91c1c;
  --warn: #92400e;
  --warn-bg: #fffbeb;
  --warn-border: #fcd34d;
  --ok: #166534;
  --ok-bg: #f0fdf4;

  --radius: 14px;
  --radius-sm: 10px;
  --tap: 48px;
  --header-h: 56px;
  --nav-h: 60px;

  --safe-t: env(safe-area-inset-top, 0px);
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-l: env(safe-area-inset-left, 0px);
  --safe-r: env(safe-area-inset-right, 0px);
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0b1220;
    --surface: #131c2e;
    --surface-2: #0f1727;
    --border: #24314b;
    --ink: #e8edf7;
    --ink-2: #a5b4cb;
    /* Same 4.5:1 requirement as the light theme; the darker slate this started
       as managed only 4.03:1 on --surface. */
    --ink-3: #7688a5;
    --danger: #fca5a5;
    --danger-bg: #2a1414;
    --warn: #fcd34d;
    --warn-bg: #2a2109;
    --warn-border: #78591a;
    --ok: #86efac;
    --ok-bg: #0d2318;
  }
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  overscroll-behavior-y: none;
}

body {
  background: var(--bg);
  color: var(--ink);
  font: 16px/1.45 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  -webkit-text-size-adjust: 100%;
  -webkit-tap-highlight-color: transparent;
  /* Card values are whatever was printed on the card: long URLs, long email
     addresses and German or Dutch compound company names all arrive as single
     unbreakable words. Without this they push the whole page sideways.
     `break-word` rather than `anywhere` so intrinsic sizing is unaffected. */
  overflow-wrap: break-word;
}

[hidden] { display: none !important; }

/* ---------- App frame ---------- */

#app {
  display: flex;
  flex-direction: column;
  min-height: 100%;
  min-height: 100dvh;
}

.app-header {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: var(--header-h);
  padding: var(--safe-t) calc(12px + var(--safe-r)) 0 calc(12px + var(--safe-l));
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}

.app-header__logo {
  width: 28px;
  height: 28px;
  border-radius: 7px;
  object-fit: cover;
  flex: none;
}

.app-header__title {
  font-size: 17px;
  font-weight: 650;
  margin: 0;
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.app-main {
  flex: 1;
  padding: 16px calc(16px + var(--safe-r)) 24px calc(16px + var(--safe-l));
  padding-bottom: calc(24px + var(--nav-h) + var(--safe-b));
}

.app-nav {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 20;
  display: flex;
  background: var(--surface);
  border-top: 1px solid var(--border);
  padding-bottom: var(--safe-b);
}

.app-nav button {
  flex: 1;
  min-height: var(--nav-h);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  border: 0;
  background: none;
  color: var(--ink-3);
  font-size: 11px;
  font-weight: 600;
  cursor: pointer;
  position: relative;
}

.app-nav button svg { width: 22px; height: 22px; }
.app-nav button[aria-current="page"] { color: var(--accent); }

.nav-badge {
  position: absolute;
  top: 8px;
  left: 50%;
  margin-left: 4px;
  min-width: 17px;
  height: 17px;
  padding: 0 4px;
  border-radius: 9px;
  /* The only persistent sign that cards are waiting, so it must stay legible
     in dark mode too. */
  background: var(--danger-solid);
  color: #fff;
  font-size: 10px;
  line-height: 17px;
  font-weight: 700;
}

/* ---------- Typography ---------- */

h2 { font-size: 20px; font-weight: 660; margin: 0 0 4px; }
h3 { font-size: 15px; font-weight: 650; margin: 20px 0 8px; color: var(--ink-2); }
.muted { color: var(--ink-2); font-size: 14px; margin: 0 0 16px; }
.tiny { font-size: 12px; color: var(--ink-3); }

/* A bare inline link is a ~16px tap target; give standalone ones real height.
   On the detail screen these are the primary actions — tap to call, tap to
   email — so they need to be comfortably hittable, not just legible. */
.tiny > a,
.field > a {
  display: inline-block;
  padding: 14px 0;
  /* An inline-block shrink-to-fits to its max-content width, and
     `overflow-wrap: break-word` does not reduce intrinsic size — so without
     this cap a long URL makes the box itself wider than the screen. */
  max-width: 100%;
}

/* ---------- Buttons ---------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: var(--tap);
  padding: 0 18px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  text-decoration: none;
}

.btn:active { transform: translateY(1px); }
.btn[disabled] { opacity: 0.5; pointer-events: none; }
.btn--primary { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.btn--danger { color: var(--danger); border-color: var(--danger); background: var(--danger-bg); }
.btn--ghost { background: transparent; border-color: transparent; color: var(--accent); }
.btn--block { display: flex; width: 100%; }
/* Still the minimum comfortable tap target — smaller only in look, not in hit
   area. Apple asks for 44pt, Android for 48dp. */
.btn--sm { min-height: 44px; padding: 0 14px; font-size: 14px; }

.btn-row { display: flex; gap: 10px; flex-wrap: wrap; }
.btn-row .btn { flex: 1 1 140px; }

/* ---------- Capture screen ---------- */

.capture {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
  /* The safe-area insets add to the header and nav, so take them off too, or
     a notched iPhone scrolls a screen that has nothing more to show. The vh
     line is for iOS before 15.4, which has no dvh (deployment target is 14). */
  min-height: calc(100vh - var(--header-h) - var(--nav-h) - var(--safe-t) - var(--safe-b) - 60px);
  min-height: calc(100dvh - var(--header-h) - var(--nav-h) - var(--safe-t) - var(--safe-b) - 60px);
  justify-content: center;
  text-align: center;
}

.capture__hint {
  color: var(--ink-2);
  font-size: 15px;
  max-width: 30ch;
  margin: 0;
}

.shutter {
  width: 148px;
  height: 148px;
  border-radius: 50%;
  border: 0;
  background: var(--accent);
  color: var(--accent-ink);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font: inherit;
  font-size: 15px;
  font-weight: 700;
  cursor: pointer;
  /* color-mix needs iOS 16.2; older iPhones keep the default blue glow. */
  box-shadow: 0 10px 28px -8px rgba(37, 99, 235, 0.65);
  box-shadow: 0 10px 28px -8px color-mix(in srgb, var(--accent) 65%, transparent);
}

.shutter svg { width: 42px; height: 42px; }
.shutter:active { transform: scale(0.97); }

.tag-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: var(--tap);
  padding: 0 18px;
  border-radius: 24px;
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--ink-2);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}

.tag-chip strong { color: var(--ink); font-weight: 700; }

/* ---------- Cards / list ---------- */

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px;
  margin-bottom: 12px;
}

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

/* A busy show is hundreds of cards, each row carrying a thumbnail. This lets
   the browser skip layout and paint for rows that are scrolled out of view.
   The intrinsic size keeps the scrollbar honest so scrolling does not jump.
   Unsupported browsers simply ignore it and render as before. */
.contact-list > li {
  content-visibility: auto;
  contain-intrinsic-size: auto 80px;
}

.contact-item {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-height: 72px;
  padding: 10px 12px;
  margin-bottom: 8px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  /* start, not left: a dir="auto" Arabic name inside must be able to align right. */
  text-align: start;
  font: inherit;
  color: inherit;
  cursor: pointer;
}

.contact-item__thumb {
  width: 54px;
  height: 40px;
  flex: none;
  border-radius: 6px;
  object-fit: cover;
  background: var(--surface-2);
  border: 1px solid var(--border);
}

.contact-item__body { flex: 1; min-width: 0; }
.contact-item__name { font-weight: 650; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.contact-item__meta {
  font-size: 13px;
  color: var(--ink-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pill {
  display: inline-block;
  padding: 1px 8px;
  border-radius: 10px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  font-size: 11px;
  font-weight: 600;
  color: var(--ink-2);
}

/* ---------- Forms ---------- */

.field { margin-bottom: 14px; }

.field > label {
  display: block;
  font-size: 13px;
  font-weight: 650;
  color: var(--ink-2);
  margin-bottom: 5px;
}

input[type="text"],
input[type="tel"],
input[type="email"],
input[type="url"],
input[type="search"],
input[type="password"],
select,
textarea {
  width: 100%;
  min-height: var(--tap);
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  /* 16px minimum stops iOS Safari zooming the page on focus */
  font-size: 16px;
}

textarea { min-height: 90px; resize: vertical; line-height: 1.4; }

input:focus-visible, select:focus-visible, textarea:focus-visible,
.btn:focus-visible, button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

input[type="color"] {
  width: 64px;
  height: var(--tap);
  padding: 2px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface);
}

/* The native file button is small and its height cannot be set directly, so
   give the control room around it instead. */
input[type="file"] {
  width: 100%;
  padding: 12px 0;
  font-size: 15px;
}

.row-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }

.repeat-row { display: flex; gap: 8px; margin-bottom: 8px; align-items: center; }
.repeat-row input { flex: 1; min-width: 0; }
.repeat-row select { flex: 0 0 108px; width: 108px; }

.icon-btn {
  flex: none;
  width: var(--tap);
  height: var(--tap);
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface);
  color: var(--ink-2);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
}

.switch-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: var(--tap);
}

/* The label is `for`-associated, so stretching it to fill the row makes the
   whole row toggle the switch rather than just the 22px box. */
.switch-row label {
  flex: 1;
  display: flex;
  align-items: center;
  min-height: var(--tap);
  cursor: pointer;
}

.switch-row input[type="checkbox"] { width: 24px; height: 24px; flex: none; accent-color: var(--accent); }

/* ---------- Banners ---------- */

.banner {
  display: block;
  padding: 12px 14px;
  border-radius: var(--radius-sm);
  font-size: 14px;
  margin-bottom: 14px;
  border: 1px solid transparent;
}

/* The pending banner is a button, so it needs the button reset too. */
button.banner {
  width: 100%;
  text-align: left;
  font: inherit;
  cursor: pointer;
  min-height: var(--tap);
}

.banner--warn { background: var(--warn-bg); border-color: var(--warn-border); color: var(--warn); }
.banner--danger { background: var(--danger-bg); border-color: var(--danger); color: var(--danger); }
.banner--ok { background: var(--ok-bg); border-color: var(--ok); color: var(--ok); }
.banner strong { display: block; }

/* ---------- Review screen ---------- */

.review-photo {
  width: 100%;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--surface-2);
  display: block;
  max-height: 34vh;
  max-height: 34dvh;
  object-fit: contain;
}

.photo-strip { display: flex; gap: 8px; margin-bottom: 16px; }
.photo-strip > * { flex: 1; min-width: 0; }

/* Save sits under the thumb, not 2.5 screens down the form. The negative side
   margins exactly cancel .app-main's padding so the bar spans the screen; the
   bottom nav is hidden on review and the toast sits above the bar. */
.review-actions {
  position: sticky;
  bottom: 0;
  z-index: 10;
  margin: 12px calc(-16px - var(--safe-r)) 0 calc(-16px - var(--safe-l));
  padding: 10px calc(16px + var(--safe-r)) calc(10px + var(--safe-b)) calc(16px + var(--safe-l));
  background: var(--bg);
  border-top: 1px solid var(--border);
}

/* While a field has focus the bar drops back into the flow, so with the
   keyboard up it can never cover what is being typed. */
#review-form:focus-within ~ .review-actions { position: static; }

/* ---------- Scanning state ---------- */

.scanning { text-align: center; padding: 24px 0; }

.scanning img {
  width: 100%;
  max-width: 320px;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  margin-bottom: 20px;
}

.spinner {
  width: 30px;
  height: 30px;
  margin: 0 auto 12px;
  border: 3px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .spinner { animation-duration: 2.4s; } }

/* ---------- Empty state ---------- */

.empty { text-align: center; padding: 44px 16px; color: var(--ink-2); }
.empty svg { width: 46px; height: 46px; opacity: 0.35; margin-bottom: 10px; }

/* ---------- Modal ---------- */

.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 50;
  background: rgba(15, 23, 42, 0.55);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: 12px;
  padding-bottom: calc(12px + var(--safe-b));
}

/* Modal bodies are written with deliberate line breaks (the restore summary,
   for one). Without this HTML collapses them into a single run of text. */
.modal .muted { white-space: pre-line; }

.modal {
  width: 100%;
  max-width: 460px;
  max-height: 80vh;
  max-height: 80dvh;
  overflow-y: auto;
  background: var(--surface);
  border-radius: var(--radius);
  padding: 18px;
}

/* ---------- Toast ---------- */

#toast {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(var(--nav-h) + var(--safe-b) + 14px);
  z-index: 60;
  max-width: calc(100vw - 32px);
  padding: 11px 16px;
  border-radius: 22px;
  background: #0f172a;
  color: #fff;
  font-size: 14px;
  font-weight: 550;
  box-shadow: 0 8px 24px -8px rgba(0, 0, 0, 0.5);
  /* A toast is only ever text. Without this it swallows taps on whatever it
     covers, such as the email field on review. */
  pointer-events: none;
}

/* ---------- List toolbar ---------- */

.list-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 10px;
  min-height: 44px;
}

.list-toolbar p { margin: 0; }
.list-toolbar__actions { display: flex; gap: 8px; flex: none; }

/* ---------- Search ---------- */

.search-row { display: flex; gap: 8px; margin-bottom: 12px; }
.search-row input { flex: 1; min-width: 0; }
.search-row select { flex: 0 0 128px; width: 128px; }

/* ---------- Desktop: usable, not beautiful ---------- */

@media (min-width: 720px) {
  .app-main { max-width: 720px; margin: 0 auto; width: 100%; }
  .app-header { padding-left: 24px; padding-right: 24px; }
}
