/* Three screens share this file: the home screen, the PIN sign-in and the
   admin page.

   The home screen is the approved homie-v2 mockup and everything it needs is
   under `body.v2` - its own palette, its own type. Sign-in and admin were
   built on the older tokens in :root below and are untouched by it. Do not
   restyle either half without the mockup that goes with it. */

:root{--bg:#12171c;--card:#1a2129;--ink:#eceff2;--soft:#8b98a5;--line:#2b343d;--warm:#f0b247;--tile:#232c35}
*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}
body{margin:0;background:var(--bg);color:var(--ink);
 font:16px/1.45 ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
 padding:0 0 30px;overscroll-behavior:none}
.wrap{max-width:900px;margin:0 auto;padding:14px 12px}
h1{margin:0;font-size:19px;font-weight:620;letter-spacing:-.01em}
.card{background:var(--card);border:1px solid var(--line);border-radius:16px;padding:12px}
.card h2{margin:2px 2px 10px;font-size:15px;font-weight:600;color:var(--ink)}
.hide{display:none}

/* ================= the home screen ================= */

body.v2{
  --bg:#0a0d11; --surface:#11161c; --raised:#161d25; --line:#1f2932; --line2:#2a3742;
  --text:#e9eff6; --dim:#8695a4; --dim2:#5c6b79;
  --amber:#f5b93f; --amber-soft:#f5b93f22;
  --teal:#3ad6c2; --teal-soft:#3ad6c220;
  --violet:#8b86ff;
  /* The Monstera. Two greens at the same chroma as the amber and the teal
     above, so the plant belongs to this screen, and two khakis for when it is
     dry - a thirsty plant loses its colour before it loses its shape. */
  --leaf:#4cc38a; --leaf-2:#37a271; --leaf-dry:#9a8f5c; --leaf-dry-2:#7a7048;
  --bad:#e2685f;
  --r:18px; --r2:13px;
  /* The one number the screen is sized in - see the shell below. Defined on
     the body and not on .app because the bottom sheet is a sibling of .app,
     and a calc() on a variable that does not exist is not a fallback, it is
     a dropped declaration. */
  --s: clamp(13px, min(0.8vw, 1.35vh), 24px);
  --gap: clamp(12px, 0.9vw, 26px);
  --pad: clamp(14px, 1.1vw, 34px);
  --shadow:0 1px 0 #ffffff08 inset, 0 12px 28px -18px #000;
  --glow-1:#14202c; --glow-2:#14181f; --card-1:#141a21; --card-2:#11161c;
  --hover:#1b232c; --sel:#1e2731; --sel-line:#3a4854;
  --bad-bg:#1a1311; --bad-line:#4d302b; --tv-bg:#14131d; --tv-line:#332f66; --tv-ink:#c4c1ff;
  --auto-bg:#101a1a; --auto-line:#1f4a4a; --auto-ink:#7fe3dc;
  --on-1:#221d10; --on-2:#191a19; --on-line:#4a3a15; --on-ink:#fbe4b0;
  --cool-1:#0f2220; --cool-2:#111a1d; --cool-line:#1d4d47; --cool-ink:#bdf1e8;
  --bulb:#1a222b; --bulb-line:#333f4b; --bulb-core:#3c4854; --tag-bg:#0e1318cc;
  --track:#222b34; --scrim:#05070ab0; --grid:#232d38; --grid-v:#1c242d; --wx-bg:rgba(10,14,18,.72);
  --breeze:#bff3ec; --wet-bg:#10161f; --wet-line:#2b3f5c; --plant-line:#2f4a33;
  color:var(--text);
  font:15px/1.45 ui-sans-serif,-apple-system,"SF Pro Text","Inter",system-ui,sans-serif;
  -webkit-font-smoothing:antialiased;
  padding:0;min-height:100vh;
  background:
    radial-gradient(1100px 600px at 78% -8%, var(--glow-1) 0%, transparent 62%),
    radial-gradient(800px 500px at 4% 102%, var(--glow-2) 0%, transparent 60%), var(--bg);
}
/* Light mode: body.light, set from the account sheet's Auto / Light / Dark (home.html head). */
body.v2.light{
  --bg:#f3f0ea; --surface:#ffffff; --raised:#f7f5f0; --line:#e3ded5; --line2:#d2cbbf;
  --text:#1e242b; --dim:#5d6873; --dim2:#8a939c;
  --amber:#d48f0c; --amber-soft:#d48f0c22; --teal:#0e9a88; --teal-soft:#0e9a881f; --violet:#5b54de; --bad:#c84a40;
  --shadow:0 1px 0 #ffffff inset, 0 12px 28px -20px #0000002e;
  --glow-1:#fff6e3; --glow-2:#eaf1f4; --card-1:#ffffff; --card-2:#faf8f4;
  --hover:#efebe4; --sel:#ebe6dd; --sel-line:#cdc5b8;
  --bad-bg:#fbeceA; --bad-line:#efc3be; --tv-bg:#efeefd; --tv-line:#cbc7f6; --tv-ink:#4a43c9;
  --auto-bg:#e6f6f3; --auto-line:#b3e2da; --auto-ink:#0a7a6c;
  --on-1:#fff3d8; --on-2:#fffaf0; --on-line:#efd391; --on-ink:#875800;
  --cool-1:#e2f5f1; --cool-2:#f2fbf9; --cool-line:#afe0d7; --cool-ink:#0a6c60;
  --bulb:#ffffff; --bulb-line:#cbc4b8; --bulb-core:#cbc4b8; --tag-bg:#ffffffd9;
  --track:#e3ded5; --scrim:#1a1f2559; --grid:#ebe7e0; --grid-v:#f2efe9; --wx-bg:rgba(255,255,255,.82);
  --breeze:#0e9a88; --wet-bg:#eef1fb; --wet-line:#c6cfee; --plant-line:#b5d8bc;
}
.v2 svg{display:block}
.v2 button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}

/* ---------- shell ----------
   This is a control panel on a wall, not a page: the header, the two columns
   and the All lights dimmer divide up the window and nothing scrolls. The
   height comes from `height:100dvh` here, and every box below it is a flex
   or grid child with `min-height:0`, which is what lets the plan and the
   tiles give up space instead of pushing the dimmer off the bottom.

   --s is the one number the whole panel is sized in, and it is why a 2560px
   window gets BIGGER tiles rather than the same tiles further apart. It
   follows whichever of the window's two dimensions is the tighter, so a wide
   short window does not grow type that then does not fit; it is clamped at
   both ends, and the floor is the size everything was drawn at, which is why
   a phone needs no override - min(0.8vw, 1.35vh) is under the floor at any
   phone size. */
.v2 .app{display:flex;flex-direction:column;gap:var(--gap);height:100dvh;padding:var(--pad);
  padding-top:max(var(--pad),env(safe-area-inset-top));padding-left:max(var(--pad),env(safe-area-inset-left));
  padding-right:max(var(--pad),env(safe-area-inset-right))}
.v2 .bar{display:flex;align-items:center;gap:calc(var(--s)*1.2);padding:0 4px;flex-wrap:wrap;flex:none}
.v2 .brand{font-size:calc(var(--s)*1.55);font-weight:650;letter-spacing:-.4px;flex:none}
.v2 .brand small{display:block;white-space:nowrap;font-size:calc(var(--s)*.82);font-weight:500;color:var(--dim2);letter-spacing:.14em;text-transform:uppercase;margin-top:2px}
.v2 .glance{display:flex;gap:calc(var(--s)*.6);margin-left:auto;align-items:center;flex-wrap:wrap;justify-content:flex-end}
.v2 .pill{display:flex;align-items:center;gap:calc(var(--s)*.52);
  padding:calc(var(--s)*.5) calc(var(--s)*.9);border-radius:999px;
  background:var(--surface);border:1px solid var(--line);font-size:var(--s);color:var(--dim);white-space:nowrap}
.v2 .pill b{color:var(--text);font-weight:600;font-variant-numeric:tabular-nums}
.v2 .pill svg{width:calc(var(--s)*1.15);height:calc(var(--s)*1.15);stroke:var(--dim2);fill:none;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
/* The three reading pills are buttons: each opens that thing's sheet. They
   look exactly as they did as divs - the only thing that moves is the border
   under a cursor, so nothing about the header changes for a reader who is not
   about to tap one. */
.v2 button.pill{transition:.16s}
.v2 button.pill:hover{border-color:var(--line2)}
.v2 button.pill:active{transform:scale(.97)}
/* the account button wears the same pill, round and one letter wide */
.v2 .pill.me{width:calc(var(--s)*2.45);height:calc(var(--s)*2.45);padding:0;justify-content:center;
  color:var(--dim);font-size:var(--s);font-weight:600;transition:.16s}
.v2 .pill.me:hover{border-color:var(--line2);color:var(--text)}
.v2 .live{width:calc(var(--s)*.54);height:calc(var(--s)*.54);border-radius:50%;background:var(--teal);box-shadow:0 0 0 3px var(--teal-soft)}
/* The plant pill when the soil is at or under its warning number. The card
   this replaced said it in the same red, on the reading and on its bar. */
.v2 .pill.dry{border-color:var(--bad-line);background:var(--bad-bg)}
.v2 .pill.dry b{color:var(--bad)}
.v2 .pill.dry svg{stroke:var(--bad)}
.v2 .pill.wet b{color:var(--violet)}
.v2 .pill.wet svg{stroke:var(--violet)}
/* The TV pill. Violet is the TV's colour on the floor plan, so the pill and
   the dot agree about what "the TV is on" looks like. Awake-but-not-playing
   is the amber the card's LED used. */
.v2 .pill.tv.awake svg{stroke:#c98a2b}
.v2 .pill.tv.on{border-color:var(--tv-line);background:var(--tv-bg)}
.v2 .pill.tv.on b{color:var(--tv-ink)}
.v2 .pill.tv.on svg{stroke:var(--violet)}
/* The thermostat pill while the rule is running. Teal, because that is air
   con everywhere else on this screen - the Air card, the AC dots, the legend
   - and because the pill beside it already proves violet means the TV. */
.v2 .pill.auto.on{border-color:var(--auto-line);background:var(--auto-bg)}
.v2 .pill.auto.on b{color:var(--auto-ink)}
.v2 .pill.auto.on svg{stroke:var(--teal)}

/* ---------- grid ----------
   35 / 65, which is Victor's split: the controls on the left, the flat on the
   right. `fr` and not `%` so the two columns divide what is left after the
   gap rather than overrunning it by one gap's width. */
.v2 .grid{display:grid;gap:var(--gap);grid-template-columns:minmax(0,35fr) minmax(0,65fr);
  flex:1 1 auto;min-height:0}

.v2 .card{background:linear-gradient(180deg,var(--card-1),var(--card-2));border:1px solid var(--line);
  border-radius:var(--r);padding:calc(var(--s)*1.15);box-shadow:var(--shadow);
  display:flex;flex-direction:column;min-height:0}
.v2 .card>h2{margin:0 0 calc(var(--s)*.9);font-size:calc(var(--s)*.88);font-weight:600;letter-spacing:.13em;
  text-transform:uppercase;color:var(--dim2);display:flex;align-items:center;gap:8px;flex:none}
.v2 .card>h2 .n{margin-left:auto;letter-spacing:0;text-transform:none;font-size:calc(var(--s)*.88);color:var(--dim)}
/* The left column. It scrolls only on a window too short to hold the
   controls at all - a 1280x600 wall panel, a laptop with the dev tools open
   - because the alternative is tiles quietly clipping their own labels. */
.v2 .col{display:flex;flex-direction:column;gap:var(--gap);min-height:0;overflow-y:auto}
/* Its two cards share the column's height instead of sitting at the top of a
   tall empty box. They grow by their number of tile ROWS - home.js counts
   them and sets --rows - so a row of Modes and a row of Air end up the same
   height, which they would not if both cards simply grew by one. Below
   min-content they do not shrink at all; that is when the column scrolls. */
.v2 .col>.card{flex:var(--rows,1) 1 auto;min-height:min-content}

/* ---------- tiles ----------
   In a card that fills its column the tiles fill the card: equal rows, and
   the label sits at the bottom of whatever height the row got (margin-top on
   .lb, not space-between, so the label and its sub-line stay together). */
.v2 .tiles{display:grid;grid-template-columns:1fr 1fr;gap:calc(var(--s)*.62)}
/* Only in the panel's cards. The bottom sheet draws its buttons with .tiles
   too, and a sheet is as tall as what is in it. */
.v2 .col .tiles{flex:1 1 auto;min-height:0;grid-auto-rows:minmax(min-content,1fr)}
.v2 .tile{position:relative;display:flex;flex-direction:column;align-items:flex-start;
  gap:calc(var(--s)*.6);padding:calc(var(--s)*.88);border-radius:var(--r2);background:var(--raised);
  border:1px solid var(--line);transition:.16s ease;overflow:hidden}
.v2 .tile:hover{border-color:var(--line2);transform:translateY(-1px)}
.v2 .tile:active{transform:translateY(0) scale(.985)}
.v2 .tile svg{width:calc(var(--s)*1.5);height:calc(var(--s)*1.5);stroke:var(--dim);fill:none;stroke-width:1.7;
  stroke-linecap:round;stroke-linejoin:round;transition:.16s;flex:none}
.v2 .tile .lb{font-size:calc(var(--s)*1.02);font-weight:550;letter-spacing:-.1px;text-align:left;margin-top:auto}
.v2 .tile .sub{font-size:calc(var(--s)*.82);color:var(--dim2);margin-top:calc(var(--s)*-.3)}
/* A tile that spans the card is a button the width of the card, and at a
   row's height its icon and label were sitting in the far left corner of a
   wide empty slab. Centred, it reads as the big single action it is. */
.v2 .tile.wide{grid-column:span 2;flex-direction:row;align-items:center;justify-content:center;
  gap:calc(var(--s)*.75);padding:calc(var(--s)*.8) calc(var(--s)*.88)}
.v2 .tile.wide .lb{margin-top:0}
/* A caption inside a sheet's button grid: which list the temperatures under
   it belong to. Spans both columns so the tiles below start their own row. */
.v2 .tiles .cap{grid-column:span 2;font-size:calc(var(--s)*.84);color:var(--dim2);
  letter-spacing:.2px;padding:calc(var(--s)*.35) 0 0 2px}
.v2 .tiles .cap:first-child{padding-top:0}
/* The Auto AC sheet's typed temperature: - [ 26.0 ] °C +, one row. */
.v2 .tiles .num{grid-column:span 2;display:grid;grid-template-columns:auto 1fr auto auto;
  align-items:center;gap:calc(var(--s)*.62)}
.v2 .tiles .num .tile{justify-content:center;align-items:center;min-width:calc(var(--s)*3.2);
  font-size:calc(var(--s)*1.3);font-weight:600;padding:calc(var(--s)*.5)}
.v2 .tiles .num input{width:100%;min-width:0;text-align:center;font:inherit;font-size:calc(var(--s)*1.4);
  font-weight:600;color:inherit;background:var(--raised);border:1px solid var(--line);
  border-radius:var(--r2);padding:calc(var(--s)*.55)}
.v2 .tiles .num input:focus{outline:none;border-color:var(--teal)}
.v2 .tiles .num span{color:var(--dim2)}
/* Three sensors to tick, one row of three. */
.v2 .tiles .row3{grid-column:span 2;display:grid;grid-template-columns:repeat(3,1fr);gap:calc(var(--s)*.62)}
/* Seven modes in two columns leaves the eighth cell empty, and in a card
   that now fills its column that hole is a quarter of the card. The odd one
   out takes the whole row instead. Nothing about the tile changes but its
   width - it is the same button running the same script. */
.v2 .col .tiles>.tile:last-child:nth-child(odd){grid-column:span 2;flex-direction:row;
  align-items:center;justify-content:center;gap:calc(var(--s)*.75)}
.v2 .col .tiles>.tile:last-child:nth-child(odd) .lb{margin-top:0}
.v2 .tile.on{background:linear-gradient(180deg,var(--on-1),var(--on-2));border-color:var(--on-line)}
.v2 .tile.on svg{stroke:var(--amber)}
.v2 .tile.on .lb{color:var(--on-ink)}
.v2 .tile.cool.on{background:linear-gradient(180deg,var(--cool-1),var(--cool-2));border-color:var(--cool-line)}
.v2 .tile.cool.on svg{stroke:var(--teal)}
.v2 .tile.cool.on .lb{color:var(--cool-ink)}

/* ---------- the TV remote ----------
   It was a card on the right of the screen; it is the TV pill's sheet now.
   Capped and centred rather than spread across the sheet: a 3-across pad of
   square keys the width of a 460px sheet would be 140px keys, which is a
   remote nobody has ever held. */
.v2 #sh-pad{max-width:320px;margin:0 auto 12px;container-type:inline-size}
/* With the pad in it the sheet is four rows of keys taller than any other,
   so the buttons under it lie down on one line each - same buttons, same
   order, a sheet that still ends above the top of a laptop screen. */
.v2 .sheet.withpad .tiles .tile{flex-direction:row;align-items:center;gap:calc(var(--s)*.7);
  padding:calc(var(--s)*.7) calc(var(--s)*.85)}
.v2 .sheet.withpad .tiles .tile .lb{margin-top:0}
.v2 .rpad{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}
.v2 .k{aspect-ratio:1/1;border-radius:12px;background:var(--raised);border:1px solid var(--line);
  display:grid;place-items:center;transition:.14s}
.v2 .k:hover{border-color:var(--line2);background:var(--hover)}
.v2 .k:active{transform:scale(.94)}
/* Sized off the pad, not the window: the key is a square of whatever the
   pad is wide, and a 17px glyph in a 100px key looks like a mistake. */
.v2 .k svg{width:clamp(17px,6.5cqw,26px);height:clamp(17px,6.5cqw,26px);stroke:var(--dim);fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
.v2 .k.ok{background:var(--sel);border-color:var(--sel-line)}
.v2 .k.ok svg{stroke:var(--text)}
.v2 .k.pw svg{stroke:#e2685f}

/* ---------- floor plan ----------
   The dots are HTML over the SVG, not inside it: a pill label that stays
   readable at any plan width is a font-size in CSS pixels, and an SVG text
   node scales with the drawing instead. */
.v2 .plan{padding:calc(var(--s)*1.15) calc(var(--s)*1.15) calc(var(--s)*.7)}
/* The plan takes the height the card has left and fits the drawing inside it
   WITHOUT leaving its aspect: planbox is a size container, so planfit can be
   told in CSS to be the smaller of "as wide as the box" and "as wide as the
   box is tall, times the plan's own ratio". Centring it is then just
   place-items. Neither `width:100%` nor `height:100%` alone can do this - one
   of them overflows on a window the other shape. */
.v2 .planbox{position:relative;flex:1 1 auto;min-height:0;container-type:size;
  display:grid;place-items:center}
.v2 .planfit{--ar:1.1230;  /* 21.0 / 18.7 - the viewBox on the plan in home.html */
  position:relative;container-type:inline-size;
  width:min(100cqw, calc(100cqh * var(--ar)));aspect-ratio:var(--ar)}
.v2 .planfit svg{width:100%;height:100%}
/* Drawing view: the illustrated apartment replaces the walls; Plan view hides the drawing. */
.v2 .planfit:not(.drawing) :is(#nightbase,#nightuse,#lituse,#breeze){display:none}
/* Move mode on the drawing: each piece has a see-through handle; tap one for its sheet, drag to place it. */
.v2 #handles{position:absolute;inset:0;pointer-events:none}
.v2 #handles>div{position:absolute;display:none;border-radius:6px;touch-action:none;cursor:grab}
body.v2.editing .planfit.drawing #handles>div{display:block;pointer-events:auto}
body.v2.editing .planfit.drawing #handles>div:hover,.v2 #handles>div.dragging{outline:2px dashed #f0d7a3;background:#f0d7a314}
/* The selected piece in Move: dashed frame, a round grip to resize, mirror and put away above it. */
.v2 #psel{position:absolute;display:none;border:2px dashed var(--amber);border-radius:6px;pointer-events:none;z-index:3}
.v2 #psel.on{display:block}
/* Where the selected decoration stands: walls whose foot is nearer than this point hide it. */
.v2 #psel::after{content:"";position:absolute;left:50%;bottom:-5px;width:10px;height:10px;margin-left:-5px;border-radius:50%;background:#fff;border:2px solid var(--amber)}
.v2 #psel .grip{position:absolute;right:-11px;bottom:-11px;width:22px;height:22px;border-radius:50%;background:var(--amber);border:2px solid #fff;
  box-shadow:0 2px 6px #0006;pointer-events:auto;cursor:nwse-resize;touch-action:none}
/* Move mode is an editor: the drawing takes the screen, the side panel holds the zoom, tools and storage. */
.v2 .editpanel{display:none}
/* Entering and leaving Move morphs the layout instead of jumping (View Transitions; older browsers just switch). */
.v2 .card.plan{view-transition-name:plancard}
.v2 .editpanel{view-transition-name:editpanel}
.v2 .col{view-transition-name:controls}
::view-transition-group(*){animation-duration:.45s;animation-timing-function:cubic-bezier(.22,1,.36,1)}
/* While a decoration is dragged out of the drawing, the panel says where it can be dropped. */
.v2 .editpanel.drop #pstore{border-color:var(--amber);background:var(--on-1)}
body.v2.editing .col,body.v2.editing .glance,body.v2.editing .dimmer,body.v2.editing #skytime,body.v2.editing #skywx,body.v2.editing #planview{display:none}
body.v2.editing .grid{grid-template-columns:minmax(0,1fr) clamp(220px,20vw,320px)}
body.v2.editing .editpanel{display:flex;gap:calc(var(--s)*.8);overflow-y:auto}
.v2 #pzoom{width:100%;aspect-ratio:1.79;border-radius:var(--r2);background:var(--raised);border:1px solid var(--line);flex:none}
.v2 .editpanel.empty #pzoom,.v2 .editpanel.empty .ptools:not(#wbrushes,#wsizes,#wzoom){opacity:.35;pointer-events:none}
.v2 .ptools{display:grid;grid-template-columns:1fr 1fr;gap:6px}
.v2 .ptools button{padding:9px 6px;border-radius:var(--r2);background:var(--raised);border:1px solid var(--line);font-size:13px;font-weight:600;color:var(--text)}
.v2 .ptools button:hover{border-color:var(--line2)}
.v2 #pstore{flex:none;display:grid;grid-template-columns:repeat(3,1fr);gap:6px;min-height:90px;align-content:start;border:1px dashed var(--line2);border-radius:var(--r2);padding:6px}
.v2 .pthumb{display:grid;justify-items:center;gap:2px;padding:6px;border-radius:10px;background:var(--raised);cursor:grab;touch-action:none;font-size:11.5px;color:var(--dim)}
.v2 .pthumb img{width:44px;height:44px;object-fit:contain;pointer-events:none}
/* The walls picture: hidden, shown over the drawing only while a brush is picked. */
.v2 #wallpaint{display:none;position:absolute;inset:0;width:100%;height:100%;opacity:0;touch-action:none;cursor:crosshair;z-index:20}
body.v2.stroking #wallpaint{opacity:.45}
.v2 :is(#wallshow,#wallpieces){display:none}
body.v2.painting .planfit.drawing :is(#wallshow,#wallpieces){display:inline}
body.v2.painting .planfit.drawing #wallpaint{display:block}
.v2 .ptools button.on{border-color:var(--amber);background:var(--on-1)}
.v2 .ptools :is(.wb1,.wb2,.wb3,.wb4,.wb5)::before{content:"";display:inline-block;width:9px;height:9px;border-radius:50%;margin-right:6px;vertical-align:middle}
.v2 .ptools .wb1::before{background:#f00}.v2 .ptools .wb2::before{background:#00f}.v2 .ptools .wb3::before{background:#ff0}.v2 .ptools .wb4::before{background:#0f0}.v2 .ptools .wb5::before{background:#f0f}
.v2 .ptools.wsizes{grid-template-columns:1fr 1fr 1fr}
/* Zoom in Move mode: the drawing scales inside its box. */
.v2 .planbox{overflow:hidden}
.v2 .planfit.drawing{touch-action:none}
.v2 .planfit.zoomed{transform-origin:0 0}
body.v2.zoomed .planbox{cursor:grab}
.v2 #pstore .hint{grid-column:1/-1;margin:0;font-size:12px;color:var(--dim2);text-align:center;align-self:center}
.pghost{position:fixed;width:64px;height:64px;object-fit:contain;transform:translate(-50%,-50%);pointer-events:none;z-index:99;opacity:.85}
.v2 .planfit.drawing{--ar:1.7917}
.v2 .planfit.drawing svg>g:not(#breeze,#lituse){display:none}
/* Rain and storm fall behind the drawing: around it and through its glass, never on the rooms. */
.v2 .card.plan{position:relative;overflow:hidden}
.v2 .card.plan>:not(#rain){position:relative;z-index:1}
.v2 #rain{display:none;position:absolute;inset:0;z-index:0;pointer-events:none;color:#7f93a8}
.v2 #rain canvas{width:100%;height:100%;display:block}
.v2 #rain .flash{position:absolute;inset:0;background:#eef4ff;opacity:0}
body.v2:not(.light) #rain{color:#9fb3c8}
/* Time-of-day tints on the Auto light theme (body.ph-*): morning fresh peach, dusk warm amber. Weather tints below win over them. */
body.v2.light.ph-morning{--bg:#f6eee6;--surface:#fffaf5;--raised:#fbf4ec;--line:#ecdfd2;--line2:#dccbb9;--card-1:#fffaf5;--card-2:#fbf3ea;
  --glow-1:#ffe4cc;--glow-2:#e4eef6;--hover:#f5eadf;--sel:#f1e3d4;--sel-line:#d9c4ad;--track:#ecdfd2;--grid:#f1e6db;--grid-v:#f6eee6}
body.v2.light.ph-dusk{--bg:#e8d3c2;--surface:#f6e8dc;--raised:#f0dfd0;--line:#dbc4af;--line2:#c8ac94;--card-1:#f6e8dc;--card-2:#eedbca;
  --glow-1:#f7c49b;--glow-2:#cdb4cb;--hover:#ead6c4;--sel:#e5cdb7;--sel-line:#c3a285;--text:#2a1f1a;--dim:#6a5548;--dim2:#8c7566;
  --track:#dbc4af;--grid:#e3cdb9;--grid-v:#ead8c7;--tag-bg:#f6e8dcd9}
/* Weather tints on top of the Auto theme (body.wx-*, set in home.html). Cloudy: greyer day. Rain: darker blue-grey day. Storm: dark slate. */
body.v2.light.wx-cloudy{--bg:#e4e7ea;--surface:#f3f5f6;--raised:#eceff1;--line:#d4d9dd;--line2:#c2c8ce;--card-1:#f4f6f7;--card-2:#eaedf0;
  --glow-1:#eef1f4;--glow-2:#dde3e8;--hover:#e2e6e9;--sel:#dce1e5;--sel-line:#bcc4cb;--track:#d4d9dd;--grid:#e3e7ea;--grid-v:#ebeef0}
body.v2.light.wx-rain{--bg:#b6c0ca;--surface:#d2d9e0;--raised:#c8d0d8;--line:#abb6c1;--line2:#97a4b1;--card-1:#d0d8df;--card-2:#c3ccd5;
  --glow-1:#c9d3dc;--glow-2:#a5b2bf;--hover:#bec8d1;--sel:#b6c0ca;--sel-line:#909eac;--text:#18212a;--dim:#43505d;--dim2:#62707d;
  --tag-bg:#d2d9e0d9;--track:#abb6c1;--grid:#bac5cf;--grid-v:#c4cdd6;--bulb:#dde3e9;--bulb-line:#97a4b1;--bulb-core:#97a4b1}
body.v2.light.wx-rain #rain{color:#55697f}
body.v2.wx-storm{--bg:#141c26;--surface:#1a2430;--raised:#212d3a;--line:#2b3846;--line2:#3a4a5b;--card-1:#1d2834;--card-2:#18222d;
  --glow-1:#283a4f;--glow-2:#1a2330;--hover:#253241;--sel:#283646;--sel-line:#41536a}
.v2 .card.plan.rain #rain{display:block}
.v2 .card.plan.storm #rain .flash{animation:flash 7s linear infinite}
@keyframes flash{0%,88%,93%,100%{opacity:0}89%,91%{opacity:.85}90%,92%{opacity:.15}}
/* Lamp light fades in and out; a running AC blows three drifting air lines. */
.v2 #lamps>*{transition:opacity 1.2s ease,rx .8s ease,ry .8s ease}
.v2 #lamps polyline{fill:none;stroke:#fff;stroke-width:.9;stroke-linejoin:round;stroke-linecap:round;filter:url(#soft)}
/* Drawing view: small faint dots, names only on hover or while moving; the label floats so the dot stays on its spot. */
.v2 .planfit.drawing .dot .tag{position:absolute;top:100%;margin-top:4px;opacity:0;pointer-events:none}
.v2 .planfit.drawing .dot.up .tag{top:auto;bottom:100%;margin:0 0 4px}
.v2 .planfit.drawing .dot:hover .tag{opacity:1}
.v2 .planfit.drawing .dot:is(.light,.ac,.tv):not(:hover) .bulb{transform:scale(.6);opacity:.55}
.v2 .planfit.drawing .dot:is(.sensor,.plant,.dehum):not(:hover,.dry){opacity:.2;transition:opacity .2s}
.v2 #breeze path{fill:none;stroke:var(--breeze);stroke-width:.07;stroke-linecap:round;stroke-dasharray:.5 .35;opacity:0;animation:blow 2.4s linear infinite}
.v2 #breeze path:nth-child(3n+2){animation-delay:.8s}.v2 #breeze path:nth-child(3n){animation-delay:1.6s}
@keyframes blow{0%{stroke-dashoffset:1.7;opacity:0}25%{opacity:.85}100%{stroke-dashoffset:0;opacity:0}}
/* Outside / inside readings with the weather icon, in the Apartment heading. */
.v2 #wx{display:flex;align-items:baseline;gap:calc(var(--s)*.5);margin-left:calc(var(--s)*1.2);text-transform:none;letter-spacing:0;color:var(--text);white-space:nowrap}
.v2 #wx .wxi{width:calc(var(--s)*1.35);height:calc(var(--s)*1.35);align-self:center;stroke:var(--amber);fill:none;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
.v2 #wx b{font-size:calc(var(--s)*1.25);font-weight:700}
.v2 #wx span{font-size:calc(var(--s)*.85);color:var(--dim)}
.v2 .dot{position:absolute;transform:translate(-50%,-50%);display:flex;flex-direction:column;
  align-items:center;gap:5px;cursor:pointer}
.v2 .dot.up{flex-direction:column-reverse}
/* Sized off the plan's own width, so a dot on a 2560px wall is a target a
   thumb finds and not the same 19px it was on a phone. */
.v2 .dot .bulb{width:clamp(15px,1.85cqw,30px);height:clamp(15px,1.85cqw,30px);
  border-radius:50%;background:var(--bulb);border:1.5px solid var(--bulb-line);
  display:grid;place-items:center;transition:.2s}
.v2 .dot .bulb::after{content:"";width:32%;height:32%;border-radius:50%;background:var(--bulb-core);transition:.2s}
.v2 .dot .tag{font-size:clamp(8px,1.35cqw,14px);font-weight:550;color:var(--dim2);white-space:nowrap;
  padding:2px 7px;border-radius:999px;background:var(--tag-bg);backdrop-filter:blur(3px);
  border:1px solid transparent;text-transform:none;letter-spacing:0;transition:.2s;opacity:.55}
.v2 .dot:hover .tag,.v2 .dot.on .tag{opacity:1}
/* Below this the plan is too small for names: at 390px five lit pills land on
   top of each other and turn into mush, and a label you cannot read is worse
   than none. The glow says what is on, and tapping a dot names it in the
   sheet. `display:none` rather than a fade, so the bulb sits exactly on its
   own coordinate instead of being pushed off it by an invisible label. */
@container (max-width:520px){
  .v2 .dot .tag{display:none}
  .v2 .dot .bulb{width:15px;height:15px}
}
.v2 .dot.on .bulb{background:var(--amber);border-color:#ffd782;
  box-shadow:0 0 0 5px #f5b93f1f,0 0 22px 5px #f5b93f35}
.v2 .dot.on .bulb::after{background:#7a5507}
.v2 .dot.on .tag{color:var(--on-ink);border-color:var(--on-line)}
.v2 .dot.ac .bulb{border-color:var(--cool-line)}
.v2 .dot.ac.on .bulb{background:var(--teal);border-color:#8ff0e3;box-shadow:0 0 0 5px #3ad6c21c,0 0 20px 4px #3ad6c230}
.v2 .dot.ac.on .tag{color:var(--cool-ink);border-color:var(--cool-line)}
.v2 .dot.tv .bulb{border-radius:5px}
.v2 .dot.tv.on .bulb{background:var(--violet);border-color:#c4c1ff;box-shadow:0 0 0 5px #8b86ff1c,0 0 20px 4px #8b86ff30}
/* nothing has reported this entity at all - not off, unknown */
.v2 .dot.dead .bulb{opacity:.4;border-style:dashed}
.v2 .dot .cnt{position:absolute;top:-6px;right:-9px;min-width:15px;height:15px;padding:0 4px;
  border-radius:999px;background:var(--raised);border:1px solid var(--line2);color:var(--dim);
  font-size:9.5px;font-weight:700;display:grid;place-items:center}
.v2 .dot.on .cnt{background:var(--on-1);border-color:var(--on-line);color:var(--on-ink)}
.v2 .legend{display:flex;gap:16px;align-items:center;padding:calc(var(--s)*.85) 4px 2px;
  color:var(--dim2);font-size:calc(var(--s)*.86);flex:none}
.v2 .legend i{width:8px;height:8px;border-radius:50%;display:inline-block;margin-right:6px;vertical-align:-1px}

/* ---------- dimmer ---------- */
/* The last row of the panel, and the one thing under the two columns. No
   margin-top: the shell's gap puts it where it belongs, and a margin here
   would be a gap the height calculation does not know about. */
.v2 .dimmer{display:flex;align-items:center;gap:calc(var(--s)*1);flex:none;
  padding:calc(var(--s)*.95) calc(var(--s)*1.1);
  border-radius:var(--r);background:linear-gradient(180deg,var(--card-1),var(--card-2));border:1px solid var(--line)}
.v2 .dimmer.hide{display:none}
.v2 .allsw{flex:none;width:calc(var(--s)*3);height:calc(var(--s)*1.7);border-radius:999px;border:1px solid var(--line2);background:var(--raised);position:relative;padding:0;cursor:pointer;transition:background .2s}
.v2 .allsw i{position:absolute;top:2px;left:2px;width:calc(var(--s)*1.7 - 6px);aspect-ratio:1;border-radius:50%;background:var(--dim);transition:transform .2s,background .2s}
.v2 .allsw[aria-checked="true"]{background:var(--amber)}
.v2 .allsw[aria-checked="true"] i{background:#fff;transform:translateX(calc(var(--s)*1.3))}
.v2 .dimmer .ico{width:calc(var(--s)*1.35);height:calc(var(--s)*1.35);flex:none;stroke:var(--amber);fill:none;
  stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
.v2 .dimmer .lb{font-size:var(--s);font-weight:550;white-space:nowrap}
.v2 .dimmer .v{font-size:var(--s);color:var(--dim);font-variant-numeric:tabular-nums;
  min-width:3em;text-align:right}
.v2 input[type=range]{-webkit-appearance:none;appearance:none;flex:1;height:6px;border-radius:99px;
  background:linear-gradient(90deg,var(--amber) var(--p,50%),var(--track) var(--p,50%));outline:none}
.v2 input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:20px;height:20px;border-radius:50%;
  background:#fff;border:3px solid #17202a;box-shadow:0 2px 8px #0008;cursor:grab}
.v2 input[type=range]::-moz-range-thumb{width:20px;height:20px;border-radius:50%;
  background:#fff;border:3px solid #17202a;box-shadow:0 2px 8px #0008;cursor:grab}

/* ---------- sheet ---------- */
.v2 .scrim{position:fixed;inset:0;background:var(--scrim);backdrop-filter:blur(3px);opacity:0;
  pointer-events:none;transition:.2s;z-index:40}
.v2 .scrim.show{opacity:1;pointer-events:auto}
.v2 .sheet{position:fixed;z-index:50;left:50%;bottom:0;width:min(460px,100%);
  transform:translate(-50%,102%);transition:transform .26s cubic-bezier(.22,1,.36,1);
  background:var(--card-1);border:1px solid var(--line);border-bottom:0;
  border-radius:22px 22px 0 0;padding:16px 16px calc(26px + env(safe-area-inset-bottom))}
.v2 .sheet.show{transform:translate(-50%,0)}
/* A pill's sheet is the tallest thing this element ever holds - a drawing, a
   row of numbers and a graph. On a short phone that can be taller than the
   screen, and a sheet you cannot reach the bottom of is a sheet with a
   missing graph. */
/* Stops under the iPhone status bar, or the close button sits beneath it. */
.v2 .sheet{max-height:calc(100dvh - env(safe-area-inset-top) - 8px);overflow-y:auto}
.v2 .sheet h3{margin:4px 0 14px;font-size:16px;font-weight:600;padding-right:38px}
/* The scrim and Escape both close the sheet, and on a phone neither one looks
   like anything. This is the one you can see. */
.v2 .sheet .close{position:absolute;top:12px;right:12px;width:34px;height:34px;
  border-radius:50%;display:grid;place-items:center;background:var(--raised);
  border:1px solid var(--line);transition:.16s}
.v2 .sheet .close:hover{border-color:var(--line2);background:var(--hover)}
.v2 .sheet .close:active{transform:scale(.94)}
.v2 .sheet .close svg{width:15px;height:15px;stroke:var(--dim);fill:none;stroke-width:2;stroke-linecap:round}
.v2 .sheet .grab{width:38px;height:4px;border-radius:99px;background:var(--line2);margin:0 auto 10px}
@media(min-width:820px){
  .v2 .sheet{bottom:22px;border-radius:22px;border-bottom:1px solid var(--line);
    transform:translate(-50%,calc(100% + 40px))}
  .v2 .sheet.show{transform:translate(-50%,0)}
  /* A sheet carrying several sensors takes the width the screen has. The
     phone width is a phone decision; 460px of a 1600px screen is a column of
     charts you scroll for no reason. The TV remote is unaffected - #sh-pad
     caps itself at 320px and stays centred however wide this gets. */
  .v2 .sheet.wide{width:min(980px,94vw)}
}

/* ---------- the phone ----------
   Under 820px this stops being a panel and goes back to being a page: one
   column in the order the markup has it, each card as tall as it needs to
   be, and the window scrolls. Everything here is undoing a rule that only
   makes sense when the height is fixed - there is no second design. */
@media(max-width:820px){
  /* A little more air under the last card than the panel needs, because on a
     phone that edge is where a thumb rests rather than where the screen
     ends. */
  .v2 .app{height:auto;min-height:100dvh;padding-bottom:calc(var(--pad) + 14px + env(safe-area-inset-bottom))}
  .v2 .grid{grid-template-columns:1fr;flex:0 0 auto}
  .v2 .col>.card{flex:0 0 auto}
  .v2 .col .tiles{flex:0 0 auto;grid-auto-rows:auto}
  /* A container cannot be a SIZE container when its height is its content:
     that is circular, and the plan would collapse to nothing. */
  .v2 .planbox{display:block;container-type:inline-size;flex:0 0 auto}
  .v2 .planfit{width:100%}
}

/* ---------- a header pill's sheet ----------
   The drawing, the numbers, the two ranges and the graph. Hidden outright for
   every other use of the sheet - `display:none` by id, because `.tiles` and
   `.dimmer` set their own display and a bare `.hide` loses to them. */
.v2 #sh-d.hide,.v2 #sh-b.hide,.v2 #sh-art.hide,.v2 #sh-pad.hide{display:none}

.v2 .detail{display:flex;flex-direction:column;gap:11px;margin-bottom:2px}

.v2 .art{display:grid;place-items:center;padding:2px 0}
.v2 .plantart{width:min(188px,54%);height:auto}
.v2 .plantart .blade{fill:var(--leaf);transition:fill .5s}
.v2 .plantart .blade.back{fill:var(--leaf-2)}
.v2 .plantart .stalk{fill:none;stroke:var(--leaf-2);stroke-width:3.4;stroke-linecap:round;transition:stroke .5s}
.v2 .plantart .rib{fill:none;stroke:#0d1a14;stroke-width:1.1;stroke-linecap:round;opacity:.45}
.v2 .plantart.dry .blade{fill:var(--leaf-dry)}
.v2 .plantart.dry .blade.back{fill:var(--leaf-dry-2)}
.v2 .plantart.dry .stalk{stroke:var(--leaf-dry-2)}
.v2 .plantart.dry .rib{stroke:#1a170d}
.v2 .plantart .pot{fill:#6b4a3a}
.v2 .plantart .rim{fill:#7d5745}
.v2 .plantart .soil{fill:#2a2119}
.v2 .plantart .trunk{stroke:#6b4a3a;stroke-width:6}
.v2 .plantart .fruit{fill:#9bc53d;stroke:#5f7f1f;stroke-width:1}
.v2 .plantart.dry .fruit{fill:#b8b060}
.v2 .plantart .flower ellipse{fill:#fbf8f0;stroke:#d9d2bf;stroke-width:.5}
.v2 .plantart .flower .eye{fill:#e8c95a}
.v2 .plantart .frond{fill:none;stroke:var(--leaf);stroke-width:1.6;stroke-linecap:round}
.v2 .plantart .frond.back{stroke:var(--leaf-2)}
.v2 .plantart.dry .frond{stroke:var(--leaf-dry)}
.v2 .plantart.dry .frond.back{stroke:var(--leaf-dry-2)}

/* The stats area holds one grid, or two when there is a summary above the
   detail. Two grids rather than arithmetic on one: the apartment pair and
   the per-sensor tiles have different column counts, and a row break forced
   with spans has to know how many sensors there are. */
.v2 .statgroup{display:flex;flex-direction:column;gap:8px}
.v2 .stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(74px,1fr));gap:8px}
/* A stats grid is ONE row: as many equal columns as it has tiles, counted by
   whoever renders it and handed over as --n.

   auto-fit is what broke this. It picks the column count off the available
   width, so six sensor tiles came out as five and one - two rows where the
   design has one, and the sheet read as five rows instead of three. */
.v2 .stats[style*="--n"]{grid-template-columns:repeat(var(--n),1fr)}
/* Below the wide sheet there is not room for six across, so a narrow screen
   uses the grid's own capped count instead: two stays two, six becomes two
   rows of three. auto-fit was picking the count off the width and gave four
   and two. */
@media(max-width:820px){
  .v2 .stats[style*="--nm"]{grid-template-columns:repeat(var(--nm),1fr)}
}
.v2 .stat{display:flex;flex-direction:column;gap:2px;padding:8px 10px;border-radius:var(--r2);
  background:var(--raised);border:1px solid var(--line)}
/* statk/statv, not k/v: `.k` is the TV remote's keys - a bordered square with
   aspect-ratio 1 - and every class on this screen shares one namespace. */
.v2 .stat .statk{font-size:10.5px;font-weight:600;letter-spacing:.09em;text-transform:uppercase;color:var(--dim2)}
.v2 .stat .statv{font-size:17px;font-weight:600;font-variant-numeric:tabular-nums}
/* soil at or under the plant's own warning number - the same red the pill
   turns, said the same way */
.v2 .stat.bad{background:var(--bad-bg);border-color:var(--bad-line)}
.v2 .stat.bad .statv{color:var(--bad)}

/* two ranges, and only ever the two the server keeps */
.v2 .ranges{display:flex;gap:6px}
.v2 .ranges button{padding:6px 15px;border-radius:999px;background:var(--raised);
  border:1px solid var(--line);font-size:12.5px;font-weight:600;color:var(--dim);transition:.16s}
.v2 .ranges button:hover{border-color:var(--line2);color:var(--text)}
.v2 .ranges button.on{background:var(--sel);border-color:var(--sel-line);color:var(--text)}

/* One box per line on a phone. A room draws two of them, a plant one, so the
   container is a stack and the box knows nothing about how many there are.

   On a wide sheet - several sensors - they lay out two across, because a
   desktop has the width and nobody wants to scroll past four full-bleed
   charts to compare the first with the last.

   TWO, hard-coded, not auto-fit. auto-fit picks the column count off the
   available width, which at 980px is three, and three columns of four
   graphs is a row of three and an orphan. Every sensor draws exactly two
   lines - a temperature and a humidity - so two columns puts each sensor
   on its own row and the row is never ragged, whatever the sensor count. */
.v2 .charts{display:grid;gap:8px}
@media(min-width:820px){
  .v2 .sheet.wide .charts{grid-template-columns:repeat(2,1fr)}
}
.v2 .chart{border-radius:var(--r2);background:var(--surface);border:1px solid var(--line);
  padding:8px 6px 4px;min-height:120px;display:grid;place-items:center}
.v2 .chart .cap{justify-self:start;padding:0 2px 2px 6px;font-size:11px;
  letter-spacing:.06em;text-transform:uppercase;color:var(--dim2)}
.v2 .chart .graph{width:100%;height:auto;touch-action:pan-y}
/* A long press on a graph is a hover, not an iOS text selection. */
.v2 .chart{position:relative;-webkit-user-select:none;user-select:none;-webkit-touch-callout:none}
.v2 .graph .cursor{stroke:var(--dim2);stroke-width:1;stroke-dasharray:3 3}
.v2 .graph .hdot{stroke:var(--surface);stroke-width:1.5}
.v2 .graph .hdot.cool{fill:var(--teal)}.v2 .graph .hdot.leaf{fill:var(--leaf)}
.v2 .graph .hdot.violet{fill:var(--violet)}.v2 .graph .hdot.amber{fill:var(--amber)}
.v2 .chart .gtip{position:absolute;top:30px;z-index:2;pointer-events:none;background:var(--raised);
  border:1px solid var(--line2);border-radius:10px;padding:6px 9px;font-size:11.5px;
  font-variant-numeric:tabular-nums;box-shadow:0 6px 18px rgba(0,0,0,.35);white-space:nowrap}
.v2 .chart .gtip .gt{color:var(--dim2);margin-bottom:3px}
.v2 .chart .gtip div{display:flex;align-items:center;gap:6px}
.v2 .chart .gtip b{margin-left:auto;padding-left:10px;font-weight:600}
.v2 .chart .gtip i{width:9px;height:2.5px;border-radius:2px;display:inline-block}
.v2 .chart .gtip i.cool-key{background:var(--teal)}.v2 .chart .gtip i.leaf-key{background:var(--leaf)}
.v2 .chart .gtip i.violet-key{background:var(--violet)}.v2 .chart .gtip i.amber-key{background:var(--amber)}
.v2 .graph .grid{stroke:var(--grid);stroke-width:1}
.v2 .graph .grid.v{stroke:var(--grid-v)}
.v2 .graph .lab{fill:var(--dim2);font-size:10px;font-family:inherit;font-variant-numeric:tabular-nums}
.v2 .graph .line{fill:none;stroke-width:1.9;stroke-linejoin:round;stroke-linecap:round}
.v2 .graph .dot,.v2 .graph .tip{stroke:none}
/* A multi-line graph wraps each line in its own <g class="<accent>">, so the
   accent has to reach a descendant as well as the svg itself. Same colours,
   two selectors, rather than a second set of classes to keep in step. */
.v2 .graph g.cool .line,.v2 .graph g.cool .tip,.v2 .graph g.cool .dot{stroke:var(--teal);fill:var(--teal)}
.v2 .graph g.cool .line{fill:none}
.v2 .graph g.leaf .line,.v2 .graph g.leaf .tip,.v2 .graph g.leaf .dot{stroke:var(--leaf);fill:var(--leaf)}
.v2 .graph g.leaf .line{fill:none}
.v2 .graph g.violet .line,.v2 .graph g.violet .tip,.v2 .graph g.violet .dot{stroke:var(--violet);fill:var(--violet)}
.v2 .graph g.violet .line{fill:none}
.v2 .graph g.amber .line,.v2 .graph g.amber .tip,.v2 .graph g.amber .dot{stroke:var(--amber);fill:var(--amber)}
.v2 .graph g.amber .line{fill:none}

/* Which line is which sensor. Three unnamed lines in one box is a picture
   nobody can read, and the caption only has room for what the box measures. */
.v2 .chart .keys{display:flex;flex-wrap:wrap;gap:10px;justify-self:start;padding:0 2px 4px 6px}
.v2 .chart .keys span{display:inline-flex;align-items:center;gap:5px;font-size:10.5px;
  letter-spacing:.04em;text-transform:uppercase;color:var(--dim2)}
.v2 .chart .keys i{width:9px;height:2.5px;border-radius:2px}
.v2 .chart .keys i.cool-key{background:var(--teal)}
.v2 .chart .keys i.leaf-key{background:var(--leaf)}
.v2 .chart .keys i.violet-key{background:var(--violet)}
.v2 .chart .keys i.amber-key{background:var(--amber)}

.v2 .graph.cool .line,.v2 .graph.cool .tip,.v2 .graph.cool .dot{stroke:var(--teal);fill:var(--teal)}
.v2 .graph.cool .line{fill:none}
.v2 .graph.cool .area{fill:var(--teal);opacity:.10}
.v2 .graph.leaf .line,.v2 .graph.leaf .tip,.v2 .graph.leaf .dot{stroke:var(--leaf);fill:var(--leaf)}
.v2 .graph.leaf .line{fill:none}
.v2 .graph.leaf .area{fill:var(--leaf);opacity:.10}
.v2 .graph.violet .line,.v2 .graph.violet .tip,.v2 .graph.violet .dot{stroke:var(--violet);fill:var(--violet)}
.v2 .graph.violet .line{fill:none}
.v2 .graph.violet .area{fill:var(--violet);opacity:.10}
/* the plant's own warning level, so the line has something to be above */
.v2 .graph .warn{stroke:var(--bad);stroke-width:1;stroke-dasharray:4 4;opacity:.7}
.v2 .graph .warnlab{fill:var(--bad);font-size:9.5px;font-family:inherit;opacity:.85}
/* fewer than two readings, or a history that would not load */
.v2 .chart .empty{color:var(--dim2);font-size:12.5px;line-height:1.5;text-align:center;
  padding:26px 14px;max-width:30em}

/* ================= sign-in, keypad and admin =================
   The older tokens at the top of this file, and nothing here touches the home
   screen. */

.center{min-height:100dvh;display:flex;align-items:center;justify-content:center;padding:20px 16px}
.panel{background:var(--card);border:1px solid var(--line);border-radius:16px;padding:20px;
 width:100%;max-width:360px}
.panel h2{margin:0 0 4px;font-size:19px;font-weight:620;letter-spacing:-.01em}
.panel .lead{color:var(--soft);font-size:13.5px;line-height:1.5;margin:0 0 16px}

.who{display:grid;gap:8px;margin-bottom:4px}
.who button{background:var(--tile);border:1px solid var(--line);border-radius:12px;color:var(--ink);
 padding:15px 14px;font-size:16px;font-weight:560;text-align:left;cursor:pointer}
.who button:active{background:#2c3742}

/* Big enough for a thumb: 4 rows of 72px keys sit in the lower half of a phone. */
.pad{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-top:14px}
.pad button{background:var(--tile);border:1px solid var(--line);border-radius:14px;color:var(--ink);
 height:66px;font-size:25px;font-weight:500;cursor:pointer;line-height:1}
.pad button:active{background:#2c3742}
.pad .wide{font-size:15px;font-weight:560;color:var(--soft)}
.pad .go{background:var(--warm);color:#12171c;font-size:17px;font-weight:650;border-color:#4a3a22}
.pad .go[disabled]{background:var(--tile);color:#55606b;border-color:var(--line);cursor:default}

.pips{display:flex;justify-content:center;gap:11px;height:15px;margin:18px 0 2px}
.pips i{width:13px;height:13px;border-radius:50%;background:#2b343d;display:block}
.pips i.on{background:var(--warm)}
.shake{animation:sh .34s}
@keyframes sh{25%{transform:translateX(-7px)}50%{transform:translateX(7px)}75%{transform:translateX(-4px)}}

.msg{min-height:19px;margin:12px 0 0;font-size:13.5px;text-align:center;color:var(--soft)}
.msg.bad{color:#e06a5a}
.msg.good{color:#4cc06a}

label{display:block;font-size:12.5px;color:var(--soft);margin:12px 0 5px}
input[type=text],input[type=password],input[type=url],select{width:100%;background:#12171c;
 border:1px solid var(--line);border-radius:10px;color:var(--ink);padding:12px;font-size:16px;
 font-family:inherit}
input:focus,select:focus{outline:none;border-color:#3d4a57}
.btn{width:100%;background:var(--warm);border:0;border-radius:10px;padding:13px;font-size:15px;
 font-weight:600;color:#12171c;cursor:pointer;margin-top:14px;font-family:inherit}
.btn.ghost{background:var(--tile);color:var(--ink);border:1px solid var(--line)}
.btn.danger{background:#3a2724;color:#e06a5a;border:1px solid #4d302b}
.btn[disabled]{opacity:.55;cursor:default}
.row{display:flex;gap:8px}
.row .btn{margin-top:0}

.bar-top{display:flex;align-items:center;gap:10px;margin:0 0 14px}
.bar-top h1{margin:0;font-size:19px;font-weight:620}
.bar-top a{margin-left:auto;color:var(--soft);font-size:13.5px;text-decoration:none}

.person{display:flex;align-items:center;gap:11px;background:var(--tile);border:1px solid var(--line);
 border-radius:12px;padding:12px;margin-bottom:8px;flex-wrap:wrap}
.person .nm{font-size:15px;font-weight:600}
.person .sub{font-size:11.5px;color:var(--soft)}
.person .acts{margin-left:auto;display:flex;gap:6px}
.person .acts button{background:var(--card);border:1px solid var(--line);border-radius:9px;
 color:var(--soft);padding:8px 11px;font-size:12.5px;cursor:pointer;font-family:inherit}
.person .acts button:active{background:#2c3742}
.person.off{opacity:.55}
.tag{font-size:10.5px;font-weight:650;letter-spacing:.04em;text-transform:uppercase;
 color:var(--warm);border:1px solid #4a3a22;border-radius:6px;padding:2px 6px}
.person .edit{flex-basis:100%}

.tokline{display:flex;align-items:center;gap:10px;background:#12171c;border:1px solid var(--line);
 border-radius:10px;padding:11px 12px;font:13px ui-monospace,Menlo,monospace;color:var(--soft)}
.tokline button{margin-left:auto;background:var(--tile);border:1px solid var(--line);border-radius:8px;
 color:var(--ink);padding:7px 12px;font:500 12.5px/1 ui-sans-serif,system-ui,sans-serif;cursor:pointer}

/* Hidden until /api/me says admin. Starts hidden rather than shown-then-
   removed, so a member never sees a button flash that they cannot use. */
.v2 .adminonly{display:none}

/* ---------- moving the dots ----------
   Move/Done sits in the plan's own heading, because what it changes is the
   plan and nothing else on the screen. */
/* The heading becomes a row so the control sits at the right edge, where a
   control belongs. margin-left:auto on the button alone did nothing - .card h2
   is a plain block - so Move rendered inline after the light count and read as
   part of the sentence. */
.v2 .card.plan h2{display:flex;align-items:center;gap:8px}
.v2 .card.plan h2 .planedit{margin-left:auto;padding:4px 11px;border-radius:999px;
  background:var(--raised);border:1px solid var(--line);font-size:11.5px;font-weight:600;
  color:var(--dim);transition:.16s}
.v2 .card.plan h2 .planedit:hover{border-color:var(--line2);color:var(--text)}
.v2 .card.plan h2 .planedit.on{background:var(--sel);border-color:var(--sel-line);color:var(--text)}
/* In edit mode a dot is a thing you pick up, so it says so - and it must not
   scroll the page out from under a drag on a phone, which is what
   touch-action does. */
body.v2.editing .dot{cursor:grab;touch-action:none}
body.v2.editing .dot .bulb{box-shadow:0 0 0 2px var(--line2)}
.v2 .dot.dragging{cursor:grabbing;z-index:9}
.v2 .dot.dragging .bulb{transform:scale(1.18)}
/* A thermometer and a plant draw their reading rather than a lamp: wider
   than a bulb, and never lit, because a temperature has no on and neither
   has soil. One shape for both - they answer the same kind of question. */
.v2 .dot.sensor .bulb,.v2 .dot.plant .bulb,.v2 .dot.dehum .bulb{width:auto;min-width:26px;height:19px;border-radius:999px;
  background:var(--raised);border:1px solid var(--line2);color:var(--text);
  font-size:11px;font-weight:700;display:grid;place-items:center;padding:0 5px;
  font-variant-numeric:tabular-nums}
.v2 .dot.sensor.dead .bulb,.v2 .dot.plant.dead .bulb,.v2 .dot.dehum.dead .bulb{color:var(--dim2)}
/* Soil at or under the plant's warning number turns the dot the same red the
   card and the pill turn, so the plan says it without being asked. */
.v2 .dot.plant .bulb{border-color:var(--plant-line)}
.v2 .dot.plant.dry .bulb{background:var(--bad-bg);border-color:var(--bad-line);color:var(--bad)}
.v2 .dot.plant.wet .bulb{background:var(--wet-bg);border-color:var(--wet-line);color:var(--violet)}

/* An app, not a page: no pinch or double-tap zoom, and 16px inputs so iOS never zooms on focus. */
html,body{touch-action:manipulation;-webkit-text-size-adjust:100%}
input,select,textarea{font-size:max(16px,1em)}

/* Sensor 1 dark grey, Outside red (Victor 2026-09-27). */
.v2 .graph g.grey .line,.v2 .graph g.grey .tip,.v2 .graph g.grey .dot{stroke:#7d8793;fill:#7d8793}
.v2 .graph g.red .line,.v2 .graph g.red .tip,.v2 .graph g.red .dot{stroke:var(--bad);fill:var(--bad)}
.v2 .graph g.grey .line,.v2 .graph g.red .line{fill:none}
.v2 .graph .hdot.grey{fill:#7d8793}.v2 .graph .hdot.red{fill:var(--bad)}
.v2 .chart .keys i.grey-key,.v2 .chart .gtip i.grey-key{background:#7d8793}
.v2 .chart .keys i.red-key,.v2 .chart .gtip i.red-key{background:var(--bad)}

/* Dehumidifier dot: teal ring, filled teal while drying. */
.v2 .dot.dehum .bulb{border-color:var(--cool-line)}
.v2 .dot.dehum.on .bulb{background:var(--cool-1);border-color:var(--teal);color:var(--cool-ink)}
.v2 .tiles .num input.modename{grid-column:1/-1;text-align:left}
/* Edit pen on a mode tile: shows on hover, always on touch screens. */
.v2 .tile .pen{position:absolute;top:6px;right:9px;font-size:14px;color:var(--dim);opacity:0;transition:.15s;cursor:pointer;padding:2px 4px}
.v2 .tile:hover .pen{opacity:1}
@media (hover:none){.v2 .tile .pen{opacity:.8}}
