@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700;800&display=swap');

/* ============================================================================
   BACKEND PAGE CHROME — every page the BACKEND renders itself.
   ----------------------------------------------------------------------------
   Its users are the six pages authored in this repo (src/pages.js + admin/):
     · sign in / request a reset / set a new password   (the editor host)
     · "No site to edit" owner notice                   (the editor host)
     · the owner admin console                          (the admin host)
     · errorPage(): impersonation link expired · admin console unavailable ·
       owner-gated lab not found
   They all share this sheet on purpose: they are one product surface — the
   account-and-operator chrome — and they are the pages that must still render when
   something else is broken.

   THEY ARE NOT STYLED BY THE FRONTEND, and that is the point. They used to link the
   frontend's css/lazyapp.css, so every editor-token tweak in the design repo landed on
   the sign-in page and the admin console unreviewed, and a rename or a tidy-up there
   could unstyle them outright. Worse, the dependency was structural: sign-in was a side
   effect of static file serving, so with no FRONTEND_DIR there was no sign-in page at
   all. This sheet is served from THIS repo at /backend-assets/css/, mounted in
   server.js unconditionally — the recovery path cannot depend on the tree it recovers.

   It was extracted from lazyapp.css at the point of the split, so these pages look
   exactly as they did — but the two sheets are now INDEPENDENT and are meant to drift.
   The identical --la-* / .la-* names are here only so the pages' markup and their
   inline var() references didn't have to be rewritten in the same pass. They are NOT
   a shared contract: do NOT "de-duplicate" this file against the frontend's, and do
   NOT copy an editor rule in just because the editor has one. Only what these pages
   actually use belongs here.

   Scope — narrow, and in three parts. Keep this list true when you add or retire a rule:
   it is the first thing the next reader checks, and a stale one sends them looking for a
   class that isn't here.

     CONTROLS       .la-app · .la-panel (+ --alert / --notice / --form) · .la-hint
                    .la-label · .la-title (+ --sm / --xs) · .la-link (+ --ink)
                    .la-field · .la-input (+ select) · .la-btn (+ --primary / --coral /
                    --full) · .la-msg (+ --error / --ok) · .la-tag (+ --live / --draft /
                    --addon / --demo)
     PAGE STRUCTURE .la-page / .la-page__card (+ --narrow) · .la-console · .la-topbar
                    (+ __group / __logo) · .la-main (+ __col / __col--wide) · .la-footer
                    .la-notice__head / __body · .la-formgrid (+ --thirds / __label)
                    .la-span-all · .la-formhead · .la-inputgroup · .la-actions
                    .la-formfoot · .la-switchrow · .la-listhead · .la-list
                    .la-property (+ __meta / __tags / __actions / __thumb)
     ACCOUNT VIEW   .la-searchbar · .la-searchbox (+ __clear) · .la-dash · .la-workarea
                    .la-workspace · .la-sidebar (+ __head) · .la-accounts
                    .la-account (+ __name / __sub) · .la-pane (+ __head) · .la-tabs
                    .la-tab · .la-empty · .la-scrim · .la-modal (+ __head) · .la-shot
     SKIN LIBRARY   .la-skinlayout · .la-skinshot · .la-skinside
                    .la-skinslots · .la-skinslot (+ __shot / __name / __plus)
                    .la-skinconfirm (+ __shot)
     RECOVER        .la-modal--recover · .la-slider (+ __range / __gaps / __gap /
                    __marks / __mark (+ --on) / __ends)
                    .la-readout · .la-restorepoints · .la-restorepoint
                    .la-summary (+ __row / __k / __v)
     UTILITIES      .la-fg-line / -coral / -teal · .la-mb-* / .la-mt-* (see why at the foot)
   ============================================================================ */
:root{
  /* colour */
  --la-paper:  #ffffff;   /* surfaces            */
  --la-bg:     #eceae4;   /* app background      */
  --la-ink:    #1a1a1a;   /* text                */
  --la-line:   #1a1a1a;   /* frames — match ink  */
  --la-teal:   #33c4bf;   /* accent — teal: hover, focus, primary action           */
  --la-coral:  #ff6f5e;   /* accent — coral: commits something (delete, suspend)   */
  --la-amber:  #f0a500;   /* accent — amber: demo accounts (neither live nor error) */
  --la-on-amber:#1a1a1a;  /* dark ink on an amber fill */
  --la-on-coral:#1a1a1a;  /* dark ink on a coral fill  */
  /* SELECTED — the fill for a control that is currently on (aria-pressed). A deep jade,
     dark enough to carry white text (AAA). */
  --la-selected:    #00594C;
  --la-on-selected: #ffffff;
  --la-press:  16%;       /* lighten amount on a filled button's hover / active */
  /* muted ink — placeholders and disabled controls, mixed from the ink so both
     themes work from one declaration */
  --la-muted:  color-mix(in oklab, var(--la-line), var(--la-paper) 55%);

  /* type — ONE TEXT SIZE for everything the operator READS (labels, buttons, inputs,
     hints); the aliases all point at it. Headings and the status badges are the exceptions,
     and they are a scale of their own below. */
  --la-font:        'Space Grotesk', system-ui, sans-serif;
  --la-text:        16px;
  --la-text-label:  var(--la-text);
  --la-text-hint:   var(--la-text);
  --la-text-sm:     var(--la-text);
  --la-weight:        700;
  --la-weight-strong: 800;
  --la-track:       .5px;   /* button/label tracking */

  /* HEADING SCALE — three steps, by how much of the page the heading owns:
       title      a page or a panel      ("Application settings", "Sign in")
       title--sm  a section within one   ("Guest chatbot limits", the top bar's name)
       title--xs  a subsection label     ("Contact", "Property address")
     Every .la-title used to carry its size inline — all eleven of them — which left
     --la-text-title's old 23px value referenced by the rule and used by nothing. These are
     the sizes the pages were already asking for, now stated once. */
  --la-text-title:     20px;
  --la-text-title-sm:  18px;
  --la-text-title-xs:  15px;
  /* Status badge (LIVE · DRAFT · CHATBOT) — small caps-ish and deliberately below the
     one-text-size rule: it labels a row, it isn't something to read. */
  --la-text-tag:       10.5px;

  /* form */
  --la-frame:      1.5px;  /* border width        */
  --la-radius:     0px;    /* flat: sharp corners */
  --la-pad-btn:    9px 14px;
  --la-pad-input:  9px 11px;
  --la-control-h:  40px;   /* one height for inputs and buttons */
  --la-trans:      .1s ease;
  --la-caret:       7px;   /* select caret — a flat solid triangle, drawn in CSS */
  --la-caret-inset: 13px;  /* its distance from the field's right edge */

  /* Hard offset shadow — the flat kit's one lift. Not a blur: a solid block dropped down
     and right in the panel's own accent colour, so an admin notice reads as raised without
     introducing soft shadows the rest of the sheet doesn't have. Admin.dc.html's three
     highlighted panels set the colour inline and take the distance from here. */
  --la-shadow-size: 4px;

  /* spacing scale — the steps the pages' own layout actually lands on */
  --la-s1: 4px;
  --la-s2: 8px;
  --la-s3: 12px;
  --la-field-gap: var(--la-s3);   /* label → its control */

  /* single-card page — one measure, one drop from the top and one side gutter, so a notice
     lands where the sign-in panel lands rather than dead-centre in the viewport */
  --la-page-card-width: 420px;
  --la-page-card-drop:  12vh;
  --la-page-gutter:     16px;

  /* the console's search bar — a reading measure, not the workspace's full width */
  --la-search-w: 520px;
  /* The property card's published-site preview. 160px is WIDTHS.card in containers/app/
     src/thumb.js — the width the image is actually stored at — so the box is the picture's
     own size and the browser never has to scale it. Change one and change the other. */
  --la-property-thumb-w: 160px;
}

/* Own the body reset. Every page here is a full-bleed dark surface, so the browser's 8px
   body margin shows as a pale frame around it. The three pages in src/pages.js each used to
   carry their own inline <style>body{margin:0}</style>; the admin console never did — it was
   getting the reset from the FRONTEND's DC runtime (support.js injects
   "html,body{height:100%;margin:0}"), i.e. exactly the hidden dependency the stylesheet
   split removed. Declared once, here, so the console keeps its edges on its own. */
html, body { margin:0; }

/* ---------- DARK THEME (every backend page sets data-theme="dark" on .la-app) ---------- */
[data-theme="dark"]{
  --la-paper:  #202227;   /* panels             */
  --la-bg:     #141518;   /* app background     */
  --la-ink:    #ecebe6;   /* text (light)       */
  --la-line:   #ecebe6;   /* frames — match ink */
}

.la-app { font-family:var(--la-font); color:var(--la-ink); background:var(--la-bg); }

/* Scrollbars are DRAWN, in the kit's own ink. They used to be suppressed everywhere here —
   "furniture the operator never operates" — which held while the console was one page and the
   wheel was the only gesture anyone used on it. It stopped holding when the account list and
   the property pane became scrollers of their own: a bar is how an inner scroller says it has
   more in it, and it is the only way to reach that on a pointing device with no wheel.
   Stated rather than left to the platform, because macOS's own bars are overlays that fade
   out after a moment — and a bar that disappears is the thing being fixed. Thin, on no track,
   so it reads as one more flat mark on the panel instead of a strip of browser chrome. */
.la-app, .la-app * { scrollbar-width:thin; scrollbar-color:var(--la-muted) transparent; }

/* Keyboard focus — one ring for every control on the page. */
.la-app :focus-visible, .la-btn:focus-visible { outline:2px solid var(--la-teal); outline-offset:2px; }

.la-panel {
  background:var(--la-paper);
  border:var(--la-frame) solid var(--la-line);
  border-radius:var(--la-radius);
}

.la-title { font-family:var(--la-font); font-weight:var(--la-weight-strong); font-size:var(--la-text-title); text-transform:uppercase; letter-spacing:.3px; color:var(--la-ink); }
.la-title--sm { font-size:var(--la-text-title-sm); }
.la-title--xs { font-size:var(--la-text-title-xs); }
.la-label { font-family:var(--la-font); font-weight:var(--la-weight); font-size:var(--la-text-label); text-transform:uppercase; letter-spacing:.8px; color:var(--la-ink); }
.la-hint  { font-family:var(--la-font); font-size:var(--la-text-hint); line-height:1.4; color:var(--la-line); }

/* ---------- FORM FIELDS ---------- */
.la-field { display:flex; flex-direction:column; gap:var(--la-field-gap); }
.la-input {
  font-family:var(--la-font); font-size:var(--la-text-label); color:var(--la-ink);
  background:var(--la-paper);
  border:var(--la-frame) solid var(--la-line);
  border-radius:var(--la-radius);
  padding:var(--la-pad-input); width:100%; box-sizing:border-box;
  height:var(--la-control-h);
  transition:border-color var(--la-trans);
}
.la-input:focus { outline:none; border-color:var(--la-teal); }
.la-input::placeholder { color:var(--la-muted); opacity:1; }

/* A <select> wearing .la-input still painted the browser's own grey chrome and hairline
   arrow — one control in a different kit from every field beside it. Strip it and draw the
   caret as a flat triangle from two gradients, same ink and same square idiom as the rest.
   Matched on the ELEMENT, not a modifier class, so a select added later can't miss it.
   color-scheme:dark because every page here is dark: without it the native option list
   pops open white-on-white. */
select.la-input {
  appearance:none; -webkit-appearance:none;
  color-scheme:dark;
  cursor:pointer;
  padding-right:calc(var(--la-caret-inset) + var(--la-caret) * 2 + var(--la-s2));
  background-image:
    linear-gradient(45deg,  transparent 50%, var(--la-ink) 50%),
    linear-gradient(135deg, var(--la-ink) 50%, transparent 50%);
  background-size:var(--la-caret) var(--la-caret), var(--la-caret) var(--la-caret);
  background-position:
    right calc(var(--la-caret-inset) + var(--la-caret)) center,
    right var(--la-caret-inset) center;
  background-repeat:no-repeat;
}
select.la-input:hover { border-color:var(--la-teal); }
select.la-input option { color:var(--la-ink); background:var(--la-paper); }

/* ---------- BUTTONS ----------
   neutral   paper fill, line border  → any secondary action
   primary   jade fill                → the one action a page is FOR (Sign in, Create
                                        tenant, Save settings). Same fill as "on" below,
                                        because both mean "this is the loaded one".
   pressed   jade fill                → an on/off control (aria-pressed), e.g. Settings
   coral     coral fill               → an action that COMMITS destructively
   disabled  muted ink on paper       → one treatment, no hover, not clickable

   .la-btn--primary replaces a .la-btn--teal that these pages wore for a long time and that
   was never defined in ANY sheet. Two things followed from that. The sign-in button and the
   owner notice's link rendered as plain secondary buttons — the primary action on the page
   looked like the way out of it. And the console's primaries got their fill by carrying a
   hard-coded aria-pressed="true", i.e. a screen reader announced "Create tenant, toggle
   button, pressed" about a button that toggles nothing. Naming the fill fixes both: the
   modifier says what it does (not what colour it happens to be — it isn't teal), and
   aria-pressed goes back to meaning a real on/off state. */
.la-btn {
  font-family:var(--la-font); font-weight:var(--la-weight); font-size:var(--la-text-label); text-transform:uppercase; letter-spacing:var(--la-track);
  background:var(--la-paper); color:var(--la-ink);
  border:var(--la-frame) solid var(--la-line);
  border-radius:var(--la-radius);
  padding:var(--la-pad-btn); cursor:pointer; transition:border-color var(--la-trans), background var(--la-trans), color var(--la-trans);
  /* alignment backbone: every button is one height, contents vertically centred */
  display:inline-flex; align-items:center; justify-content:center; gap:var(--la-s2); line-height:1;
  min-height:var(--la-control-h); box-sizing:border-box;
}
.la-btn:hover { border-color:var(--la-teal); }
/* One declaration for both, so "primary" and "on" can never drift apart. */
.la-btn--primary,
.la-btn[aria-pressed="true"]       { background:var(--la-selected); color:var(--la-on-selected); border-color:var(--la-selected); }
.la-btn--primary:hover,
.la-btn[aria-pressed="true"]:hover { background:color-mix(in oklab, var(--la-selected), white var(--la-press)); color:var(--la-on-selected); border-color:var(--la-selected); }
.la-btn--coral       { background:var(--la-coral); color:var(--la-on-coral); border-color:var(--la-coral); }
.la-btn--coral:hover { background:color-mix(in oklab, var(--la-coral), black var(--la-press)); border-color:var(--la-coral); color:var(--la-on-coral); }
.la-btn:disabled, .la-btn[disabled] {
  background:var(--la-paper); color:var(--la-muted); border-color:var(--la-muted);
  cursor:not-allowed;
}
.la-btn:disabled:hover, .la-btn[disabled]:hover { border-color:var(--la-muted); }
/* An <a> wearing .la-btn is still an anchor, so it underlines by default — a rule straight
   through a button's label. Every page had its own inline text-decoration:none for this (or
   was simply underlined and wrong). */
a.la-btn { text-decoration:none; }

/* ---------- LINKS ----------
   Was declared inline four times over two files — color:var(--la-teal);text-decoration:none
   on the console's tenant URLs, and a two-letter .lk class in the sign-in page's own <style>
   block. One treatment, named, so the next page doesn't invent a fifth.
     .la-link       teal — a link that leaves for somewhere else (a tenant's live site)
     .la-link--ink  ink  — a link that IS the thing it sits on (the tenant name heading),
                           where teal would read as a second, competing action */
.la-link { color:var(--la-teal); text-decoration:none; cursor:pointer; }
.la-link:hover { text-decoration:underline; }
.la-link--ink { color:var(--la-ink); }

/* ---------- STATUS BADGE ----------
   What a property row says about itself: LIVE · DRAFT · CHATBOT · CHATBOT OFF. These wore
   .la-swatch — a colour-chip class that gave them only a border — and then re-declared the
   whole badge treatment inline, five properties deep, four times over. Nothing else in the
   console ever used .la-swatch, so it's gone: there was no colour chip, only this.
   ONE geometry for all five, and colour is the only thing a variant changes — because the
   badges sit shoulder to shoulder in a row, where any other difference reads as a mistake
   rather than a meaning. DEMO used to be the odd one out on three counts at once: 16px
   against 10.5px, inheriting weight 400 against 700, and 8px of side padding against 7px.
   It had its own rule (.la-demo-tag) purely because it was written at a different time. */
.la-tag {
  display:inline-flex; align-items:center;
  padding:1px 7px;
  font-size:var(--la-text-tag); font-weight:var(--la-weight); letter-spacing:.6px;
  border:var(--la-frame) solid var(--la-ink); border-radius:var(--la-radius);
}
/* What the row is saying, one modifier each — it used to be spelled out in a style
   attribute, so the markup said "teal background" where it meant "this site is live".
   The add-on is deliberately outlined and hairline-thin: it is sold per PROPERTY and
   must never compete with LIVE/DRAFT, which describe that property's publishing. */
.la-tag--live       { background:var(--la-teal); color:var(--la-paper); }
.la-tag--draft      { color:var(--la-line); }
.la-tag--addon      { color:var(--la-teal); border:1px solid var(--la-teal); }

/* Amber: not live, not an error. Carried by the ACCOUNT now, not by a site — the window
   belongs to the person. The fill is repainted by JS ([data-lv-bg]) — coral once the demo
   has expired — which still wins over this rule. */
.la-tag--demo       { background:var(--la-amber); color:var(--la-on-amber); }

/* ---------- SINGLE-CARD PAGE ----------
   A page that is nothing but one panel: sign in, the owner notice, an error. All three
   built this shape inline; the two older ones now share it too (the note that used to sit
   here said "convert them when they're next touched" — this is that). The drop from the
   top and the side gutter are the sign-in page's own values, so nothing about the page a
   tenant actually meets has moved. */
.la-page {
  min-height:100vh; box-sizing:border-box;
  display:flex; align-items:flex-start; justify-content:center;
  padding:var(--la-page-card-drop) var(--la-page-gutter) var(--la-page-gutter);
}
.la-page__card { width:100%; max-width:var(--la-page-card-width); padding:22px; }
.la-page__card--narrow { max-width:380px; }   /* sign in: a two-field form needs less */

/* Form feedback under a field — the error/confirmation line. Holds its height whether or
   not it has text, so a message appearing never pushes the button out from under the
   cursor mid-click. Was five inline copies of the same three declarations. */
.la-msg { min-height:18px; }
.la-msg--error { color:var(--la-coral); }
.la-msg--ok    { color:var(--la-teal); }
/* The quiet link line under a form ("Forgot your password?"). */
.la-formfoot { margin-top:14px; text-align:center; }

/* ============================================================================
   PAGE STRUCTURE — the console's layout, which used to live in style="" attributes
   ----------------------------------------------------------------------------
   These are components, not utilities: each one is a thing the console HAS (a top bar, a
   property card, a switch row), named for what it is, carrying exactly the values it carried
   inline. Several are used once — a page has one top bar — and they are still classes,
   because a nine-declaration layout blob in an attribute is what a stylesheet is for. What
   stayed inline is the genuinely singular: a logo's height, one panel's margin.

   Three inline `display` values also stayed on purpose, and must: pages.js toggles the
   sign-in views with `el.style.display = shown ? '' : 'none'`, and clearing an inline value
   falls back to the CLASS — so a .la-hidden class would make those views unopenable. The
   console's #pm-address-group is the same story (JS writes 'contents'/'none').
   ============================================================================ */
/* Exactly the window, not "at least" it. The two lists scroll INSIDE their panels
   (.la-sidebar / .la-pane below), and a column free to grow past the viewport would give
   them nothing definite to be the height of — they would run on and the page would scroll
   instead, which is the one thing that must not happen here: an operator comparing an
   account's properties against the list of accounts cannot scroll one out of sight to
   reach the other. */
.la-console { height:100vh; display:flex; flex-direction:column; }
.la-topbar {
  display:flex; align-items:center; justify-content:space-between; gap:14px;
  padding:15px 22px; flex-shrink:0;
  background:var(--la-paper); border-bottom:var(--la-frame) solid var(--la-line);
}
.la-topbar__group { display:flex; align-items:center; gap:var(--la-s3); }
.la-topbar__logo  { height:32px; object-fit:contain; display:block; }
/* One scrolling column, centred, with the page's measure. The console's account view is
   two columns side by side, so it gets a wider measure — 880px was chosen for a single
   stack of tenant cards and squeezes a sidebar plus a card to nothing. */
/* min-height:0 on every link of the chain below: a flex item's floor is its CONTENT, so
   without it each one refuses to be shorter than the list inside it and the height never
   reaches the panels. .la-main still scrolls itself — the settings panel is taller than the
   window on a short screen, and that one IS a page to scroll. */
.la-main      { flex:1; min-height:0; display:flex; justify-content:center; padding:26px 22px; overflow:auto; }
.la-main__col { width:100%; max-width:880px; display:flex; flex-direction:column; min-height:0; }
.la-main__col--wide { max-width:1180px; }

/* The version stamp: what release, which build, and the commit of each repo the image was
   built from (src/build-stamp.js). The console's third flex child, so it is pinned under
   .la-main's scroller rather than living at the end of it — an operator reporting a fault
   should not have to scroll a list of accounts to find out what they are looking at.
   flex-shrink:0 for the same reason the topbar has it: a short window must take its height
   out of the scroller, never out of this.
   Muted, and in the topbar's own paper so the console is framed top and bottom by the same
   surface. It is a fact to look up, not a thing to read on the way past. */
.la-footer {
  flex-shrink:0; display:flex; justify-content:center;
  padding:var(--la-s2) 22px;            /* 22px = the topbar's and .la-main's side gutter */
  background:var(--la-paper); border-top:var(--la-frame) solid var(--la-line);
}
/* One line, always. A commit is unreadable broken across two, and a wrapped footer would
   push the console's own height about; on a narrow window it scrolls sideways instead. */
/* Its own type rather than .la-hint's, which it would otherwise have to override for the
   colour — and would win only by sitting further down this file. Nothing here should depend
   on rule order that a reorganisation can quietly reverse. */
.la-footer__line {
  font-family:var(--la-font); font-size:var(--la-text-hint); line-height:1.4;
  color:var(--la-muted); white-space:nowrap; overflow-x:auto; max-width:100%;
}

/* A panel raised by the flat kit's offset block, in the colour of what it's telling you:
   coral for a failure, teal for a result you asked for. --notice and --form keep their
   padding with the instance (they hold very different amounts); --alert carries its own,
   because it is one shape — a single line of failure text — rendered in more than one
   place now: the console's own banner and, when a dialog is open, inside that dialog. */
.la-panel--alert  { border-color:var(--la-coral); box-shadow:var(--la-shadow-size) var(--la-shadow-size) 0 var(--la-coral); padding:11px 14px; }
.la-panel--notice { border-color:var(--la-teal);  box-shadow:var(--la-shadow-size) var(--la-shadow-size) 0 var(--la-teal); }
.la-panel--form   { padding:20px; }   /* a panel holding a full form: settings, a property dialog */
.la-notice__head  { display:flex; align-items:center; justify-content:space-between; gap:10px; margin-bottom:var(--la-s2); }
.la-notice__body  { color:var(--la-ink); line-height:1.8; }

/* The two-column form grid, and the full-width subheadings that sit inside it. */
.la-formgrid        { display:grid; grid-template-columns:1fr 1fr; gap:14px; }
.la-formgrid--thirds{ grid-template-columns:1fr 1fr 1fr; }
.la-formgrid__label { grid-column:1 / -1; margin-top:var(--la-s2); color:var(--la-line); }
.la-span-all        { grid-column:1 / -1; }
.la-formhead        { display:flex; align-items:center; margin-bottom:16px; }
/* An input with a button welded to it (password + Gen). */
.la-inputgroup      { display:flex; gap:var(--la-s2); }
/* A row of buttons closing a form or a panel. */
.la-actions         { display:flex; align-items:center; gap:var(--la-s2); margin-top:18px; }

/* A setting that is one switch: label + hint on the left, the toggle on the right. Its
   border colour is repainted by JS (data-lv-borderc), which still wins over this rule. */
.la-switchrow {
  display:flex; align-items:center; justify-content:space-between; gap:var(--la-s3);
  padding:11px 13px; border:var(--la-frame) solid var(--la-line);
}
/* Compact by CONTEXT, not by a modifier the author has to remember: the only button in a
   switch row is its switch, and the only buttons in a property's row are its actions.
   Stating it here means a button added to either place is already the right size. */
.la-switchrow .la-btn         { padding:6px 12px; }
.la-property__actions .la-btn { padding:6px 10px; }

/* The property list and one property's card. Named .la-property, not .la-tenant: a tenant
   used to BE a site, and the card now describes one of the several a landlord may own. */
.la-listhead          { display:flex; align-items:center; justify-content:space-between; gap:var(--la-s3); margin-bottom:var(--la-s3); }
.la-list              { display:flex; flex-direction:column; gap:var(--la-s3); }
/* min-height so the preview beside the meta has something to stretch to: a card with only a
   name and a date would otherwise give it a sliver. */
.la-property          { padding:14px 16px; display:flex; align-items:center; gap:14px; flex-wrap:wrap; min-height:132px; }
.la-property__meta    { flex:1; min-width:200px; }
.la-property__tags    { display:flex; align-items:center; gap:9px; }
/* The actions wrap WITHIN their own column rather than dropping the whole block onto a line
   of its own. Five buttons is past what fits beside the meta, and the unconstrained block
   pushed itself under the preview — which cost the preview the card's height, since
   align-self:stretch fills a flex LINE, not a wrapped card. Capped, the buttons stack in the
   right-hand column, the card grows, and the picture grows with it. */
.la-property__actions { display:flex; gap:var(--la-s2); flex-wrap:wrap; flex:0 1 232px; align-content:flex-start; }

/* The published-site preview.
   IT RESERVES ITS SPACE. The width is stated, and 5:8 gives the height from it, so the box is
   the same size before the picture arrives as after — and the same size as the empty box on a
   property that has no picture at all.

   It used to be `flex:0 0 auto; align-self:stretch; min-height:112px`, on the reasoning that
   the height should come from the CARD so a preview never left a gap on a tall one. None of
   that was doing anything. `auto` basis means the width came from the CONTENTS, and with
   aspect-ratio deriving the height from that width, the height was definite — so stretch and
   min-height were both dead, and the size depended on what happened to be inside the box:

     image loaded                163 x 261
     empty ("Not published")     127 x 192
     image present, not yet sized 70 x 184

   Which made the list reflow as pictures landed, one card at a time, in network order. Open
   the console and catch it mid-flight and the previews are visibly different sizes with no
   pattern to it — because the pattern is which images have arrived.

   5:8 because the shot is a TALL slice of a desktop layout — the point of going vertical is
   to show the hero AND what follows it, which a wide thumbnail cannot. */
.la-property__thumb {
  flex:0 0 var(--la-property-thumb-w); aspect-ratio:5/8;
  padding:0; overflow:hidden; cursor:pointer; box-sizing:border-box;
  background:var(--la-bg); border:var(--la-frame) solid var(--la-line);
}
.la-property__thumb img    { display:block; width:100%; height:100%; object-fit:cover; }
.la-property__thumb--empty { display:flex; align-items:center; justify-content:center;
                             text-align:center; padding:var(--la-s2); cursor:default; }
/* Demo is the one empty state that is not a missing thing — it is what the account IS, and
   it can never become a picture. It takes the amber the console already spends on demo
   accounts (.la-tag--demo, --la-amber), so the card and the sidebar badge say the same thing
   in the same colour instead of inventing a second vocabulary for it. */
.la-property__thumb--empty[data-state="demo"] { border-color:var(--la-amber); color:var(--la-amber); }
/* Narrow: the row has already wrapped, so a full-height sliver beside stacked text is worse
   than a fixed-width one at the top of it. */
@media (max-width: 620px) { .la-property__thumb { align-self:flex-start; width:96px; } }

/* The preview at stored size, inside .la-modal (already max-width:640px — the image width). */
.la-shot { display:block; width:100%; height:auto; border:var(--la-frame) solid var(--la-line); }

/* ---------- THE SKIN LIBRARY'S SLOT PICKER ----------
   Which slot in the shared library a property's design goes into. Taller than the other
   dialogs and scrolls INSIDE itself: the library only grows, so the list is the one part
   here with no natural end, and letting the dialog grow instead would push Save off-screen
   on the day it stops fitting. */
/* Wide enough for a 640px preview beside a column of slots. A flex COLUMN so the two-column
   row can be told to shrink while the head and the buttons keep their size — the mistake to
   avoid here is a dialog that opens to a picture with Save below the fold. */
.la-modal--skin { max-width:1000px; max-height:92vh; display:flex; flex-direction:column; }
.la-skinlayout { display:flex; gap:var(--la-s3); align-items:flex-start; min-height:0; flex:1 1 auto; }

/* The preview at its own scale — 1024px is the stored image's natural height, so it is never
   upscaled past what was actually captured. The vh half is what keeps the dialog usable on a
   laptop: below a tall screen the picture gives way, rather than the controls beside it. */
.la-skinshot {
  flex:0 1 auto; display:block; width:auto; max-width:62%;
  max-height:min(1024px, 74vh); object-fit:contain; object-position:top;
  border:var(--la-frame) solid var(--la-line);
}

/* Name on top, slots under it — and only the slots scroll, so the name field and the question
   above them stay put while the list moves. The list is the one part with no natural end. */
.la-skinside { flex:1 1 300px; min-width:0; min-height:0; display:flex; flex-direction:column;
               align-self:stretch; }
/* Two columns and 3:4 cards, mirroring .la-skingrid in the editor's Skin pane: the operator
   is choosing a slot in the grid the host will end up looking at, so it should look like it. */
.la-skinslots {
  display:grid; grid-template-columns:1fr 1fr; gap:var(--la-s3);
  overflow-y:auto; min-height:0; flex:1 1 auto;
  padding:2px; margin-bottom:var(--la-s3);
}
.la-skinslot {
  display:flex; flex-direction:column; gap:var(--la-s2);
  padding:var(--la-s2); cursor:pointer; text-align:center;
  background:var(--la-bg); color:var(--la-ink);
  border:var(--la-frame) solid var(--la-line); border-radius:var(--la-radius);
}
.la-skinslot:hover { border-color:var(--la-muted); }
/* The chosen slot, in the same teal the console spends on a selected account — one
   vocabulary for "this is the one you picked". */
.la-skinslot[aria-pressed="true"] { border-color:var(--la-teal); color:var(--la-teal); }
/* 3:4, the editor's card shape — the same reasoning as .la-skincard there: a whole page
   stays legible in half a column, and a page is taller than it is wide. */
.la-skinslot__shot { display:block; width:100%; aspect-ratio:3/4; object-fit:cover;
                     object-position:top; border:var(--la-frame) solid var(--la-line); }
.la-skinslot__shot--empty { background:var(--la-bg); }
.la-skinslot__name { display:block; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
/* "New Skin" is the only slot with nothing to show, so the plus stands where a picture
   would be rather than beside the name — it reads as an empty frame, which is what it is. */
.la-skinslot--new .la-skinslot__plus {
  display:flex; align-items:center; justify-content:center;
  width:100%; aspect-ratio:3/4;
  border:var(--la-frame) dashed var(--la-line); border-radius:50% / 8%;
}
/* The overwrite warning sits over the picker, so it is narrower — a dialog the same width
   as the one behind it reads as a replacement rather than a question about it. */
.la-modal--confirm { max-width:420px; }
.la-skinconfirm { display:flex; gap:var(--la-s3); align-items:flex-start; margin-bottom:var(--la-s3); }
.la-skinconfirm__shot { flex:0 0 auto; width:80px; aspect-ratio:5/8; object-fit:cover;
                        border:var(--la-frame) solid var(--la-line); }

/* ---------- RECOVER FROM BACKUP ----------
   doc/alex-working-docs/RESTORES_UI_SPEC.md. A moment on a timeline, and afterwards a
   report — the only one there is, since a recovery changes nothing the property card shows.

   The slider is drawn from the same flat vocabulary as every other control here: a square
   thumb one border-width thick, no rounding, no gradient. It is a control for choosing a
   POINT, so the track is a hairline and the thumb is what the eye lands on. */
.la-modal--recover { max-width:680px; }

.la-slider { position:relative; }
.la-slider__range {
  -webkit-appearance:none; appearance:none;
  display:block; width:100%; height:var(--la-s3); margin:0; background:transparent; cursor:pointer;
}
.la-slider__range::-webkit-slider-runnable-track { height:var(--la-frame); background:var(--la-line); }
.la-slider__range::-moz-range-track             { height:var(--la-frame); background:var(--la-line); }
.la-slider__range::-webkit-slider-thumb {
  -webkit-appearance:none; appearance:none;
  width:var(--la-s3); height:18px; margin-top:-9px;
  background:var(--la-selected); border:var(--la-frame) solid var(--la-line); border-radius:var(--la-radius);
}
.la-slider__range::-moz-range-thumb {
  width:var(--la-s3); height:18px;
  background:var(--la-selected); border:var(--la-frame) solid var(--la-line); border-radius:var(--la-radius);
}
.la-slider__range:focus-visible { outline:2px solid var(--la-teal); outline-offset:4px; }

/* The seams between Litestream generations — periods the replica cannot reach. Coral,
   because coral is this sheet's "something is being committed / something is wrong" accent
   and a dead zone is the second. Positioned imperatively (Admin.dc.html decorateRecover):
   the geometry is data, and a style hole is empty during the placeholder phase. */
.la-slider__gaps { position:relative; height:var(--la-s1); margin-bottom:var(--la-s1); }
.la-slider__gap  { position:absolute; top:0; height:100%; min-width:2px; background:var(--la-coral); }

/* Where the daily copies fall inside the window, under the track — the same information the
   rows below carry, placed where it can be read WHILE dragging. Teal, not coral: these are
   points that can be restored, the opposite of a dead zone, and the sheet's two accents
   already mean exactly that. The selected one grows rather than changing colour, so the tick
   and the row it belongs to agree without needing a third colour. */
.la-slider__marks { position:relative; height:9px; margin-top:2px; }
.la-slider__mark  { position:absolute; top:0; width:var(--la-frame); height:5px;
                    margin-left:calc(var(--la-frame) / -2); background:var(--la-teal); }
.la-slider__mark--on { height:9px; width:3px; margin-left:-1.5px; }

.la-slider__ends { display:flex; justify-content:space-between; gap:var(--la-s3); margin-top:var(--la-s1); }

.la-readout { display:flex; flex-direction:column; }

/* The daily copies, when the moment asked for is older than the replica reaches. Three at
   most — the closest, its neighbour before and its neighbour after — so they sit in a row
   and stack only when there is no width for it. */
.la-restorepoints { display:flex; gap:var(--la-s2); flex-wrap:wrap; }
.la-restorepoint {
  flex:1 1 170px; display:flex; flex-direction:column; align-items:flex-start; gap:0;
  padding:10px 12px; text-align:left; cursor:pointer;
  font-family:var(--la-font); color:var(--la-ink);
  background:var(--la-paper); border:var(--la-frame) solid var(--la-muted); border-radius:var(--la-radius);
  transition:border-color var(--la-trans), color var(--la-trans);
}
.la-restorepoint:hover                  { border-color:var(--la-muted); color:var(--la-teal); }
.la-restorepoint[aria-pressed="true"]   { border-color:var(--la-teal); color:var(--la-teal); }

/* The report. A key/value list rather than prose: an operator reads it to answer specific
   questions ("did the saved design come back", "who got signed out"), and prose makes each
   of those a search. Keys stay one column wide so the values line up down the panel. */
.la-summary       { display:flex; flex-direction:column; gap:var(--la-s1); }
.la-summary__row  { display:grid; grid-template-columns:150px minmax(0, 1fr); gap:var(--la-s3); align-items:start; }
.la-summary__k    { text-transform:uppercase; letter-spacing:.8px; }
.la-summary__v    { overflow-wrap:anywhere; }
@media (max-width: 560px) { .la-summary__row { grid-template-columns:1fr; gap:0; } }

/* ---------- THE ONE SEARCH ----------
   Above BOTH lists rather than inside the sidebar, because it reaches into both: it filters
   the accounts on the left and finds a property on the right. Held to a reading measure and
   not the console's full 1180px — a text input that wide reads as a page banner instead of
   as a field. */
.la-searchbar { max-width:var(--la-search-w); }
/* The field and its × share one box, so the clear sits INSIDE the input's frame rather than
   arriving as a second control beside it. */
.la-searchbox { position:relative; }
/* Room for the × at the end of the line, so a long term never runs under it. */
.la-searchbox .la-input { padding-right:var(--la-control-h); }
/* WebKit draws its own clear affordance on type="search" — a grey circle in a kit with no
   circles, and a second × next to the one below. */
.la-searchbox .la-input::-webkit-search-cancel-button { -webkit-appearance:none; display:none; }
/* Muted until pointed at: it is the way OUT of a search, never the reason to look at the
   box. Square and the control's own height, so its hit area matches every other button
   here even though it carries no frame. */
.la-searchbox__clear {
  position:absolute; top:0; right:0;
  width:var(--la-control-h); height:var(--la-control-h);
  display:flex; align-items:center; justify-content:center;
  padding:0; background:none; border:none; cursor:pointer;
  font-family:var(--la-font); font-size:var(--la-text-title); line-height:1;
  color:var(--la-muted); transition:color var(--la-trans);
}
.la-searchbox__clear:hover { color:var(--la-ink); }

/* ---------- THE ACCOUNT WORKSPACE ----------
   Accounts on the left, the selected one's detail on the right. The split is the whole
   point of the redesign: the console's subject is now a PERSON who may rent out several
   cottages, and a flat list of sites could not say which of them belonged together.
   Collapses to one column on a narrow screen — the sidebar is a list, so it stacks. */
/* Height carriers, no look of their own: the wrappers the markup already had between the
   column and the workspace, now passing the window's height down to it. */
.la-dash, .la-workarea { display:flex; flex-direction:column; flex:1; min-height:0; }
/* stretch, not start: both panels are the full height of the row, which is the whole point —
   each one scrolls its own contents rather than the page scrolling past both. */
.la-workspace { display:grid; grid-template-columns:300px minmax(0, 1fr); gap:var(--la-s3); align-items:stretch; flex:1; min-height:0; }
/* One column on a narrow screen, and there the PAGE scrolls again: two half-height panels
   stacked would each be a peephole. */
@media (max-width: 860px) {
  .la-console   { height:auto; min-height:100vh; }
  .la-main      { overflow:visible; }
  .la-workspace { grid-template-columns:1fr; align-items:start; }
  .la-sidebar, .la-pane { overflow:visible; }
}

/* Each panel scrolls its own list. The head and the accounts scroll TOGETHER — pinning the
   head would cost a row of the list to keep a title that never changes. */
.la-sidebar        { padding:14px; overflow:auto; }
.la-sidebar__head  { display:flex; align-items:center; justify-content:space-between; gap:var(--la-s2); margin-bottom:var(--la-s3); }
/* No height of its own any more: it used to cap at 62vh — a figure that fits no particular
   window — and scroll inside itself while the panel around it stayed short. The panel is now
   the window's height and does the scrolling, so the list simply runs as long as it is. */
.la-accounts       { display:flex; flex-direction:column; gap:2px; }

/* One account row. The left border is the selection: a fill would put a third weight of
   colour next to the DEMO badge it sits beside, and the badge has to stay the loudest
   thing in the row. Transparent when unselected so nothing shifts when it lights up. */
.la-account {
  display:block; width:100%; text-align:left; cursor:pointer;
  padding:9px 11px; background:none; font-family:var(--la-font); color:var(--la-ink);
  border:none; border-left:3px solid transparent;
}
.la-account:hover          { border-left-color:var(--la-muted); }
.la-account[aria-current="true"] { border-left-color:var(--la-teal); background:color-mix(in oklab, var(--la-paper), var(--la-teal) 8%); }
.la-account__name  { display:flex; align-items:center; gap:var(--la-s2); flex-wrap:wrap; }
/* Block, not inline: these are spans (a <button> may not contain a <div>), and an inline
   box would drop the margin that separates the three lines of the row. */
.la-account__sub   { display:block; margin-top:3px; }

/* The detail pane's tabs. Flat and underlined rather than buttons: they select a view,
   they do not commit anything, and .la-btn's filled treatment is reserved for actions. */
.la-pane      { padding:18px 20px; overflow:auto; }
.la-pane__head{ margin-bottom:var(--la-s3); }
.la-tabs      { display:flex; gap:18px; border-bottom:var(--la-frame) solid var(--la-line); margin-bottom:18px; }
.la-tab {
  padding:0 0 9px; background:none; border:none; cursor:pointer;
  font-family:var(--la-font); font-weight:var(--la-weight); font-size:var(--la-text-label);
  text-transform:uppercase; letter-spacing:.8px; color:var(--la-muted);
  border-bottom:3px solid transparent; margin-bottom:calc(-1 * var(--la-frame));
}
.la-tab:hover                    { color:var(--la-ink); }
.la-tab[aria-selected="true"]    { color:var(--la-ink); border-bottom-color:var(--la-teal); }
.la-empty { padding:34px 20px; text-align:center; }

/* ---------- MODAL ----------
   A property's settings open over the console instead of pushing the list down the page:
   the form is long, and the account it belongs to has to stay on screen behind it. The
   scrim is what makes it modal; the card is an ordinary raised panel, so a dialog looks
   like every other panel in the console rather than like a different application. */
.la-scrim {
  position:fixed; inset:0; z-index:50; display:flex; align-items:flex-start; justify-content:center;
  padding:6vh 16px 16px; overflow:auto;
  background:color-mix(in oklab, #000, transparent 40%);
}
.la-modal      { width:100%; max-width:640px; padding:20px; }
.la-modal__head{ display:flex; align-items:center; justify-content:space-between; gap:10px; margin-bottom:16px; }

/* ---------- the few utilities the pages genuinely repeat ----------
   Ink, and vertical rhythm. The margins are px-named on purpose: the console's spacing has
   never followed a scale (2·3·4·6·8·10·12·14·16·18·20·22·26px all appear), so a name like
   --step-3 would imply a system that isn't there. Putting them ON a scale means moving
   pixels, which is a change to propose, not to smuggle into a refactor. */
.la-fg-line  { color:var(--la-line); }
.la-fg-coral { color:var(--la-coral); }
.la-fg-teal  { color:var(--la-teal); }
.la-btn--full { width:100%; }
.la-mb-4  { margin-bottom:var(--la-s1); }
.la-mb-6  { margin-bottom:6px; }
.la-mb-8  { margin-bottom:var(--la-s2); }
.la-mb-10 { margin-bottom:10px; }
.la-mb-12 { margin-bottom:var(--la-s3); }
.la-mb-16 { margin-bottom:16px; }
.la-mb-18 { margin-bottom:18px; }
.la-mb-20 { margin-bottom:20px; }
.la-mt-2  { margin-top:2px; }
.la-mt-3  { margin-top:3px; }
.la-mt-4  { margin-top:var(--la-s1); }
.la-mt-5  { margin-top:5px; }
.la-mt-6  { margin-top:6px; }
.la-mt-12 { margin-top:var(--la-s3); }
.la-mt-18 { margin-top:18px; }
