/* Layout and components for every page in the app.
   Colours, type and shape live in themes/, which is what makes the brand
   swappable. This file assumes those variables exist and never defines one.

   It used to be a <style> block inside index.html. It moved out so the public
   report page can use the same components without a second copy of them, and
   because a stylesheet that three separate rules have already lost to on
   source order is easier to reason about as a file than as a block in the
   middle of a document. */
/* Vercel theme (21st.dev/community/themes/vercel): monochrome, black primary
   on near-white, pure black in dark mode, Geist, 0.5rem radius, faint shadows.
   Semantic colours for condition and cleanliness are kept, because an
   inspector has to tell "good" from "damaged" at a glance, but they are
   desaturated to sit inside a neutral palette. */
* { box-sizing: border-box; }
/* .btn sets display, which beats the user agent's rule for [hidden] and
   leaves anything hidden still on screen. The queue pill spent a week
   showing "0" in every header because of this. */
[hidden] { display: none !important; }
html, body { margin: 0; background: var(--bg); color: var(--ink); font: 15px/1.5 var(--font); -webkit-font-smoothing: antialiased; }
a { color: inherit; text-decoration: none; }
button, input, select, textarea { font: inherit; color: inherit; }
h1, h2, h3 { font-family: var(--font-display); font-weight: 600; letter-spacing: -.02em; margin: 0; }
h1 { font-size: 26px; } h2 { font-size: 20px; } h3 { font-size: 16px; }
code, .mono { font-family: var(--font-mono); font-size: .92em; }
.muted { color: var(--ink-2); } .small { font-size: 13px; } .tiny { font-size: 12px; color: var(--ink-3); }

/* Layout */
#app { min-height: 100dvh; display: flex; flex-direction: column; }
header.top { position: sticky; top: 0; z-index: 20; background: color-mix(in srgb, var(--bg) 88%, transparent); backdrop-filter: blur(12px); border-bottom: 1px solid var(--line); }
header.top .in { max-width: 1080px; margin: 0 auto; padding: 10px 16px; display: flex; align-items: center; gap: 12px; position: relative; }
header.top .brand { font-family: var(--font-display); font-weight: 600; font-size: 16px; letter-spacing: -.02em; display: flex; align-items: center; }
/* One copy of the lockup serves both schemes: the two brand fills are
   variables, so dark mode gets the supplied invert artwork rather than a
   washed-out version of the colour one. */
.brand-logo { display: var(--brand-logo, block); height: 40px; width: auto; aspect-ratio: 850.39 / 340.16; }
.brand-text { display: var(--brand-text, none); }
.vh { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
header.top .spacer { flex: 1; }

/* Mobile nav.
   The header has to hold a logo, a status pill that must never be hidden,
   and three controls. On a 390px phone that is one row too many, so the
   controls fold into a menu and the pill keeps its place: knowing photos
   are still on the phone is not something to bury behind a tap. */
/* These all have to out-specify .btn and .row, which are defined further
   down the sheet and would otherwise win on source order alone. */
header.top .burger { display: none; width: 40px; height: 40px; padding: 0; place-items: center; flex: none; }
header.top .burger span { display: block; width: 18px; height: 2px; background: currentColor; border-radius: 2px; position: relative; transition: transform .18s, opacity .18s; }
header.top .burger span::before, .burger span::after { content: ''; position: absolute; left: 0; width: 18px; height: 2px; background: currentColor; border-radius: 2px; transition: transform .18s; }
header.top .burger span::before { top: -6px; } .burger span::after { top: 6px; }
header.top .burger[aria-expanded="true"] span { background: transparent; }
header.top .burger[aria-expanded="true"] span::before { transform: translateY(6px) rotate(45deg); }
header.top .burger[aria-expanded="true"] span::after { transform: translateY(-6px) rotate(-45deg); }

header.top .nav-panel { position: absolute; right: 12px; top: calc(100% + 6px); width: max-content; min-width: 200px; max-width: calc(100vw - 24px); background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow-lg); padding: 6px; display: none; z-index: 30; }
header.top .nav-panel.open { display: block; }
header.top .nav-panel > * { display: flex; align-items: center; width: 100%; min-height: 44px; padding: 0 12px; border: 0; background: none; border-radius: var(--radius-s); text-align: left; cursor: pointer; font: inherit; color: inherit; }
header.top .nav-panel > *:hover { background: var(--bg-3); }
header.top .nav-panel hr { min-height: 0; height: 1px; padding: 0; margin: 6px 4px; background: var(--line); border: 0; }

@media (max-width: 639px) {
  header.top .nav-inline { display: none; }
  header.top .burger { display: grid; }
  /* The pill keeps the dot and the count; the words are in the panel it
     opens, so a narrow header never pushes the logo off the screen. */
  header.top .queue-pill .full { display: none; }
  /* 44px rather than the 40px it gets on a desktop. The header is the one
     place on a phone with room to spare, and the lockup's strapline stops
     being legible much below this. */
  header.top .brand-logo { height: 44px; }
}
@media (min-width: 640px) { header.top .queue-pill .short { display: none; } }
main { flex: 1; max-width: 1080px; width: 100%; margin: 0 auto; padding: 16px 16px calc(24px + var(--safe-b)); }
.page-head { display: flex; align-items: flex-start; gap: 12px; margin-bottom: 16px; flex-wrap: wrap; }
.page-head .grow { flex: 1; min-width: 200px; }
.crumb { font-size: 13px; color: var(--ink-2); display: inline-flex; gap: 6px; align-items: center; margin-bottom: 6px; }
.crumb a:hover { color: var(--accent); }

/* Components */
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; padding: 10px 16px; border-radius: var(--radius-s); border: 1px solid var(--line); background: var(--bg-2); cursor: pointer; font-weight: 500; min-height: 42px; white-space: nowrap; }
.btn:hover { border-color: var(--ink-3); }
.btn.primary { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
.btn.danger { color: var(--bad); }
.btn.ghost { background: transparent; border-color: transparent; }
.btn.sm { padding: 6px 10px; min-height: 34px; font-size: 13px; }
.btn.block { width: 100%; }
.btn:disabled { opacity: .5; cursor: default; }
.btn.icon { padding: 8px; width: 42px; }
.card { background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow); }
.card.pad { padding: 16px; }
.stack > * + * { margin-top: 12px; }
.row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.grid { display: grid; gap: 12px; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); }
.field { display: flex; flex-direction: column; gap: 6px; }
.field label { font-size: 13px; font-weight: 500; color: var(--ink-2); }
/* Applies to every control, not only those inside .field. A bare textarea
   (the inspection summary, for one) was falling back to the browser default,
   which is white and unreadable in dark mode. */
input:not([type=checkbox]):not([type=radio]):not([type=file]), select, textarea {
  padding: 10px 12px; border: 1px solid var(--line); border-radius: var(--radius-s);
  background: var(--bg-2); color: var(--ink); min-height: 42px; width: 100%;
}
textarea { min-height: 84px; resize: vertical; }
input::placeholder, textarea::placeholder { color: var(--ink-3); }
input:focus, select:focus, textarea:focus { outline: 2px solid var(--accent); outline-offset: 1px; border-color: transparent; }
input:disabled, select:disabled, textarea:disabled { opacity: .6; }
.fields { display: grid; gap: 12px; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
.fields .full { grid-column: 1 / -1; }
.pill { display: inline-flex; align-items: center; gap: 6px; padding: 3px 9px; border-radius: 999px; font-size: 12px; font-weight: 500; background: var(--bg-3); color: var(--ink-2); }
.pill.ok { background: var(--ok-soft); color: var(--ok); } .pill.warn { background: var(--warn-soft); color: var(--warn); } .pill.bad { background: var(--bad-soft); color: var(--bad); } .pill.accent { background: var(--accent-soft); color: var(--accent); }
.tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--line); margin-bottom: 16px; overflow-x: auto; scrollbar-width: none; }
.tabs::-webkit-scrollbar { display: none; }
.tabs a { padding: 10px 12px; border-bottom: 2px solid transparent; color: var(--ink-2); font-weight: 500; white-space: nowrap; }
.tabs a.active { color: var(--ink); border-color: var(--accent); }
.list { display: flex; flex-direction: column; }
.list > .item { display: flex; align-items: center; gap: 12px; padding: 12px 16px; border-top: 1px solid var(--line); }
.list > .item:first-child { border-top: 0; }
.list > a.item:hover { background: var(--bg-3); }
.list .item .grow { flex: 1; min-width: 0; }
.empty { padding: 32px 16px; text-align: center; color: var(--ink-2); }
.toast { position: fixed; left: 50%; bottom: calc(20px + var(--safe-b)); transform: translateX(-50%); background: var(--ink); color: var(--bg); padding: 10px 16px; border-radius: 999px; font-size: 14px; z-index: 50; box-shadow: var(--shadow); max-width: 90vw; }
.toast.bad { background: var(--bad); color: #fff; }
.modal-bg { position: fixed; inset: 0; background: rgba(0,0,0,.45); z-index: 40; display: flex; align-items: flex-end; justify-content: center; }
.modal { background: var(--bg-2); width: 100%; max-width: 560px; max-height: 92dvh; overflow: auto; border-radius: 16px 16px 0 0; padding: 20px 16px calc(20px + var(--safe-b)); }
@media (min-width: 640px) { .modal-bg { align-items: center; } .modal { border-radius: 16px; padding: 24px; } }
.modal h2 { margin-bottom: 16px; }
.actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 16px; flex-wrap: wrap; }

/* Property cards */
.prop-card { display: block; overflow: hidden; }
.prop-card .cover { aspect-ratio: 4/3; background: var(--bg-3); display: grid; place-items: center; color: var(--ink-3); overflow: hidden; }
.prop-card .cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.prop-card .body { padding: 12px 14px; }

/* Media */
.thumbs { display: grid; gap: 8px; grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); }
.thumb { position: relative; aspect-ratio: 1; border-radius: var(--radius-s); overflow: hidden; background: var(--bg-3); border: 1px solid var(--line); }
.thumb img, .thumb video { width: 100%; height: 100%; object-fit: cover; display: block; }
.thumb .tag { position: absolute; left: 6px; bottom: 6px; }
.thumb .prog { position: absolute; inset: 0; display: grid; place-items: center; background: rgba(0,0,0,.35); color: #fff; font-size: 12px; font-weight: 600; }
.thumb .del { position: absolute; top: 4px; right: 4px; width: 26px; height: 26px; border-radius: 50%; border: 0; background: rgba(0,0,0,.55); color: #fff; cursor: pointer; display: grid; place-items: center; }
/* In-app camera. Full bleed so the viewfinder is the whole screen and the
   shutter falls under a thumb rather than in the middle of the page. */
.cam { position: fixed; inset: 0; z-index: 90; background: #000; display: grid; grid-template-rows: auto 1fr auto auto; }
.cam video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.cam-flash { position: absolute; inset: 0; background: #fff; opacity: 0; pointer-events: none; transition: opacity .12s; }
.cam-flash.on { opacity: .85; }
.cam-top { position: relative; display: flex; align-items: center; gap: 12px; padding: max(12px, env(safe-area-inset-top)) 16px 12px; background: linear-gradient(rgba(0,0,0,.55), transparent); }
.cam-count { color: #fff; font-size: 13px; font-weight: 600; flex: 1; }
.cam-top .btn { background: rgba(255,255,255,.15); color: #fff; border-color: rgba(255,255,255,.3); }
.cam-chip { position: relative; align-self: end; justify-self: center; padding: 0 16px 12px; }
.cam-chip .btn { background: rgba(0,0,0,.6); color: #fff; border-color: rgba(255,255,255,.35); }
.cam-comment { display: flex; gap: 8px; background: rgba(0,0,0,.75); padding: 8px; border-radius: var(--radius-s); }
.cam-comment input { min-width: 180px; background: rgba(255,255,255,.12); color: #fff; border-color: rgba(255,255,255,.3); }
.cam-bar { position: relative; display: grid; place-items: center; padding: 20px 0 max(24px, env(safe-area-inset-bottom)); background: linear-gradient(transparent, rgba(0,0,0,.55)); }
.cam-shutter { width: 72px; height: 72px; border-radius: 50%; background: #fff; border: 4px solid rgba(255,255,255,.4); background-clip: padding-box; cursor: pointer; }
.cam-shutter:active { transform: scale(.94); }
.cam-shutter:disabled { opacity: .5; }
/* The review tray. Shots stay here until Done, so a blurred one costs a tap
   rather than an upload and a delete. */
.cam-tray { position: relative; display: flex; gap: 6px; overflow-x: auto; padding: 8px 12px; background: rgba(0,0,0,.45); scrollbar-width: none; }
.cam-tray.empty { display: none; }
.cam-tray::-webkit-scrollbar { display: none; }
.cam-shot { position: relative; flex: none; width: 60px; height: 60px; border-radius: var(--radius-s); overflow: hidden; border: 2px solid transparent; }
.cam-shot.has-cap { border-color: var(--ok); }
.cam-shot img { width: 100%; height: 100%; object-fit: cover; display: block; cursor: pointer; }
.cam-shot .del { position: absolute; top: 1px; right: 1px; width: 20px; height: 20px; border-radius: 50%; border: 0; background: rgba(0,0,0,.7); color: #fff; font-size: 11px; cursor: pointer; display: grid; place-items: center; padding: 0; }

/* Picking loose photos to file against a room. */
.thumbs.pick { grid-template-columns: repeat(auto-fill, minmax(92px, 1fr)); max-height: 46vh; overflow-y: auto; }
.thumb.pick { cursor: pointer; }
.thumb.pick .tick-badge { position: absolute; top: 5px; right: 5px; width: 22px; height: 22px; border-radius: 50%; display: grid; place-items: center; font-size: 12px; background: rgba(0,0,0,.45); color: transparent; border: 2px solid rgba(255,255,255,.8); }
.thumb.pick.on { outline: 3px solid var(--accent); outline-offset: -3px; }
.thumb.pick.on .tick-badge { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
.thumb.pick:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }

.offline-bar { background: var(--warn-soft, #3a2f00); color: var(--ink); border-bottom: 1px solid var(--line); padding: 8px 16px; font-size: 13px; text-align: center; }
.cam-shot.failed { border-color: var(--bad, #c0392b); }
.cam-shot-warn { position: absolute; left: 2px; bottom: 2px; width: 16px; height: 16px; border-radius: 50%; background: var(--bad, #c0392b); color: #fff; font-size: 11px; font-weight: 700; display: grid; place-items: center; }
.thumb.pending .prog { font-size: 11px; text-align: center; padding: 0 4px; }

/* Brand picker in Settings. */
.pick-card { width: 100%; text-align: left; background: none; border: 0; cursor: pointer; }
.pick-card.on { background: var(--accent-soft); }
.swatches { display: flex; flex: none; border-radius: var(--radius-s); overflow: hidden; border: 1px solid var(--line); }
.swatches span { width: 18px; height: 34px; display: block; }

/* Work waiting to reach the server. */
.queue-pill { display: inline-flex; align-items: center; gap: 6px; }
.queue-pill .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--warn, #b58900); flex: none; }
.queue-pill.offline .dot { background: var(--bad, #c0392b); }
.thumb .cap { position: absolute; top: 4px; left: 4px; min-height: 26px; padding: 0 8px; border-radius: 13px; border: 0; background: rgba(0,0,0,.55); color: #fff; font-size: 11px; cursor: pointer; }
.thumb .cap-text { position: absolute; left: 0; right: 0; bottom: 0; padding: 14px 8px 6px; font-size: 11px; line-height: 1.3; color: #fff; background: linear-gradient(transparent, rgba(0,0,0,.75)); }
/* The Video and Set cover pills sit bottom-left, so lift them clear of a caption. */
.thumb.has-cap .tag { bottom: 26px; }
/* The size sits bottom-right, quieter than they are: it is reference, not a
   control. Two pills on one row do not fit a 121px tile, which is what the
   desktop grid gives at its narrowest, so when a tile carries both the size
   lifts clear rather than landing on top of the button. Measured, not
   guessed: side by side they overlapped by 20px. */
.thumb .size { position: absolute; right: 6px; bottom: 6px; background: rgba(0,0,0,.55); color: #fff; border: 0; font-variant-numeric: tabular-nums; }
.thumb:has(.tag) .size { bottom: 34px; }
/* A captioned tile carries its size on the caption line instead, so there is
   no badge to place and a caption that wraps cannot collide with one. */
.cap-size { opacity: .75; font-variant-numeric: tabular-nums; white-space: nowrap; }
.capture-bar { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.capture-bar label { display: flex; align-items: center; justify-content: center; gap: 8px; min-height: 56px; border-radius: var(--radius-s); border: 1px dashed var(--ink-3); cursor: pointer; font-weight: 500; }
.capture-bar label:active { background: var(--bg-3); }
.capture-bar input { display: none; }

/* Grades */
.grades { display: flex; gap: 6px; flex-wrap: wrap; }
.grades button { padding: 8px 12px; border-radius: 999px; border: 1px solid var(--line); background: var(--bg-2); cursor: pointer; font-size: 13px; }
.grades button.on { background: var(--ink); color: var(--bg); border-color: var(--ink); }

.room-row { gap: 8px; }
.room-row .room-link { flex: 1; min-width: 0; gap: 12px; color: inherit; text-decoration: none; align-items: center; margin: -12px -4px -12px -16px; padding: 12px 4px 12px 16px; }
.room-row .room-link:hover { background: var(--bg-3); }
.room-row .tick { width: 28px; height: 28px; border-radius: 50%; border: 2px solid var(--line); display: grid; place-items: center; font-size: 14px; flex: none; }
.room-row .tick.done { background: var(--ok); border-color: var(--ok); color: #fff; }
.lightbox { position: fixed; inset: 0; background: #000; z-index: 60; display: grid; place-items: center; }
.lightbox img, .lightbox video { max-width: 100vw; max-height: 100dvh; }
.lightbox .close { position: absolute; top: 12px; right: 12px; }
.cmp { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
/* Skeletons.
   A sheen rather than a pulse: a block that fades in and out reads as
   something broken, while a sheen crossing left to right reads as loading.
   It is also cheap, one transform on the GPU rather than repainting. */
.sk { background: var(--bg-3); border-radius: 6px; position: relative; overflow: hidden; flex: none; }
.sk::after { content: ''; position: absolute; inset: 0; transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--bg-2) 70%, transparent), transparent);
  animation: sheen 1.25s ease-in-out infinite; }
@keyframes sheen { to { transform: translateX(100%); } }
/* Anyone who has asked their phone to stop animating things means it. The
   shape still does its job standing still. */
@media (prefers-reduced-motion: reduce) { .sk::after { animation: none; } }
/* A photo tile carries the same sheen until its own image has decoded. */
.thumb.loading::before { content: ''; position: absolute; inset: 0; z-index: 1; transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--bg-2) 70%, transparent), transparent);
  animation: sheen 1.25s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) { .thumb.loading::before { animation: none; } }

.spinner { width: 22px; height: 22px; border: 3px solid var(--line); border-top-color: var(--accent); border-radius: 50%; animation: spin .8s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
.center { display: grid; place-items: center; padding: 48px 0; }
.login { max-width: 400px; margin: 10vh auto 0; }
.or { display: flex; align-items: center; gap: 12px; color: var(--ink-3); font-size: 13px; }
.or::before, .or::after { content: ''; flex: 1; height: 1px; background: var(--line); }
/* ---- Phones ------------------------------------------------------------
   Everything below is about a 390px screen held in one hand, usually with
   the other hand holding a torch. Two rules: nothing important may be
   pushed out of reach, and anything that gets tapped has to be big enough
   to hit without looking. */
@media (max-width: 599px) {
  /* Six tabs do not fit. Scrolling them hides the ones off the edge behind
     an affordance nobody sees on a touch screen, so they wrap instead and
     everything stays visible. */
  .tabs { flex-wrap: wrap; overflow-x: visible; row-gap: 0; }

  /* A list row with several actions squeezed its text into whatever the
     buttons left over, which on the meter list was about sixty pixels and
     one word per line. The text takes the first line and the actions drop
     below it. Rows with a single control, like a room and its chevron, are
     left alone. */
  .list > .item:has(.btn ~ .btn) { flex-wrap: wrap; row-gap: 8px; }
  .list > .item:has(.btn ~ .btn) > .grow { flex: 1 0 100%; }

  /* Three columns put a photograph in 110px, which is too small to tell a
     scuff from a shadow and leaves its controls overlapping the picture. */
  .thumbs { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }

  /* 34px is fine under a mouse and mean under a thumb. */
  .btn.sm { min-height: 40px; padding: 8px 12px; }
  .thumb .del { width: 36px; height: 36px; }
  .thumb .cap { min-height: 36px; padding: 0 12px; font-size: 12px; }
  .thumb .tag { padding: 7px 12px; font-size: 12px; }
  .thumb .size { padding: 7px 10px; font-size: 12px; }

  /* The most tapped controls in the app, chosen while standing up. */
  .grades { gap: 8px; }
  .grades button { min-height: 44px; padding: 10px 16px; font-size: 14px; }

  /* A full-width primary action is easier to hit than a right-aligned one,
     and the page head is where the main verb lives. */
  .page-head .row .btn { flex: 1; }
}
