/* ==========================================================================
   Ja-Lee Portal — global stylesheet
   All color via custom properties; dark mode overrides the same tokens.
   Icons are inline SVG data-URIs defined here as CSS vars, applied via
   mask-image so they inherit currentColor (no icon font, no asset files).
   ========================================================================== */

:root{
  /* Light scheme, built from the active brand theme's palette (below).
     Every pairing is checked against WCAG AA in all three themes. */
  --bg:var(--jl-ground);             /* page ground */
  --bg-alt:var(--jl-line);           /* hover fill, neutral badges */
  --surface:var(--jl-ground-deep);   /* cards, sidebar */
  --surface-2:var(--jl-ground);      /* inputs and plain buttons */
  --text:var(--jl-ink);
  --text-muted:var(--jl-ink-soft);   /* not slate: slate misses 4.5:1 on the grounds */
  --text-faint:var(--jl-slate);      /* disabled/decorative only, never body text */
  --border:var(--jl-line);           /* hairlines and dividers (decorative) */
  --border-input:var(--jl-slate);    /* form-control edges need 3:1 (WCAG 1.4.11) */
  --focus:var(--jl-ink);

  /* The accent is a fill, never type: ink sits on it. */
  --accent:var(--jl-accent);
  --accent-text:var(--jl-ink);
  --accent-hover:var(--jl-accent-hover);

  /* Strong emphasis: the current nav item, "new" counts. */
  --strong:var(--jl-ink);
  --strong-text:var(--jl-ground);

  /* Status colours are outside the brand palettes, which define none; the
     accent cannot carry a warning because it is the call to action. */
  --danger:#A93A2C;
  --danger-text:var(--jl-ground);
  --danger-hover:#8E3024;

  --success:#2E6A4B;
  --success-text:var(--jl-ground);

  --warning:var(--jl-ink-soft);
  --warning-text:var(--jl-ground);

  /* Tints for hover/focus washes, and a shadow cast in the theme's ink. */
  --accent-wash:rgba(var(--jl-accent-rgb),.18);
  --accent-wash-strong:rgba(var(--jl-accent-rgb),.32);
  --shadow:rgba(var(--jl-ink-rgb),.28);

  --radius:0.25rem;

  /* Motion. The built-in CSS easings are too weak to read as intentional, so
     these are the stronger custom curves. Durations are named for the moment
     they belong to rather than their length, so call sites stay self-evident. */
  --ease-out:cubic-bezier(0.23,1,0.32,1);
  --ease-in-out:cubic-bezier(0.77,0,0.175,1);
  --dur-hover:120ms;
  --dur-press:160ms;
  --dur-enter:180ms;
  --dur-exit:140ms;
  /* One family does everything, per the brand guide. */
  --font-display:"Outfit",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
  --font-body:"Outfit",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
  /* Only for machine text (JSON, CSS, links) where 0/O and 1/l must differ. */
  --font-mono:ui-monospace,"SF Mono",Menlo,Consolas,"Liberation Mono",monospace;

  /* Translucent surface used when a user background image is set. Alpha is
     kept high enough that body text still clears WCAG AA over any image. */
  --acrylic:rgba(var(--jl-ground-deep-rgb),.84);
  --acrylic-border:rgba(var(--jl-ink-rgb),.2);
  --room-label-bg:rgba(var(--jl-ground-rgb),.9);

  /* icon glyphs — 24x24, stroke-based, applied via mask + currentColor */
  --icon-menu:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'%3E%3Cline x1='3' y1='6' x2='21' y2='6'/%3E%3Cline x1='3' y1='12' x2='21' y2='12'/%3E%3Cline x1='3' y1='18' x2='21' y2='18'/%3E%3C/svg%3E");
  --icon-close:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'%3E%3Cline x1='5' y1='5' x2='19' y2='19'/%3E%3Cline x1='19' y1='5' x2='5' y2='19'/%3E%3C/svg%3E");
  --icon-home:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round' stroke-linecap='round'%3E%3Cpath d='M3 11l9-7 9 7'/%3E%3Cpath d='M5 10v10h14V10'/%3E%3C/svg%3E");
  --icon-layers:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round' stroke-linecap='round'%3E%3Cpolygon points='12,3 21,8 12,13 3,8'/%3E%3Cpolyline points='3,13 12,18 21,13'/%3E%3Cpolyline points='3,18 12,23 21,18'/%3E%3C/svg%3E");
  --icon-door:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round' stroke-linecap='round'%3E%3Crect x='5' y='2' width='14' height='20' rx='1'/%3E%3Ccircle cx='15' cy='12' r='1'/%3E%3C/svg%3E");
  --icon-wrench:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round' stroke-linecap='round'%3E%3Cpath d='M14.7 6.3a4 4 0 0 0-5.4 5.4L3 18v3h3l6.3-6.3a4 4 0 0 0 5.4-5.4l-2.8 2.8-2-2z'/%3E%3C/svg%3E");
  --icon-inbox:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round' stroke-linecap='round'%3E%3Cpolyline points='4,4 4,20 20,20 20,4'/%3E%3Cpolyline points='4,4 20,4'/%3E%3Cpath d='M4 13h4l2 3h4l2-3h4'/%3E%3C/svg%3E");
  --icon-user:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round' stroke-linecap='round'%3E%3Ccircle cx='12' cy='8' r='4'/%3E%3Cpath d='M4 21c0-4.4 3.6-7 8-7s8 2.6 8 7'/%3E%3C/svg%3E");
  --icon-logout:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round' stroke-linecap='round'%3E%3Cpath d='M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4'/%3E%3Cpolyline points='16,17 21,12 16,7'/%3E%3Cline x1='21' y1='12' x2='9' y2='12'/%3E%3C/svg%3E");
  --icon-chevron:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round' stroke-linecap='round'%3E%3Cpolyline points='9,6 15,12 9,18'/%3E%3C/svg%3E");
  --icon-check:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.5' stroke-linejoin='round' stroke-linecap='round'%3E%3Cpolyline points='4,13 9,18 20,6'/%3E%3C/svg%3E");
  --icon-alert:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round' stroke-linecap='round'%3E%3Cpath d='M12 3l10 18H2z'/%3E%3Cline x1='12' y1='10' x2='12' y2='14'/%3E%3Ccircle cx='12' cy='17.3' r='.4' fill='%23000' stroke='none'/%3E%3C/svg%3E");
  --icon-camera:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round' stroke-linecap='round'%3E%3Cpath d='M4 8h3l2-2h6l2 2h3v12H4z'/%3E%3Ccircle cx='12' cy='14' r='4'/%3E%3C/svg%3E");
  --icon-clip:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round' stroke-linecap='round'%3E%3Cpath d='M21 11.5l-8.6 8.6a5.5 5.5 0 0 1-7.8-7.8l8.6-8.6a3.7 3.7 0 0 1 5.2 5.2l-8.6 8.6a1.8 1.8 0 0 1-2.6-2.6l7.9-7.9'/%3E%3C/svg%3E");
  --icon-send:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round' stroke-linecap='round'%3E%3Cpath d='M4 12h15'/%3E%3Cpolyline points='13,6 19,12 13,18'/%3E%3C/svg%3E");
  --icon-file:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round' stroke-linecap='round'%3E%3Cpath d='M6 3h8l5 5v13H6z'/%3E%3Cpolyline points='14,3 14,8 19,8'/%3E%3C/svg%3E");
  --icon-download:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round' stroke-linecap='round'%3E%3Cpath d='M12 4v11'/%3E%3Cpolyline points='7,10 12,15 17,10'/%3E%3Cpath d='M5 20h14'/%3E%3C/svg%3E");
  --icon-plus:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'%3E%3Cline x1='12' y1='5' x2='12' y2='19'/%3E%3Cline x1='5' y1='12' x2='19' y2='12'/%3E%3C/svg%3E");
  --icon-calendar:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round' stroke-linecap='round'%3E%3Crect x='3' y='5' width='18' height='16' rx='1'/%3E%3Cline x1='3' y1='10' x2='21' y2='10'/%3E%3Cline x1='8' y1='2' x2='8' y2='6'/%3E%3Cline x1='16' y1='2' x2='16' y2='6'/%3E%3C/svg%3E");
  --icon-pin:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round' stroke-linecap='round'%3E%3Cpath d='M12 21s-7-7.58-7-12a7 7 0 0 1 14 0c0 4.42-7 12-7 12z'/%3E%3Ccircle cx='12' cy='9' r='2.5'/%3E%3C/svg%3E");
  --icon-settings:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round' stroke-linecap='round'%3E%3Ccircle cx='12' cy='12' r='3.2'/%3E%3Cpath d='M12 2.5v3M12 18.5v3M2.5 12h3M18.5 12h3M5.3 5.3l2.1 2.1M16.6 16.6l2.1 2.1M18.7 5.3l-2.1 2.1M7.4 16.6l-2.1 2.1'/%3E%3C/svg%3E");
}

/* ── brand themes ─────────────────────────────────────────── */
/* The three palettes from the brand guidelines, chosen per user in Settings
   and set as data-brand on <html>. Only these raw colours differ between
   themes; everything above and below is written against them.
   Beyond the eight guideline colours each theme carries:
   - --jl-accent-hover: its accent-deep where ink text still clears 4.5:1 on
     it (Warm), otherwise the nearest blend of accent and accent-deep that does
     (Harbour: Cobalt is 3.79 with ink and 4.3 with white; Garden: Fern 3.95).
   - three dark-scheme surfaces derived from its ink, ink soft and slate. */
/* Warm trust (default): Ink, Ink soft, Slate, Amber, Terracotta, Cream, Cream deep, Rule */
:root{
  --jl-ink:#112230;
  --jl-ink-soft:#2C3F50;
  --jl-slate:#677784;
  --jl-accent:#E1973E;
  --jl-accent-deep:#C9732F;
  --jl-ground:#FAF5ED;
  --jl-ground-deep:#EFE7D9;
  --jl-line:#DAD3C7;
  --jl-accent-hover:#C9732F;
  --jl-dark-field:#1E303F;
  --jl-dark-hover:#415160;
  --jl-dark-edge:#8C969E;
  --jl-ink-rgb:17,34,48;
  --jl-ink-soft-rgb:44,63,80;
  --jl-accent-rgb:225,151,62;
  --jl-ground-rgb:250,245,237;
  --jl-ground-deep-rgb:239,231,217;
}
/* Harbour: Harbour ink, Deep water, Slate, Sky, Cobalt, Mist, Mist deep, Rule */
:root[data-brand="harbour"]{
  --jl-ink:#0E2235;
  --jl-ink-soft:#283F57;
  --jl-slate:#647788;
  --jl-accent:#54B7E7;
  --jl-accent-deep:#347EC4;
  --jl-ground:#F2F8FB;
  --jl-ground-deep:#E0ECF3;
  --jl-line:#CBD8E0;
  --jl-accent-hover:#4195D2;
  --jl-dark-field:#1A3045;
  --jl-dark-hover:#3C5267;
  --jl-dark-edge:#8897A5;
  --jl-ink-rgb:14,34,53;
  --jl-ink-soft-rgb:40,63,87;
  --jl-accent-rgb:84,183,231;
  --jl-ground-rgb:242,248,251;
  --jl-ground-deep-rgb:224,236,243;
}
/* Garden: Forest, Pine, Moss grey, Leaf, Fern, Linen, Linen deep, Rule */
:root[data-brand="garden"]{
  --jl-ink:#112D1E;
  --jl-ink-soft:#294B35;
  --jl-slate:#687A6B;
  --jl-accent:#99CB88;
  --jl-accent-deep:#47935F;
  --jl-ground:#F6F8EF;
  --jl-ground-deep:#E7ECDC;
  --jl-line:#D3D9C7;
  --jl-accent-hover:#5CA169;
  --jl-dark-field:#1D3B29;
  --jl-dark-hover:#3E5C48;
  --jl-dark-edge:#8C9A8C;
  --jl-ink-rgb:17,45,30;
  --jl-ink-soft-rgb:41,75,53;
  --jl-accent-rgb:153,203,136;
  --jl-ground-rgb:246,248,239;
  --jl-ground-deep-rgb:231,236,220;
}

/* Dark scheme. Applied when the device asks for dark AND the user has not
   forced light in Settings, or whenever the user has explicitly chosen dark.
   Built from the active theme's palette, like the light scheme. */
@media (prefers-color-scheme: dark){
  :root:not([data-scheme="light"]){
    --bg:var(--jl-ink);
    --bg-alt:var(--jl-dark-hover);
    --surface:var(--jl-ink-soft);
    --surface-2:var(--jl-dark-field);
    --text:var(--jl-ground);
    --text-muted:rgba(var(--jl-ground-rgb),.74);
    --text-faint:rgba(var(--jl-ground-rgb),.4);
    --border:rgba(var(--jl-ground-rgb),.16);
    --border-input:var(--jl-dark-edge);
    --focus:var(--jl-accent);

    --accent:var(--jl-accent);
    --accent-text:var(--jl-ink);
    --accent-hover:var(--jl-accent-hover);

    --strong:var(--jl-ground);
    --strong-text:var(--jl-ink);

    --danger:#F0A095;
    --danger-text:var(--jl-ink);
    --danger-hover:#F4B5AC;

    --success:#72BD93;
    --success-text:var(--jl-ink);

    --warning:var(--jl-line);
    --warning-text:var(--jl-ink);

    --accent-wash:rgba(var(--jl-accent-rgb),.2);
    --accent-wash-strong:rgba(var(--jl-accent-rgb),.36);
    --shadow:rgba(0,0,0,.45);

    --acrylic:rgba(var(--jl-ink-rgb),.84);
    --acrylic-border:rgba(var(--jl-ground-rgb),.2);
    --room-label-bg:rgba(var(--jl-ink-soft-rgb),.92);
  }
}
:root[data-scheme="dark"]{
  --bg:var(--jl-ink);
  --bg-alt:var(--jl-dark-hover);
  --surface:var(--jl-ink-soft);
  --surface-2:var(--jl-dark-field);
  --text:var(--jl-ground);
  --text-muted:rgba(var(--jl-ground-rgb),.74);
  --text-faint:rgba(var(--jl-ground-rgb),.4);
  --border:rgba(var(--jl-ground-rgb),.16);
  --border-input:var(--jl-dark-edge);
  --focus:var(--jl-accent);

  --accent:var(--jl-accent);
  --accent-text:var(--jl-ink);
  --accent-hover:var(--jl-accent-hover);

  --strong:var(--jl-ground);
  --strong-text:var(--jl-ink);

  --danger:#F0A095;
  --danger-text:var(--jl-ink);
  --danger-hover:#F4B5AC;

  --success:#72BD93;
  --success-text:var(--jl-ink);

  --warning:var(--jl-line);
  --warning-text:var(--jl-ink);

  --accent-wash:rgba(var(--jl-accent-rgb),.2);
  --accent-wash-strong:rgba(var(--jl-accent-rgb),.36);
  --shadow:rgba(0,0,0,.45);

  --acrylic:rgba(var(--jl-ink-rgb),.84);
  --acrylic-border:rgba(var(--jl-ground-rgb),.2);
  --room-label-bg:rgba(var(--jl-ink-soft-rgb),.92);
}

*,*::before,*::after{box-sizing:border-box}
html,body{margin:0;padding:0}
body{
  background:var(--bg);
  color:var(--text);
  font-family:var(--font-body);
  font-size:0.9375rem;               /* brand body: 400 · 15px */
  line-height:1.55;
  -webkit-font-smoothing:antialiased;
  min-height:100vh;
}
h1,h2,h3,h4{
  font-family:var(--font-display);
  font-weight:600;
  
  
  line-height:1.2;
  margin:0 0 .5em;
  color:var(--text);
}
h1{font-size:1.625rem}          /* brand heading: 600 · 26px */
h2{font-size:1.1875rem;font-weight:500}  /* brand subhead: 500 · 19px */
h3{font-size:1.0625rem}
p{margin:0 0 1em}
a{color:var(--text);text-decoration-thickness:0.0938rem;text-underline-offset:0.1875rem}
a:not(.btn):not(.plain){text-decoration:underline}
/* The hidden attribute must win over component display rules (.btn is
   inline-flex), or script that toggles it shows both of a pair at once. */
[hidden]{display:none!important}
img{max-width:100%;display:block}
code{font-family:var(--font-mono);font-size:.9em}

:focus-visible{outline:0.1875rem solid var(--focus);outline-offset:0.125rem}
.sr-only{position:absolute;width:0.0625rem;height:0.0625rem;padding:0;margin:-0.0625rem;overflow:hidden;
  clip:rect(0,0,0,0);white-space:nowrap;border:0}

/* Parked off-canvas by transform rather than by `top`, so revealing it on
   keyboard focus is a GPU composite instead of a layout pass. */
.skip-link{
  position:absolute;left:0.5rem;top:0.5rem;z-index:200;background:var(--accent);color:var(--accent-text);
  padding:0.625rem 1rem;border-radius:var(--radius);font-family:var(--font-display);font-weight:600;
  text-decoration:none;
  transform:translateY(-3.5rem);
  transition:transform var(--dur-enter) var(--ease-out)
}
.skip-link:focus{transform:translateY(0)}

/* ── layout shell ─────────────────────────────────────────── */
.app-shell{display:flex;min-height:100vh}
.app-nav{
  width:13.75rem;flex:none;background:var(--surface);border-right:0.0625rem solid var(--border);
  padding:1.25rem 0.75rem;display:flex;flex-direction:column;gap:0.25rem
}
/* ── logo ─────────────────────────────────────────────────── */
/* Two <img>s, primary and reversed, with CSS showing the one that suits the
   active theme. Same three-way cascade as the colour tokens, so a user who
   forces light or dark in Settings gets the matching artwork, not the OS's.
   2rem tall puts the horizontal lockup at ~131px wide, above the guide's
   120px minimum for it. */
.brand{display:inline-flex;align-items:center;flex:none;border-radius:var(--radius);text-decoration:none}
.logo{display:block;height:2rem;width:auto;max-width:none}
.logo-dark{display:none}
@media (prefers-color-scheme: dark){
  :root:not([data-scheme="light"]) .logo-light{display:none}
  :root:not([data-scheme="light"]) .logo-dark{display:block}
}
:root[data-scheme="dark"] .logo-light{display:none}
:root[data-scheme="dark"] .logo-dark{display:block}

.app-nav .brand{align-self:flex-start;margin:0 0.625rem 1.25rem}
.nav-link{
  display:flex;align-items:center;gap:0.625rem;padding:0.6875rem 0.625rem;border-radius:var(--radius);
  color:var(--text);text-decoration:none;font-size:.95rem;min-height:2.75rem
}
.nav-link.on{background:var(--strong);color:var(--strong-text)}
.nav-link .icon{flex:none}
.app-main{flex:1;min-width:0;padding:1.75rem clamp(1rem,4vw,3rem)}
.app-topbar{
  display:none;align-items:center;justify-content:space-between;gap:1rem;width:100%;
  padding:0.75rem 1rem;background:var(--surface);border-bottom:0.0625rem solid var(--border)
}

/* Signed-out pages (login, invite, reset…) have no nav, only the logo. */
.public-header{padding:1rem clamp(1rem,4vw,3rem) 0}
body.signed-out .app-shell{flex-direction:column}
body.signed-out .app-main{padding-top:1.25rem}

/* Settings: one consistent gap between every section, including the cards
   inside the appearance form. */
/* One column by default; two once the content area is wide enough for two
   ~22rem cards. The breakpoint allows for the 13.75rem sidebar, so between
   the mobile breakpoint and 64rem the cards stay full width rather than
   squeezing. Cards in a row share a height (grid's default stretch), and
   DOM order is reading order in both layouts: Push | Background, Color
   Scheme | Theme, then Advanced CSS and Save across the full width.
   The appearance form uses display:contents so its cards are grid items too;
   its fields still belong to it and submit with it. */
.settings-grid{display:grid;grid-template-columns:minmax(0,1fr);gap:1rem}
.settings-form{display:contents}
.settings-form > .btn{grid-column:1 / -1;justify-self:start}
@media (min-width:64rem){
  .settings-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
  .settings-wide{grid-column:1 / -1}
}
.theme-hints{list-style:none;margin:0.5rem 0 0;padding:0}
.theme-hints li+li{margin-top:0.125rem}

/* ── icons ────────────────────────────────────────────────── */
.icon{
  width:1.2em;height:1.2em;display:inline-block;flex:none;
  background-color:currentColor;
  -webkit-mask:var(--icon) center/contain no-repeat;
  mask:var(--icon) center/contain no-repeat;
}
.icon-lg{width:1.6em;height:1.6em}
.icon-menu{--icon:var(--icon-menu)} .icon-close{--icon:var(--icon-close)}
.icon-home{--icon:var(--icon-home)} .icon-layers{--icon:var(--icon-layers)}
.icon-door{--icon:var(--icon-door)} .icon-wrench{--icon:var(--icon-wrench)}
.icon-inbox{--icon:var(--icon-inbox)} .icon-user{--icon:var(--icon-user)}
.icon-logout{--icon:var(--icon-logout)} .icon-chevron{--icon:var(--icon-chevron)}
.icon-check{--icon:var(--icon-check)} .icon-alert{--icon:var(--icon-alert)}
.icon-camera{--icon:var(--icon-camera)} .icon-plus{--icon:var(--icon-plus)}
.icon-clip{--icon:var(--icon-clip)} .icon-send{--icon:var(--icon-send)}
.icon-file{--icon:var(--icon-file)} .icon-download{--icon:var(--icon-download)}
.icon-calendar{--icon:var(--icon-calendar)}
.icon-pin{--icon:var(--icon-pin)}
.icon-settings{--icon:var(--icon-settings)}

/* ── buttons ──────────────────────────────────────────────── */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:0.5rem;
  min-height:2.75rem;min-width:2.75rem;padding:0.625rem 1.125rem;border-radius:var(--radius);border:0.0625rem solid transparent;
  font-family:var(--font-display);font-weight:600;font-size:.95rem;
  cursor:pointer;text-decoration:none;background:var(--surface-2);color:var(--text);border-color:var(--border);
}
.btn-primary{background:var(--accent);color:var(--accent-text);border-color:var(--accent)}
.btn-danger{background:var(--danger);color:var(--danger-text);border-color:var(--danger)}
.btn-block{width:100%}
.btn:disabled{opacity:.5;cursor:not-allowed}

/* ── cards / panels ───────────────────────────────────────── */
.card{background:var(--surface);border:0.0625rem solid var(--border);border-radius:var(--radius);padding:1.25rem}
.stack{display:flex;flex-direction:column;gap:1rem}
.grid{display:grid;gap:1rem}
.grid-2{grid-template-columns:repeat(auto-fit,minmax(15rem,1fr))}

/* ── badges / status pills ────────────────────────────────── */
.badge{display:inline-flex;align-items:center;gap:0.375rem;padding:0.1875rem 0.625rem;border-radius:6.1875rem;
  font-size:.78rem;font-weight:600;background:var(--bg-alt);color:var(--text)}
.badge-accent{background:var(--accent);color:var(--accent-text)}
.badge-strong{background:var(--strong);color:var(--strong-text)}
.badge-success{background:var(--success);color:var(--success-text)}
.badge-warning{background:var(--warning);color:var(--warning-text)}
.badge-danger{background:var(--danger);color:var(--danger-text)}

/* ── forms ────────────────────────────────────────────────── */
form{max-width:40rem}
.field{margin-bottom:1.125rem}
.field label{display:block;font-weight:600;margin-bottom:0.375rem;font-size:.9rem}
.field .hint{color:var(--text-muted);font-size:.82rem;margin-top:0.25rem}

/* ── map link ─────────────────────────────────────────────── */
.map-link{
  display:inline-flex;align-items:center;gap:0.3125rem;font-size:.85rem;font-weight:600;
  color:var(--text);text-decoration:none;white-space:nowrap
}
.map-link .icon{width:1em;height:1em}
.map-link:focus-visible{text-decoration:underline}
input[type=text],input[type=email],input[type=password],input[type=tel],
input[type=date],input[type=number],input[type=file],select,textarea{
  width:100%;min-height:2.75rem;padding:0.625rem 0.75rem;border-radius:var(--radius);border:0.0625rem solid var(--border-input);
  background:var(--surface-2);color:var(--text);font:inherit;font-size:1rem
}
textarea{min-height:7.5rem;resize:vertical}
fieldset{border:0.0625rem solid var(--border);border-radius:var(--radius);padding:0.875rem;margin:0 0 1.125rem}
legend{font-weight:600;padding:0 0.375rem;font-family:var(--font-display);}
.checkbox-row{display:flex;align-items:center;gap:0.625rem;min-height:2.75rem}
.checkbox-row input{width:1.25rem;height:1.25rem;flex:none}
.field-error{color:var(--danger);font-size:.85rem;margin-top:0.375rem;font-weight:600}

/* ── flash / live region ──────────────────────────────────── */
.flash{padding:0.75rem 1rem;border-radius:var(--radius);margin-bottom:1rem;font-weight:600}
.flash-success{background:var(--success);color:var(--success-text)}
.flash-error{background:var(--danger);color:var(--danger-text)}
.flash-info{background:var(--accent);color:var(--accent-text)}

/* Proof-of-work status line: silent while the check runs in the background,
   and only speaks up if someone submits early or the check can't run. */
.pow-status{margin:0 0 0.75rem;font-size:.82rem;color:var(--text-muted)}
.pow-status:empty{display:none}

/* ── tables ───────────────────────────────────────────────── */
.table-wrap{overflow-x:auto;border:0.0625rem solid var(--border);border-radius:var(--radius)}
table{width:100%;border-collapse:collapse;min-width:30rem}
th,td{text-align:left;padding:0.625rem 0.875rem;border-bottom:0.0625rem solid var(--border)}
th{font-family:var(--font-display);font-size:.82rem;color:var(--text-muted)}
tr:last-child td{border-bottom:0}

/* ── message thread (inbox) ───────────────────────────────── */
.message{padding:0.875rem 1rem;border-radius:var(--radius);background:var(--surface-2);border:0.0625rem solid var(--border)}
.message+.message{margin-top:0.625rem}
.message .meta{font-size:.78rem;color:var(--text-muted);margin-bottom:0.375rem}
.message.from-self{border-color:var(--text-muted)}

/* Photos sit in the thread at the moment they were uploaded, as their own entry. */
.message-photos{
  list-style:none;margin:0;padding:0;
  display:grid;grid-template-columns:repeat(auto-fill,minmax(6.5rem,1fr));gap:0.5rem
}
.message-photos a{display:block;border-radius:var(--radius)}
.message-photos img{
  width:100%;aspect-ratio:1;object-fit:cover;display:block;
  border-radius:var(--radius);border:0.0625rem solid var(--border)
}

/* ── messages grouped by project ─────────────────────────── */
/* Sort and View menus: side by side with the Apply button on their baseline,
   stacking on narrow screens. */
.message-controls{display:flex;flex-wrap:wrap;align-items:flex-end;gap:0.75rem 1rem;margin:0 0 1.25rem;max-width:none}
.message-controls .field{margin:0;flex:1 1 11rem;max-width:16rem}
.message-controls .btn{flex:none}
.project-head{
  display:flex;align-items:flex-start;justify-content:space-between;
  gap:0.5rem 1rem;flex-wrap:wrap;margin-bottom:0.875rem
}
.project-title{min-width:0}
.project-title h2{margin:0;font-size:1.15rem;overflow-wrap:anywhere}
.project-title h2 a{color:inherit}
.project-meta{margin:0.25rem 0 0;font-size:.8rem;color:var(--text-muted)}
.project-badges{display:flex;gap:0.375rem;flex-wrap:wrap}

/* ── mobile bottom-left fixed menu button ─────────────────── */
.menu-toggle{
  display:none;position:fixed;left:1rem;bottom:1rem;z-index:100;
  min-width:5.5rem;min-height:3rem;padding:0.625rem 1.375rem;border-radius:var(--radius);
  background:var(--accent);color:var(--accent-text);border:none;
  font-family:var(--font-display);font-weight:600;font-size:1rem;
  align-items:center;justify-content:center;box-shadow:0 0.25rem 1rem var(--shadow);cursor:pointer
}
.nav-drawer{
  display:none;position:fixed;left:1rem;bottom:4.75rem;z-index:99;width:min(17.5rem,calc(100vw - 2rem));
  background:var(--surface);border:0.0625rem solid var(--border);border-radius:var(--radius);
  box-shadow:0 0.5rem 1.875rem var(--shadow);padding:0.625rem;flex-direction:column;gap:0.125rem;
  max-height:min(60vh,26.25rem);overflow-y:auto;
  /* Scales out of the Menu button in the bottom-left corner rather than out of
     its own centre, and transitions `display` discretely so the close is seen
     instead of the panel blinking out. Exit is quicker than enter: by then the
     user has already decided. */
  opacity:0;transform:translateY(0.5rem) scale(0.97);transform-origin:bottom left;
  transition:opacity var(--dur-exit) var(--ease-out),
             transform var(--dur-exit) var(--ease-out),
             display var(--dur-exit) allow-discrete
}
.nav-drawer.open{
  display:flex;opacity:1;transform:translateY(0) scale(1);
  transition-duration:var(--dur-enter)
}
@starting-style{
  .nav-drawer.open{opacity:0;transform:translateY(0.5rem) scale(0.97)}
}

@media (max-width:53.75rem){
  .app-shell{flex-direction:column}
  .app-nav{display:none}
  .app-topbar{display:flex}
  .app-main{padding:1.25rem 1rem 6rem}
  .menu-toggle{display:flex}
}

/* ── desktop sidebar collapse ─────────────────────────────── */
/* The sidebar still shows by default; this pins its Collapse control to the
   bottom of the column. The button lives inside .app-nav, which is already
   display:none below the breakpoint, so it needs no media query of its own. */
.nav-collapse{
  margin-top:auto;
  display:flex;align-items:center;gap:0.625rem;
  min-height:2.75rem;padding:0.6875rem 0.625rem;
  border:0.0625rem solid var(--border);border-radius:var(--radius);
  background:var(--surface-2);color:var(--text);
  font-family:var(--font-display);font-weight:600;font-size:.85rem;
  text-align:left;cursor:pointer
}
/* The chevron glyph points right; flip it so it reads as "tuck away left". */
.nav-collapse .icon{transform:rotate(180deg)}

/* Collapsed: the sidebar goes away and the same fixed Menu button the mobile
   layout uses takes over, at any width.
   Excluded on the home owner's menu page, which hides the sidebar by design
   and already centres those destinations — a Menu button there would offer a
   "Show sidebar" that could not produce one. */
body.nav-collapsed .app-nav{display:none}
/* With the sidebar gone, the logo moves to a top bar so it still sits top-left. */
body.nav-collapsed .app-shell{flex-direction:column}
body.nav-collapsed .app-topbar{display:flex}
body.nav-collapsed:not(.menu-page) .menu-toggle{display:flex}

/* The drawer's counterpart control, so collapsing is not a one-way door.
   Hidden below the breakpoint, where there is no sidebar to restore. */
.nav-expand{
  margin-top:0.25rem;width:100%;
  display:flex;align-items:center;gap:0.625rem;
  min-height:2.75rem;padding:0.6875rem 0.625rem;
  border:0.0625rem solid var(--border);border-radius:var(--radius);
  background:var(--surface-2);color:var(--text);
  font-family:var(--font-display);font-weight:600;font-size:.85rem;
  text-align:left;cursor:pointer
}
@media (max-width:53.75rem){.nav-expand{display:none}}

/* ── floorplan viewer ─────────────────────────────────────── */
.floor-tabs{display:flex;flex-wrap:wrap;gap:0.5rem;margin-bottom:1rem}
.floor-tab{
  min-height:2.75rem;padding:0.625rem 1.125rem;border-radius:var(--radius);border:0.0625rem solid var(--border);
  background:var(--surface-2);color:var(--text);font-family:var(--font-display);font-weight:600;
  text-decoration:none;font-size:.9rem
}
.floor-tab.on{background:var(--accent);color:var(--accent-text);border-color:var(--accent)}
.floorplan-frame{position:relative;width:100%;max-width:56.25rem;border:0.0625rem solid var(--border);
  border-radius:var(--radius);overflow:hidden;background:var(--surface)}
.floorplan-frame svg{display:block;width:100%;height:100%}
.floorplan-overlay{position:absolute;inset:0}
.room-hit{
  position:absolute;display:flex;align-items:center;justify-content:center;text-align:center;
  padding:0.25rem;text-decoration:none;color:var(--text);font-family:var(--font-display);font-weight:600;
  font-size:clamp(.65rem,1.6vw,.95rem);
  border:0.0625rem solid transparent;border-radius:0.125rem;line-height:1.15
}
.room-hit:focus-visible{background:var(--accent-wash);border-color:var(--accent)}
.room-hit-label{
  background:var(--room-label-bg);padding:0.125rem 0.375rem;border-radius:0.125rem;
  box-shadow:0 0.0625rem 0.1875rem var(--shadow);pointer-events:none
}

/* ── floorplan view switcher + isometric view ─────────────── */
/* Floor selectors on the left, the View control pushed to the upper right so
   the two never crowd each other. */
.floorplan-header{
  display:flex;align-items:flex-start;justify-content:space-between;
  gap:1rem;flex-wrap:wrap;margin-bottom:1rem
}
.floorplan-header .floor-tabs{margin-bottom:0;flex:1 1 auto}
.floorplan-toolbar{
  display:flex;align-items:center;gap:0.625rem;flex:0 0 auto;margin-left:auto
}
.floorplan-toolbar label{font-weight:600;font-size:.9rem}
.floorplan-toolbar select{width:auto;min-width:10rem}
@media (max-width:34rem){
  .floorplan-toolbar{width:100%;margin-left:0}
  .floorplan-toolbar select{flex:1 1 auto}
}

/* The two views are different enough that swapping innerHTML reads as a hard
   cut. Fading through a slight blur bridges them, so the eye sees one drawing
   change rather than two drawings trading places. */
#floorplan-mount{
  transition:opacity var(--dur-exit) var(--ease-out),filter var(--dur-exit) var(--ease-out)
}
#floorplan-mount.swapping{opacity:0;filter:blur(0.125rem)}

.iso-svg{display:block;width:100%;height:100%}
.room-hit-iso{cursor:pointer}
.room-hit-iso .iso-hit{transition:fill .12s}
.room-hit-iso:focus-visible .iso-hit{fill:var(--accent-wash-strong)}
.room-hit-iso:focus-visible{outline:none}
.room-hit-iso:focus-visible .iso-hit{stroke:var(--focus);stroke-width:.5}
.iso-label-bg{fill:var(--room-label-bg)}
.iso-label{
  fill:var(--text);font-family:var(--font-display);font-weight:600;
  pointer-events:none
}

/* ── room photo gallery ───────────────────────────────────── */
.photo-gallery{
  list-style:none;padding:0;margin:0;
  display:grid;grid-template-columns:repeat(auto-fill,minmax(11rem,1fr));gap:0.75rem
}
.photo-tile{
  border:0.0625rem solid var(--border);border-radius:var(--radius);
  background:var(--surface);overflow:hidden;display:flex;flex-direction:column
}
.photo-tile img{width:100%;height:8.5rem;object-fit:cover;display:block}
.photo-tile a:focus-visible img{outline:0.1875rem solid var(--focus);outline-offset:-0.1875rem}
.photo-tile-foot{
  display:flex;align-items:center;justify-content:space-between;gap:0.5rem;
  padding:0.375rem 0.5rem
}
.photo-tile-foot form{margin:0}
.btn-small{
  min-height:2rem;min-width:auto;padding:0.25rem 0.625rem;font-size:.78rem
}

/* ── item category grouping ───────────────────────────────── */
.category-group{margin-bottom:1.25rem}
.category-group h2{
  display:flex;align-items:center;gap:0.5rem;font-size:1rem;padding-bottom:0.375rem;border-bottom:0.0625rem solid var(--border)
}
.item-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(10rem,1fr));gap:0.625rem;margin-top:0.625rem}
.item-tile{
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:0.375rem;
  min-height:5.5rem;padding:0.875rem 0.625rem;border-radius:var(--radius);border:0.0625rem solid var(--border);
  background:var(--surface-2);color:var(--text);text-decoration:none;text-align:center;
  font-family:var(--font-display);font-weight:600;font-size:.85rem
}
.item-tile:focus-visible{border-color:var(--accent);background:var(--accent-wash)}

/* ── home owner main menu ─────────────────────────────────── */
/* The sidebar is hidden on the menu page itself — the same destinations are
   already presented as the centred menu, so repeating them is noise. */
body.menu-page .app-nav{display:none}
/* The logo is centred above the menu itself on this page, so no top bar. */
body.menu-page .app-topbar{display:none}
body.menu-page .app-shell{flex-direction:column}

/* The menu is the whole page, so centre its panel in the viewport instead of
   letting it sit at the top. Auto margins rather than justify-content:center
   because auto margins still let the panel scroll into reach if it ever grows
   taller than the viewport, where centring would clip the top off. */
body.menu-page .app-main{display:flex;flex-direction:column}
body.menu-page .content-surface{margin-top:auto;margin-bottom:auto}

.menu-secondary{display:flex;gap:1rem;justify-content:center;flex-wrap:wrap}
.menu-secondary a{
  display:inline-flex;align-items:center;gap:0.5rem;min-height:2.75rem;padding:0.5rem 0.75rem;
  color:var(--text);font-size:.9rem
}

/* No min-height here any more — the panel hugs its content and the parent
   above does the vertical centring. */
.main-menu{
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:1.75rem;text-align:center;padding:1rem 0
}
.main-menu-head h1{margin-bottom:0.25rem}
/* Vertical lockup, above the guide's 72px minimum at every width. */
.menu-logo{display:flex;justify-content:center;margin:0 auto 1.25rem}
.menu-logo .logo{height:auto;width:clamp(7.5rem,30vw,10rem)}
.main-menu-head .hint{margin:0}
.menu-grid{display:grid;gap:1rem;width:min(26rem,100%)}
.menu-item{
  position:relative;display:flex;align-items:center;justify-content:center;gap:0.75rem;
  min-height:5.5rem;padding:1rem 1.5rem;border-radius:var(--radius);
  border:0.0625rem solid var(--border);background:var(--surface);color:var(--text);
  font-family:var(--font-display);font-weight:600;font-size:1.5rem;
  text-decoration:none
}
.menu-item .icon{width:1.5em;height:1.5em}
.menu-item:focus-visible{background:var(--accent);color:var(--accent-text);border-color:var(--accent)}
.menu-item-badge{position:absolute;top:0.75rem;right:0.75rem;font-size:.8rem}

/* ── user background image + acrylic surfaces ─────────────── */
body.has-bg{background-color:transparent}
body.has-bg::before{
  content:'';position:fixed;inset:0;z-index:-1;
  background-image:var(--user-bg);background-size:cover;background-position:center;background-repeat:no-repeat
}
/* The frosted panels: the whole page-content container plus the chrome that
   sits outside it. Every text element on every page lives inside one of
   these, so nothing is ever left reading directly off the photo. */
body.has-bg .content-surface,
body.has-bg .app-nav,
body.has-bg .app-topbar,
body.has-bg .nav-drawer{
  -webkit-backdrop-filter:blur(1.875rem) saturate(125%);
  backdrop-filter:blur(1.875rem) saturate(125%);
  background-color:var(--acrylic);
  border-color:var(--acrylic-border)
}
body.has-bg .content-surface{
  border:0.0625rem solid var(--acrylic-border);
  border-radius:var(--radius);
  padding:1.25rem
}
/* Main menu only: narrow and centre the panel so more of the background
   image shows on wider viewports. Scoped to the menu page so the wider
   layouts (admin tables, the floorplan, room items) keep their full width. */
body.has-bg.menu-page .content-surface{
  max-width:33rem;
  margin-left:auto;
  margin-right:auto;
  width: 100%;
}

/* Panels *inside* the content surface get the same tint but no filter of
   their own: stacking a second backdrop-filter on an already-filtered
   backdrop blurs twice and turns the area muddy. */
body.has-bg .content-surface .card,
body.has-bg .content-surface .table-wrap,
body.has-bg .content-surface .message,
body.has-bg .content-surface .item-tile,
body.has-bg .content-surface .menu-item,
body.has-bg .content-surface .floorplan-frame,
body.has-bg .content-surface .floor-tab,
body.has-bg .content-surface fieldset,
body.has-bg .content-surface input[type=text],
body.has-bg .content-surface input[type=email],
body.has-bg .content-surface input[type=password],
body.has-bg .content-surface input[type=tel],
body.has-bg .content-surface input[type=date],
body.has-bg .content-surface input[type=number],
body.has-bg .content-surface input[type=file],
body.has-bg .content-surface select,
body.has-bg .content-surface textarea,
body.has-bg .content-surface .btn:not(.btn-primary):not(.btn-danger){
  -webkit-backdrop-filter:none;
  backdrop-filter:none;
  background-color:var(--acrylic);
  border-color:var(--acrylic-border)
}
/* Form controls keep their slate edge over the photo: a field whose outline
   fades into the backdrop fails WCAG 1.4.11 and is hard to find. */
body.has-bg .content-surface input,
body.has-bg .content-surface select,
body.has-bg .content-surface textarea{border-color:var(--border-input)}
/* Both sidebar-collapse controls sit inside already-filtered chrome
   (.app-nav / .nav-drawer), so they take the tint without a second filter. */
body.has-bg .nav-collapse,
body.has-bg .nav-expand{
  -webkit-backdrop-filter:none;
  backdrop-filter:none;
  background-color:var(--acrylic);
  border-color:var(--acrylic-border)
}

/* Accent/danger controls and the active states stay solid so their text
   keeps its verified contrast rather than compositing over the photo. */
body.has-bg .menu-item:focus-visible,
body.has-bg .floor-tab.on{background-color:var(--accent);border-color:var(--accent)}
body.has-bg .nav-link.on{background-color:var(--strong);border-color:var(--strong)}

/* ── motion ───────────────────────────────────────────────── */
/* Kept together as one layer so the interaction feel of the whole portal is
   legible in a single place rather than scattered through the components. */

.btn,.menu-toggle,.menu-item,.item-tile,.floor-tab,.nav-link,.nav-collapse,.nav-expand{
  transition:transform var(--dur-press) var(--ease-out),
             background-color var(--dur-hover) ease,
             border-color var(--dur-hover) ease,
             color var(--dur-hover) ease
}

/* Press feedback: every pressable surface acknowledges the touch, which is the
   only confirmation a touch user gets before the page navigates.
   Deliberately excludes the floorplan hit areas (.room-hit / .room-hit-iso) —
   each is positioned to sit exactly over a room in the drawing beneath it, and
   scaling one would visibly peel it off its room. */
.btn:not(:disabled):active,.menu-toggle:active,.menu-item:active,.item-tile:active,
.floor-tab:active,.nav-link:active,.nav-collapse:active,.nav-expand:active{
  transform:scale(0.97)
}

/* Hover is gated: a touch tap fires :hover and then leaves it stuck on, so on
   a phone the last room or tile tapped would stay highlighted. :focus-visible
   is deliberately NOT in here — keyboard focus must survive on touch devices.
   Source order is preserved from where these rules used to live so the
   same-specificity ties (.btn vs .btn-primary) still resolve the same way. */
/* ── Conversations: list beside the open conversation, one pane on phones ── */
.conv-page-title{margin-bottom:1rem}
.conv{display:grid;grid-template-columns:minmax(0,20rem) minmax(0,1fr);gap:1rem;align-items:start}
.conv-list{background:var(--surface);border:0.0625rem solid var(--border);border-radius:var(--radius);padding:0.75rem}
.conv-list .message-controls{margin:0 0 0.75rem;gap:0.5rem}
.conv-list .message-controls .field{flex:1 1 7rem;min-width:0;max-width:none}
/* With the script on, Sort and View apply as soon as they change. */
.message-controls.auto-apply .btn{display:none}
/* minmax(0,…): a grid column otherwise grows to its widest row, and the
   snippets never wrap, so one long message would widen the whole list. */
.conv-list ul{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:minmax(0,1fr);gap:0.25rem}
.conv-list li{min-width:0}
.conv-list-empty{color:var(--text-muted);font-size:.9rem;margin:0}
.conv-row{display:block;padding:0.625rem 0.75rem;border:0.0625rem solid transparent;border-radius:var(--radius);color:var(--text)}
a.conv-row:not(.btn):not(.plain){text-decoration:none}
.conv-row.on{background:var(--accent-wash);border-color:var(--accent)}
.conv-row-top{display:flex;justify-content:space-between;align-items:baseline;gap:0.5rem}
.conv-row-title{font-weight:600;overflow-wrap:anywhere}
.conv-row-time{flex:none;font-size:.75rem;color:var(--text-muted)}
.conv-row.on .conv-row-time,.conv-row.on .conv-row-snippet{color:var(--text)}
.conv-row-snippet{display:block;margin-top:0.125rem;font-size:.85rem;color:var(--text-muted);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.conv-row-badges{display:flex;flex-wrap:wrap;gap:0.375rem;margin-top:0.375rem}

/* The page scrolls, not the pane: the thread and its reply box sit in the
   normal flow, so a long conversation reads like any other page. */
.conv-pane{background:var(--surface);border:0.0625rem solid var(--border);border-radius:var(--radius);
  display:flex;flex-direction:column}
.conv-pane-empty{min-height:12rem;align-items:center;justify-content:center;color:var(--text-muted)}
.conv-head{display:flex;flex-wrap:wrap;align-items:flex-start;gap:0.5rem 0.75rem;padding:0.875rem 1rem;
  border-bottom:0.0625rem solid var(--border)}
.conv-head-main{flex:1 1 14rem;min-width:0}
.conv-head h2{margin:0;font-size:1.15rem;overflow-wrap:anywhere}
.conv-head-meta{margin:0.25rem 0 0;font-size:.8rem;color:var(--text-muted)}
.conv-back{display:none}
.conv-log{padding:1rem;display:flex;flex-direction:column;gap:0.5rem}
.conv-empty{margin:auto;color:var(--text-muted);text-align:center}
.conv-day,.conv-new{display:flex;align-items:center;gap:0.75rem;margin:0.5rem 0;font-size:.75rem;color:var(--text-muted)}
.conv-day::before,.conv-day::after,.conv-new::before,.conv-new::after{content:"";flex:1;border-top:0.0625rem solid var(--border)}
.conv-new{color:var(--text);font-weight:600}
.conv-new::before,.conv-new::after{border-top-color:var(--accent)}
.conv-event{align-self:center;max-width:36rem;margin:0.25rem 0;text-align:center;font-size:.8rem;color:var(--text-muted)}
.conv-summary,.conv-msg{border:0.0625rem solid var(--border);border-radius:var(--radius);padding:0.625rem 0.875rem}
.conv-summary{background:var(--surface-2);font-size:.9rem}
.conv-msg{align-self:flex-start;max-width:min(36rem,88%);background:var(--surface-2)}
.conv-msg.mine{align-self:flex-end;background:var(--accent-wash);border-color:var(--accent)}
.conv-meta{margin:0 0 0.25rem;font-size:.75rem;color:var(--text-muted)}
.conv-msg.mine .conv-meta{color:var(--text)}
.conv-meta .badge{margin-left:0.25rem;font-size:.7rem}
.conv-body{overflow-wrap:anywhere}
.conv-photos{list-style:none;margin:0.5rem 0 0;padding:0;display:grid;grid-template-columns:repeat(auto-fill,minmax(5.5rem,1fr));gap:0.375rem}
.conv-photos a{display:block}
.conv-photos img{display:block;width:100%;aspect-ratio:1;object-fit:cover;border:0.0625rem solid var(--border);border-radius:var(--radius)}
.conv-video{margin:0.5rem 0 0}
.conv-video video{display:block;width:100%;max-height:20rem;border-radius:var(--radius);background:var(--jl-ink)}
.conv-video figcaption{margin-top:0.25rem;font-size:.8rem}
.conv-video figcaption a,.conv-file{display:inline-flex;align-items:center;gap:0.375rem}
.conv-file{display:flex;margin-top:0.5rem;padding:0.5rem 0.625rem;background:var(--surface-2);color:var(--text);
  border:0.0625rem solid var(--border-input);border-radius:var(--radius)}
a.conv-file:not(.btn):not(.plain){text-decoration:none}
.conv-file-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.conv-file-size{font-size:.75rem;color:var(--text-muted)}
.conv-reply{display:inline-block;margin-top:0.375rem;font-size:.8rem}

.conv-composer{display:flex;flex-direction:column;gap:0.5rem;padding:0.75rem 1rem 1rem;border-top:0.0625rem solid var(--border);max-width:none}
.conv-to{display:flex;align-items:center;gap:0.5rem;margin:0;font-size:.85rem}
.conv-to label{margin:0}
.conv-to select{width:auto;max-width:100%}
.conv-composer textarea{min-height:2.75rem;max-height:12rem}
.conv-actions{display:flex;flex-wrap:wrap;align-items:center;gap:0.5rem 0.75rem}
.conv-attach{position:relative;cursor:pointer}
.conv-attach:focus-within{outline:0.1875rem solid var(--focus);outline-offset:0.125rem}
.conv-limits{flex:1 1 12rem;margin:0;font-size:.78rem}
.conv-chips{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:0.375rem}
.conv-chips:empty{display:none}
.conv-chip{display:inline-flex;align-items:center;gap:0.375rem;padding:0.25rem 0.5rem;font-size:.8rem;
  background:var(--surface-2);border:0.0625rem solid var(--border-input);border-radius:var(--radius)}
.conv-chip button{padding:0 0.125rem;border:0;background:none;color:var(--text);font:inherit;cursor:pointer}
.conv-error{margin:0;color:var(--danger);font-size:.85rem}
.conv-progress{display:flex;align-items:center;gap:0.5rem;font-size:.8rem}
.conv-progress progress{flex:1;accent-color:var(--accent)}
.conv-composer .hint:empty{display:none}

/* Inside a request page's card the pane is just the card's content. */
.card .conv-pane{border:0;background:none}
.card .conv-log{padding:0}
.card .conv-composer{padding:0.75rem 0 0}

@media (max-width:64rem){
  .conv{grid-template-columns:minmax(0,1fr)}
  .conv.conv-open .conv-list,.conv:not(.conv-open) .conv-pane{display:none}
  .conv-back{display:inline-flex;align-items:center;gap:0.25rem;flex-basis:100%;font-size:.85rem}
  .conv-back .icon{transform:rotate(180deg)}
}

/* ── Admin: System email ── */
.mail-settings fieldset{margin-top:0.25rem}
.mail-transcript{margin:0 0 1rem}
.mail-transcript summary{cursor:pointer;font-weight:600}
/* Server replies can be long: they wrap rather than scroll sideways. */
.mail-transcript pre{margin:0.5rem 0 0;padding:0.75rem;white-space:pre-wrap;overflow-wrap:anywhere;
  font-family:var(--font-mono);font-size:.78rem;background:var(--surface-2);color:var(--text);
  border:0.0625rem solid var(--border);border-radius:var(--radius)}

@media (hover:hover) and (pointer:fine){
  /* :not(.on) keeps the current-page link on its accent fill; without it this
     later rule would override .nav-link.on and grey out the active link. */
  .nav-link:not(.on):hover{background:var(--bg-alt)}
  .btn:not(:disabled):hover{background:var(--bg-alt)}
  .btn-primary:not(:disabled):hover{background:var(--accent-hover);border-color:var(--accent-hover)}
  .btn-danger:not(:disabled):hover{background:var(--danger-hover)}
  .map-link:hover{text-decoration:underline}
  .nav-collapse:hover,.nav-expand:hover{background:var(--bg-alt);border-color:var(--accent)}
  .room-hit:hover{background:var(--accent-wash);border-color:var(--accent)}
  .room-hit-iso:hover .iso-hit{fill:var(--accent-wash-strong)}
  .item-tile:hover{border-color:var(--accent);background:var(--accent-wash)}
  .menu-item:hover{background:var(--accent);color:var(--accent-text);border-color:var(--accent)}
  body.has-bg .menu-item:hover{background-color:var(--accent);border-color:var(--accent)}
  .conv-row:not(.on):hover{background:var(--bg-alt)}
  .conv-file:hover{border-color:var(--accent)}
  .conv-attach:hover{border-color:var(--accent)}
}

/* Reduced motion means gentler, not none. Colour and opacity still explain
   what changed, so only the transform-based movement is dropped — `display`
   stays in the list so the drawer can still fade out instead of blinking. */
@media (prefers-reduced-motion:reduce){
  *{
    transition-property:opacity,background-color,border-color,color,fill,display!important;
    scroll-behavior:auto!important
  }
  .nav-drawer,.nav-drawer.open{transform:none!important}
  #floorplan-mount.swapping{filter:none!important}
  /* Press still needs to answer the user, so it dips opacity instead. */
  .btn:not(:disabled):active,.menu-toggle:active,.menu-item:active,.item-tile:active,
  .floor-tab:active,.nav-link:active,.nav-collapse:active,.nav-expand:active{
    transform:none!important;opacity:.85
  }
}
