/* Phone first. Everything here is sized for a thumb, not a mouse. */

:root {
  --bg: #101418;
  --card: #1a2027;
  --card-2: #222a33;
  --line: #2c353f;
  --ink: #e8edf2;
  --muted: #94a3b1;
  --accent: #f0b429;
  --accent-ink: #1a1305;
  --on: #f5c451;
  --danger: #e2685c;
  --shared: #4aa3c7;
  --house: #c98a7a;
  --radiation: #e0a73c;
  --hp-price: #7ab87a;
  /* Current flowing. The one green on the card, and it means only this. */
  --charging: #3fb950;
  /* Toned down from 0.07 to make room for the charging wash on top of
     them. Both tones are still painted - a bare day reads as a hole - but
     they are now the quietest thing in the chart, which is right: they say
     which day it is, not what is happening. */
  --day-band: rgba(255, 255, 255, 0.02);
  --day-band-alt: rgba(255, 255, 255, 0.05);
  /* The sensor chart's grid. Ink at low alpha rather than --line, which is
     a border colour and all but vanishes on the chart's own background.
     Major lines carry a number or a midnight; minor ones only guide. */
  --grid-major: rgba(232, 237, 242, 0.22);
  --grid-minor: rgba(232, 237, 242, 0.10);
  /* The two lines on a sensor chart. Blue and orange because that pair stays
     apart for every kind of colour blindness - checked with the dataviz
     validator against the chart's own background, in both themes. */
  --sensor-temperature: #3d8fd6;
  --sensor-humidity: #c4801e;
  /* Behind a quarter the charger picked. Separated from the day bands by
     HUE as much as by weight - amber against neutral - because stacking two
     faint greys would just have read as a third, darker grey. */
  --plan-wash: rgba(240, 180, 41, 0.15);
  --plan-wash-shared: rgba(74, 163, 199, 0.12);
  /* The same two over a guessed price, at a little under half. */
  --plan-wash-est: rgba(240, 180, 41, 0.07);
  --plan-wash-shared-est: rgba(74, 163, 199, 0.055);
  /* The price curve. Its own colour rather than --line, which is a border
     tone: at 1.3:1 against the card the curve was barely a shade and the
     shape of the night could not be read off it. Raised to about 2:1, the
     same ratio in both themes - measured, not eyeballed, because the two
     backgrounds are nothing alike. Deliberately still quieter than the
     charging marks: the curve is the question, the plan is the answer. */
  --price-bar: #4b5664;
  /* The strip's empty track. Deliberately lighter than --line: at full
     strength it was the heaviest thing in the chart and read as a second
     axis, which is a lot of ink for "nothing is happening here". */
  --plan-track: rgba(255, 255, 255, 0.06);
  /* The hatch over the quarters nobody may charge in - before now, and past
     the deadline. Light enough to veil rather than to hide: they are real
     prices, just not on offer. */
  --plan-closed: rgba(255, 255, 255, 0.14);
  --radius: 14px;

  /* TELLS THE BROWSER WHICH WAY ROUND WE ARE. Without it the native controls -
     the date and time pickers on the charging card, scrollbars, the focus ring
     - are drawn light on a dark page, and two blinding white boxes sat in the
     middle of the card on a phone at night. */
  color-scheme: dark;
}

@media (prefers-color-scheme: light) {
  :root {
    color-scheme: light;
    --bg: #f2f4f6;
    --card: #ffffff;
    --card-2: #eef1f4;
    --line: #dde3e9;
    --ink: #17202a;
    --muted: #5d6b79;
    --accent: #c2870b;
    --accent-ink: #ffffff;
    --on: #d99b12;
    --danger: #b3382c;
    --shared: #2d7d9e;
    --house: #b5705e;
    --radiation: #a66f12;
    --hp-price: #2f8f4e;
    --charging: #2da44e;
    --day-band: rgba(17, 32, 42, 0.02);
    --day-band-alt: rgba(17, 32, 42, 0.05);
    --grid-major: rgba(23, 32, 42, 0.20);
    --grid-minor: rgba(23, 32, 42, 0.09);
    --sensor-temperature: #1d74b5;
    --sensor-humidity: #a66f12;
    --plan-wash: rgba(194, 135, 11, 0.22);
    --plan-wash-shared: rgba(45, 125, 158, 0.11);
    --plan-wash-est: rgba(194, 135, 11, 0.10);
    --plan-wash-shared-est: rgba(45, 125, 158, 0.05);
    --price-bar: #aab7c4;
    --plan-track: rgba(17, 32, 42, 0.06);
    --plan-closed: rgba(17, 32, 42, 0.16);
  }
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 16px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  -webkit-font-smoothing: antialiased;
  padding: 0 0 48px;
}

.wrap { max-width: 560px; margin: 0 auto; padding: 24px 16px 0; }

header { margin-bottom: 22px; }
h1 { font-size: 26px; font-weight: 650; margin: 0; letter-spacing: -0.02em; }
.sub { color: var(--muted); margin: 6px 0 0; font-size: 15px; }

/* TWO COLUMNS, and everything spans both of them unless it asks not to.
   A grid rather than a flex column so a pair of half cards can share a row
   without either of them having to know how wide the other one is. */
.stack { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; align-items: start; }
.stack > * { grid-column: span 2; }
.stack > .card.half { grid-column: span 1; }

/* Below this there is no room for two of anything. */
@media (max-width: 340px) {
  .stack > .card.half { grid-column: span 2; }
}

.card.half { padding: 14px; }
.card.half h2 { font-size: 15px; }

/* THREE EQUAL THIRDS, on one row. Left to size themselves the words wrapped
   onto a second line inside a card this narrow - measured at 177px, where
   the three came to 160 and the room was 149. Equal flex basis also means
   the three targets are the same size, which is what a switch looks like. */
.card.half .options.compact { flex-wrap: nowrap; gap: 6px; }
.card.half .options.compact .option {
  flex: 1 1 0; min-width: 0; padding: 9px 0; text-align: center; font-size: 13px;
}

.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 16px;
}

.card .top {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: 4px;
}

.card .top .title { flex: 1 1 auto; min-width: 0; }

/* A ROW THAT NEVER BREAKS. The charging card pins its switches to the top
   right corner, and a wrapped row does not put them there - it drops them
   under the car and, because the row is space-between, over to the LEFT.
   Measured on a 396 px phone, which is where it showed.
   The column is a fixed 58 px; the name takes what is left and shrinks. */
.card .top.tight { flex-wrap: nowrap; }
.card .top.tight .title { flex: 1 1 auto; min-width: 0; }
.card .top.tight .top-actions { flex: 0 0 auto; }

/* Pause and the smart-charging toggle, as one block. */
.card .top .top-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 0 0 auto;
}

/* THE CHARGING CARD STACKS THEM, with what the charger IS underneath.
   Across the row the pair came to about 110 px and left the name some ninety
   to live in; a column is the width of one switch and hands the rest back. */
.card .top .top-actions.stacked {
  flex-direction: column;
  align-items: flex-end;
  gap: 6px;
}

/* THE SAME WIDTH AS THE SWITCH ABOVE IT. Side by side their different
   widths never showed; stacked, the ragged left edge is the first thing the
   eye finds. Shorter than the chip is elsewhere, too - the column is what
   decides this row's height now, and every pixel of it pushes the price
   curve further down the card. */
.card .top .top-actions.stacked .chip {
  width: 58px;
  height: 38px;
  padding: 0;
  display: grid;
  place-items: center;
}

/* The state, under the two switches that change it. Centred on the column
   rather than on the card, because it belongs to them and not to the row. */
.chargestate {
  font-size: 12px;
  color: var(--muted);
  line-height: 1;
  /* Reserved whether or not there is a word, so a card does not change
     height as a car starts and stops. */
  min-height: 1em;
  user-select: none;
}

/* ON A PHONE they take a row of their own, so the car's name and its figures
   get the width back. Squeezed in beside them, "31 % of 80 % . 10,1 kW" broke
   over three lines - and a line of three words stacked under each other reads
   as something gone wrong rather than as a measurement. */
@media (max-width: 460px) {
  .card .top:not(.tight) { flex-wrap: wrap; }
  .card .top:not(.tight) .title { flex: 1 1 60%; }
  .card .top:not(.tight) .top-actions {
    flex: 1 0 100%;
    justify-content: flex-end;
  }
}
.card h2 { font-size: 16px; font-weight: 600; margin: 0; }
.card .meta { color: var(--muted); font-size: 13px; }
.card.off h2 { color: var(--muted); }
.card.unavailable { opacity: 0.55; }

/* The scroll grip ---------------------------------------------------- */

/* A STRIP DOWN THE LEFT WITH NOTHING IN IT. The Adjustments tabs are taller
   than a phone and are made almost entirely of sliders, so the surface you
   would naturally drag to scroll is the surface that changes values. This is
   the one place on the page guaranteed to be safe to grab.

   No JavaScript: an empty element passes the gesture to the page, which is
   exactly the behaviour wanted. touch-action says so out loud rather than
   leaving it to the default.

   It takes its own column rather than floating over the cards - see
   .has-grip .wrap. Laid on top it would have swallowed the left third of
   every mode card, which is the Auto button. */
.scroll-grip {
  position: fixed;
  left: 0; top: 0; bottom: 0;
  width: 40px;
  touch-action: pan-y;
  z-index: 5;
}

/* Something to aim at. Quiet enough to ignore, present enough to find -
   drawn like a handle rather than a scrollbar, because it does not report
   position and should not look as though it does. */
.scroll-grip::after {
  content: "";
  position: absolute;
  left: 17px; top: 50%; transform: translateY(-50%);
  width: 5px; height: 72px; border-radius: 3px;
  background: var(--line);
}

.has-grip .wrap { padding-left: 48px; }

/* ON ANYTHING WIDER THAN A PHONE there is a mouse, a scrollbar and a page
   that does not fill the width anyway. The strip would be forty pixels of
   furniture solving a problem nobody has there. */
@media (min-width: 620px) {
  .scroll-grip { display: none; }
  .has-grip .wrap { padding-left: 16px; }
}

/* Toggle ------------------------------------------------------------ */

.toggle {
  appearance: none; border: 0; cursor: pointer;
  width: 58px; height: 34px; border-radius: 17px;
  background: var(--card-2); position: relative; flex: none;
  transition: background 0.18s ease;
}
.toggle::after {
  content: ""; position: absolute; top: 4px; left: 4px;
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--muted); transition: transform 0.18s ease, background 0.18s ease;
}
.toggle[aria-pressed="true"] { background: var(--on); }
.toggle[aria-pressed="true"]::after { transform: translateX(24px); background: #fff; }
.toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* Sliders ----------------------------------------------------------- */

.slider { margin-top: 14px; }
.slider label {
  display: flex; justify-content: space-between;
  font-size: 13px; color: var(--muted); margin-bottom: 7px;
}
input[type="range"] {
  -webkit-appearance: none; appearance: none;
  width: 100%; height: 34px; background: transparent; margin: 0;
  /* UP AND DOWN IS SCROLLING, SIDEWAYS IS SETTING. Without this the browser
     decides, and it decides differently per browser: a drag begun on a slider
     could either scroll the page or move the number, and which one you got was
     not something a thumb could predict. A page full of sliders that is taller
     than the screen needs that to be predictable. */
  touch-action: pan-y;
}
input[type="range"]::-webkit-slider-runnable-track {
  height: 10px; border-radius: 5px; background: var(--card-2);
}
input[type="range"]::-moz-range-track {
  height: 10px; border-radius: 5px; background: var(--card-2);
}
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; width: 28px; height: 28px; border-radius: 50%;
  background: var(--accent); margin-top: -9px; border: 0;
}
input[type="range"]::-moz-range-thumb {
  width: 28px; height: 28px; border-radius: 50%;
  background: var(--accent); border: 0;
}
input[type="range"]:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }

/* Options, fields, buttons ------------------------------------------ */

/* Tabs, the shape a browser draws them: left-aligned, each one as wide as its
   own word, sitting on a rule that runs the width of the page.

   THEY WERE PILLS, AND PILLS WERE WRONG HERE. Two of them stretched to half
   the width each, directly above two columns of lamp cards - so they read as
   column headings for the lamps underneath rather than as a choice between
   two pages. Nothing about the behaviour changed; it was the geometry that
   lied. */
.tabs {
  display: flex; gap: 2px;
  border-bottom: 1px solid var(--line);
}
.tab {
  flex: 0 0 auto;
  border: 0; border-bottom: 3px solid transparent; border-radius: 0;
  background: none; color: var(--muted);
  padding: 10px 16px 9px; font-size: 15px; cursor: pointer;
  /* Over the rule above, not under it - which is what joins the chosen tab
     to the page below and makes the other one look like it is behind. */
  margin-bottom: -1px;
}
.tab[aria-selected="true"] { color: var(--ink); font-weight: 600; border-bottom-color: var(--accent); }
.tab:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* A heading over a run of cards - which lamp the next two sliders are for.
   Quieter than a tab and much quieter than a card: it is a label on a group,
   not a thing to press. The negative top margin eats part of the grid's own
   gap, so the heading sits with the cards it belongs to rather than floating
   equidistant between two groups. The first one in a run has nothing above it
   to close up to, hence :not(:first-child). */
.group {
  grid-column: span 2;
  margin: 0; font-size: 13px; font-weight: 600;
  letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--muted);
}
.group:not(:first-child) { margin-top: 6px; }

.options { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.option {
  border: 1px solid var(--line); background: var(--card-2); color: var(--ink);
  border-radius: 999px; padding: 9px 16px; font-size: 14px; cursor: pointer;
}
.option[aria-pressed="true"] { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }

/* --- WHAT THE MODE MEANS -------------------------------------------------
   Three options that mean three different things, so three marks. Selected
   only - an unchosen Off is just an option, not a state.

   Green is the colour the charging halo already uses for "running as it
   should"; the accent is the panel's mark for a deliberate choice. Off keeps
   the card's own quiet tones, because it is the absence of the other two. */

.option.is-auto {
  background: var(--charging);
  border-color: var(--charging);
  color: #fff;
}

/* .is-manual keeps the accent it already had - nothing to add. */

.option.is-off {
  background: var(--card-2);
  border-color: var(--muted);
  color: var(--muted);
  /* The one selected state that does not shout. A lamp that is off should
     read as off, and a filled chip would say the opposite. */
  box-shadow: inset 0 0 0 1px var(--muted);
}

.field { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 12px; }
.field span { font-size: 14px; color: var(--muted); }
.field input {
  background: var(--card-2); border: 1px solid var(--line); color: var(--ink);
  border-radius: 9px; padding: 10px 12px; font-size: 16px; min-width: 116px; text-align: right;
}

.row { display: flex; gap: 8px; margin-top: 12px; }
.btn {
  flex: 1; border: 1px solid var(--line); background: var(--card-2); color: var(--ink);
  border-radius: 10px; padding: 13px; font-size: 15px; cursor: pointer;
}
.btn.primary { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }

/* Revoking cannot be undone - the phone has to come back to the house. It
   should not look like every other button on the page. Outlined rather than
   filled, though: a wall of solid red on a list of devices reads as an alarm,
   and the usual state of that list is "nothing is wrong". */
.btn.danger {
  flex: 0 0 auto;
  border-color: var(--danger);
  color: var(--danger);
  background: transparent;
  padding: 9px 16px;
  font-size: 14px;
}

/* Index, lock screen, errors ---------------------------------------- */

a.panel-link {
  display: flex; align-items: center; justify-content: space-between;
  text-decoration: none; color: inherit;
}
a.panel-link .chev { color: var(--muted); }

.lock { text-align: center; padding: 34px 16px; }
.lock input {
  font-size: 30px; letter-spacing: 0.35em; text-align: center;
  width: 190px; padding: 14px 10px 14px 20px; margin: 18px 0;
  background: var(--card-2); border: 1px solid var(--line);
  border-radius: 12px; color: var(--ink);
}
/* Words, not four digits: the keypad's big spaced-out numerals would make a
   password unreadable and push a long one off the side of a phone. */
.lock .login { display: flex; flex-direction: column; align-items: center; gap: 10px; }
.lock .login input {
  font-size: 17px; letter-spacing: normal; text-align: left;
  width: 100%; max-width: 300px; padding: 12px 14px; margin: 0;
}
.lock .login .btn { flex: 0 0 auto; width: 100%; max-width: 300px; }

/* Under the cards, small, and out of the way of anything that is read. */
.logout { display: flex; justify-content: center; margin-top: 8px; }
.logout .btn { flex: 0 0 auto; padding: 9px 18px; font-size: 14px; }

.error { color: var(--danger); font-size: 14px; min-height: 20px; }
.note { color: var(--muted); font-size: 14px; }

/* --- the charging plan ----------------------------------------------------
   A quarter is about two pixels wide on a phone, so there is no gap between
   the bars and no stroke on them - colour is the only thing with room to say
   whether a quarter was chosen. */

.plan {
  margin-top: 16px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
}

.plan-bars {
  display: flex;
  /* The columns carry the full height now and each bar is placed inside one
     against the zero line, which is what lets a negative price hang. */
  align-items: stretch;
  height: 64px;
  position: relative;
}

.plan-bars .col { flex: 1 1 0; min-width: 0; position: relative; }

/* Floor to ceiling behind a chosen quarter, so the eye can run from the
   strip below straight up to the price that earned it. The columns are
   positioned and come after the day bands in document order, so this paints
   over them without needing a z-index. */
.plan-bars .col.picked { background: var(--plan-wash); }
.plan-bars .col.picked.shared { background: var(--plan-wash-shared); }

/* Quarters resting on a guessed price. The wash fades with the bar it sits
   behind - a plan may rest on a guess, but it must not look like it rests
   on a fact. */
.plan-bars .col.est.picked { background: var(--plan-wash-est); }
.plan-bars .col.est.picked.shared { background: var(--plan-wash-shared-est); }

/* One tone per calendar day behind the bars, alternating. Both tones are
   painted - a bare day reads as a hole in the chart rather than as a day.
   Added to the row before the columns are, so they paint underneath
   without needing a z-index. */
.plan-day {
  position: absolute;
  top: 0;
  bottom: 0;
  background: var(--day-band);
}

.plan-day.alt { background: var(--day-band-alt); }

/* Where a price stops costing and starts paying. Drawn only when some
   quarter in the window is actually negative; otherwise it would be a line
   along the floor saying nothing. */
.plan-zero {
  position: absolute;
  left: 0;
  right: 0;
  border-top: 1px solid var(--muted);
  opacity: 0.5;
  /* Above the columns. Without this the charging wash paints over it, and
     the one line that says which side of nothing a price is on disappears
     exactly where the charging is - which is where negative prices are. */
  z-index: 1;
}

/* Guessed quarters, faded uniformly so the fade means one thing - not a
   price, not a plan, just "this part is not known yet". */
.plan-bars .bar.est { opacity: 0.4; }

/* THE DEADLINE, and the hour or two drawn past it.

   Muted, because it is a boundary in the data - the same family as the
   midnight lines, not the same family as NOW. It is told apart from them by
   being solid and by what stands beside it: the hatch, which says the
   quarters to its right were never on offer. A faded bar with no mark on it
   would read as one the planner turned down. */
.plan-deadline {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 2px;
  margin-left: -1px;
  background: var(--muted);
  opacity: 0.75;
  z-index: 2;
  pointer-events: none;
}

/* A square head, where NOW has an arrow. Two marks that moved together would
   be one mark with two meanings. */
.plan-deadline::before {
  content: '';
  position: absolute;
  left: -2px;
  top: -5px;
  width: 6px;
  height: 4px;
  background: var(--muted);
}

/* CLOSED GROUND, at either end: quarters that have gone and quarters past
   the deadline. The same pattern for both because it is the same sentence -
   nothing can be charged here - and what it leaves behind is the window the
   plan was actually chosen from. */
.plan-closed {
  position: absolute;
  top: 0;
  bottom: 0;
  /* OVER the bars, not behind them. Behind, the hatch only showed in the gaps
     between bars and the tallest quarters - the ones that catch the eye -
     came through untouched. */
  z-index: 3;
  background: repeating-linear-gradient(135deg,
    var(--plan-closed) 0 2px, transparent 2px 6px);
  pointer-events: none;
}

/* NOW. Solid where the other two markers are dashed: they mean "a boundary
   in the data", this one means "here, this instant", and it moves.

   INK, NOT ACCENT, and that was the whole mistake. `bar.on` IS var(--accent)
   and the wash behind a chosen quarter is the same gold - so the line saying
   "here, this instant" was drawn in the colour that means "charging here",
   and read as one more charged quarter stood on end. Ink is worn by nothing
   else in this chart. */
.plan-now {
  position: absolute;
  /* RISES OUT OF THE PLOT. A marker that stops at the same ceiling as the
     bars is one more thing standing in the row; one that breaks the top edge
     is plainly an annotation ON the chart rather than a reading IN it. */
  top: -5px;
  bottom: 0;
  width: 2px;
  /* Centred on the instant rather than starting at it. */
  margin-left: -1px;
  background: var(--ink);
  /* A card-coloured hair on each side, so it stays a line of its own over a
     bar, over the wash and over a day band instead of merging with whichever
     it happens to cross. */
  box-shadow: -1px 0 0 var(--card), 1px 0 0 var(--card);
  /* Above the columns AND the bars, unlike .plan-edge. Behind them it would
     vanish under the charging wash - which is exactly where it is needed,
     because that is the part of the chart being looked at. */
  z-index: 2;
  pointer-events: none;
}

/* The head, in the 5 px above the plot: an arrow pointing down INTO the
   chart at the instant. It is what makes the line read as a playhead rather
   than as a third kind of gridline. */
.plan-now::before {
  content: '';
  position: absolute;
  left: -3px;
  top: 0;
  width: 0;
  height: 0;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 5px solid var(--ink);
}

.plan-edge {
  position: absolute;
  top: 0;
  bottom: 0;
  border-left: 1px dashed var(--muted);
  opacity: 0.6;
  /* Behind the bars. A boundary drawn ON TOP of a chosen quarter would look
     like a bar split in two, which is the one thing it must not say. */
  z-index: 0;
}

/* MIDNIGHT, same family and quieter on purpose. The estimate edge marks a
   change in what is KNOWN and happens once in a window; midnight happens
   every night, and two lines of equal weight would leave the reader working
   out which is which. */
.plan-edge.day {
  opacity: 0.25;
}

.plan-bars .bar {
  position: absolute;
  left: 0;
  right: 0;
  /* Above the midnight lines, which are appended after the columns and would
     otherwise be drawn across them - a bar with a dashed line through it
     reads as two bars. */
  z-index: 1;
  background: var(--price-bar);
  border-radius: 1px 1px 0 0;
}

/* Hanging below the line, so the corners round the other way. */
.plan-bars .bar.below { border-radius: 0 0 1px 1px; }

.plan-bars .bar.on { background: var(--accent); }

/* --- when the cable is awake ---------------------------------------------
   Same axis as the prices, fixed height, no relation to what anything
   costs. See planStrip for why the plan could not stay on the bars. */

.plan-strip {
  display: flex;
  height: 5px;
  margin-top: 3px;
  border-radius: 3px;
  overflow: hidden;
  /* The empty track, so the filled parts have something to be positioned
     against instead of floating. */
  background: var(--plan-track);
}

.plan-strip .seg { flex: 1 1 0; min-width: 0; }
.plan-strip .seg.on { background: var(--accent); }
.plan-strip .seg.on.shared { background: var(--shared); }
.plan-strip .seg.on.est { opacity: 0.4; }

/* Date and time as one control. They are two inputs because browsers have no
   single field for both, but they read as one setting and must not look like
   two unrelated ones. */
.deadline {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}

.deadline input { min-width: 0; }

/* Said out loud when a value that has gone is refused. A control that quietly
   keeps its old value is the same silence this whole change was meant to
   remove. */
.deadline .past {
  flex-basis: 100%;
  color: var(--danger);
  font-size: 12px;
}

/* --- THE DAYS, over the chart ---------------------------------------------
   A plan can reach days out, and "06" on its own says
   nothing about which morning it is. The day name used to replace midnight's
   hour on the axis below - so the label people hunt for took the place of the
   one that said how far through the day they were, and finding either meant
   counting.

   Each day gets a cell as wide as its own stretch of the chart, with the name
   centred in it. A divider sits on midnight. */

.day-band {
  position: relative;
  height: 18px;
  margin-bottom: 2px;
  /* A day cut short at the right edge would otherwise push its name past the
     chart it names. */
  overflow: hidden;
}

.day-cell {
  position: absolute;
  top: 0;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
  /* The name is a caption, not a control - it must never catch a thumb aimed
     at the bar beneath it. */
  pointer-events: none;
  user-select: none;
  white-space: nowrap;
  overflow: hidden;
}

/* On midnight, and only between days - never at the window's own edges, where
   it would read as a boundary in the data rather than a change of day. */
.day-cell.day-edge {
  border-left: 1px solid var(--line);
}

.plan-axis { position: relative; height: 16px; margin-top: 4px; }

.plan-axis span {
  position: absolute;
  transform: translateX(-50%);
  font-size: 11px;
  color: var(--muted);
}

/* The weekday at midnight. Brighter than the hours around it, because it
   is the mark the eye should land on when the window spans several days. */
/* Midnight, darker than the hours around it: it is the mark the eye lands on
   when a window spans several days, and the day band above names which day it
   begins. The class was .day when the day name itself sat here. */
.plan-axis span.midnight { color: var(--ink); }

.plan-sum {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px 14px;
  margin-top: 10px;
}

.plan-sum .big { font-size: 18px; font-weight: 600; }
.plan-sum .muted { color: var(--muted); font-size: 13px; }

.note.warn { color: var(--danger); }

/* Revoking is not undoable - the device has to come back to the house. Worth
   looking like it costs something. */
.option.danger,
.chip.danger {
  border-color: var(--danger);
  color: var(--danger);
}

/* A plan that is not being carried out. Dimmed as a whole rather than
   recoloured: the figures are real arithmetic on real prices, they are just
   answering a question nobody has said yes to yet. */
.note.would { font-style: italic; }
.plan-sum.would { opacity: 0.75; }

.plan-bars .bar.on.shared { background: var(--shared); }

.shared-note { margin-top: 8px; }

.shared-note .swatch {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 2px;
  background: var(--shared);
  vertical-align: baseline;
}

/* --- what the house itself is taking -------------------------------------
   A slim band under the prices, same time axis. Scaled against the whole
   supply, so a cold night is visibly taller than a mild one. */

.house-bars {
  display: flex;
  align-items: flex-start;
  height: 26px;
  /* Clear of the plan strip above it. At 2px the two touched and read as
     one three-layered object; the strip belongs to the prices, the house
     band is its own statement. */
  margin-top: 7px;
  opacity: 0.75;
}

.house-bars .hbar {
  flex: 1 1 0;
  min-width: 0;
  background: var(--house);
  border-radius: 0 0 1px 1px;
}

.house-legend .swatch {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 2px;
  background: var(--house);
  vertical-align: baseline;
}

/* A control pointing at an entity Home Assistant does not have. Quiet rather
   than alarming - it is a configuration slip, not a fault in the house. */
.field.missing span + span {
  font-size: 12px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}


/* --- the charging card's controls ----------------------------------------
   One line each, and the short ones share a line. The card used to be five
   stacked rows for four numbers, with the picture squeezed into whatever was
   left. */

.control {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 12px;
  min-width: 0;
}

.control-label { color: var(--muted); font-size: 14px; white-space: nowrap; }
.control-value { font-variant-numeric: tabular-nums; white-space: nowrap; }

/* The same target in the other two units. Quieter than the figure the
   slider is actually setting, and allowed to fall to its own line before
   it squeezes the slider on a phone. */
.control-alt {
  color: var(--muted);
  font-size: 12px;
  margin-left: 8px;
}

@media (max-width: 460px) {
  .control { flex-wrap: wrap; }
  .control-alt { display: block; margin-left: 0; }
}
.control .grow { flex: 1 1 auto; min-width: 60px; }

/* THE CHARGE TARGET, as two buttons and a figure instead of a slider. The
   number between them is the thing being set, so it takes the middle and the
   buttons sit at the ends where a thumb finds them without finding the value
   on the way past. Square-ish and the same height as the option buttons, so
   the card keeps one language.

   The value is centred rather than left aligned: it changes width as it moves
   between 95 % and 100 %, and a left aligned figure would walk the buttons
   sideways every press. */
/* THE CHARGE TARGET. Label over it, an arrow at each end, the figure large
   in the middle, the other two units underneath - drawn that way because it
   was asked for that way, and it reads better than the label-left row it
   replaced: the value is the biggest thing in its own block instead of
   competing with its label, and the kWh and mil sit under the per cent they
   belong to rather than trailing off the right edge of a narrow card. */
.charge-target { margin-top: 12px; }

.charge-label {
  text-align: center; color: var(--muted); font-size: 14px; margin-bottom: 8px;
}

/* THE ARROWS BELONG TO THE NUMBER, not to the card's edges. They used to be
   pushed apart by a figure that grew to fill the row - capped at 300 px,
   which on a phone never bit, so the group spread to both margins and the
   three stopped reading as one control. The number now takes its own width
   and the group is centred around it. */
.stepper {
  display: flex; align-items: center; justify-content: center;
  gap: 10px; min-width: 0;
}

.charge-value {
  flex: 0 0 auto; text-align: center;
  /* WIDE ENOUGH FOR THE WIDEST READING, so the arrows hold still. Without it
     they would step inwards as the target went 100 to 95, which is movement
     caused by the very press being made. Tabular figures keep the digits
     themselves from shifting; this keeps the box from doing it. */
  min-width: 5.5ch;
  font-size: 22px; font-weight: 600;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}

/* Quieter than the figure it describes, and allowed to be: it is the same
   target said twice more, for whoever thinks in kilowatt-hours or in mil. */
.charge-alt {
  text-align: center; color: var(--muted); font-size: 12px; margin-top: 6px;
  font-variant-numeric: tabular-nums;
}
.step {
  flex: 0 0 auto;
  width: 44px; height: 40px;
  border: 1px solid var(--line); border-radius: 10px;
  background: var(--card-2); color: var(--ink);
  font-size: 20px; line-height: 1; cursor: pointer;
}
.step:disabled { opacity: 0.35; cursor: default; }
.step:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* --- THE REMOTE'S PAD ------------------------------------------------------
   Three by three with the corners empty, so OK sits where the thumb expects
   it and the arrows surround it. Laid out by shape rather than as a list:
   this is a control used without looking at it. */

/* An icon inside a button. Sized in em so it follows whatever text sits
   beside it, and coloured by currentColor so it needs no theme of its own. */
.icon {
  width: 1.5em;
  height: 1.5em;
  display: block;
  margin: 0 auto;
  flex: 0 0 auto;
}

/* A button that is only an icon still has to be worth aiming at. */
.chip .icon { width: 1.3em; height: 1.3em; }
/* The television -----------------------------------------------------

   Laid out the way the thing on the coffee table is laid out: the set's own
   switch at the top, the pad in the middle where the thumb lives, then sound,
   then what is playing, then the apps. Nothing here is a slider, because
   nothing on a remote is. */

/* THE SET'S OWN SWITCH. A round key rather than the sliding toggle the lamps
   wear, and the difference is deliberate: a lamp switch is a thing you hold a
   position on, and a television's power is a press that the set then answers
   - sometimes a second or two later. A toggle that has visibly moved while
   the set has not is a toggle that is lying; a key that lights up when the
   set reports back is not. */
.power {
  appearance: none;
  width: 44px; height: 44px;
  flex: none;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: var(--card-2);
  color: var(--muted);
  display: grid; place-items: center;
  cursor: pointer;
  transition: background 0.18s ease, color 0.18s ease, border-color 0.18s ease;
}
.power .icon { width: 22px; height: 22px; }
.power[aria-pressed="true"] {
  background: var(--danger);
  border-color: var(--danger);
  color: #fff;
}
.power:disabled { opacity: 0.4; cursor: default; }
.power:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* THE PAD, round. The ring is the point: it is what a thumb finds without
   looking, and it is why the four arrows read as one control instead of as
   four buttons that happen to be near each other. The square version did the
   job and said nothing - this says "remote" from across the room.

   Centred on its own line now. The volume rocker used to sit beside it and
   needed an empty counterweight on the left to keep OK on the card's middle;
   the rocker has moved under the pad, so both the counterweight and the flex
   row it existed inside are gone. */
.pad-ring {
  display: flex;
  justify-content: center;
  margin-top: 14px;
}

.dpad {
  position: relative;
  display: grid;
  /* ROWS AS WELL AS COLUMNS, and both fixed shares. With auto rows the
     middle row took its height from OK, while OK took its size from the
     row - and the circle came out 165 by 199, which is not a circle. Three
     equal thirds each way give every key a square cell to be a share of. */
  grid-template-columns: repeat(3, minmax(0, 1fr));
  grid-template-rows: repeat(3, minmax(0, 1fr));
  place-items: center;
  /* Wide enough to hit, narrow enough that the thumb reaches all of it from
     one position. A pad spanning a tablet is no longer a pad. */
  width: 100%;
  max-width: 246px;
  aspect-ratio: 1;
  padding: 7px;
  border: 1px solid var(--line);
  border-radius: 50%;
  overflow: hidden;
}

/* The four spokes, drawn and then masked out of the middle so they stop at
   OK rather than running under it. Two diagonal hairlines are all it takes:
   they separate the arrows from each other without putting a border on each,
   which is what made the square pad read as four separate things. */
.dpad::before {
  content: '';
  position: absolute;
  inset: 0;
  background:
    linear-gradient(45deg,  transparent calc(50% - 0.5px), var(--line) 50%, transparent calc(50% + 0.5px)),
    linear-gradient(-45deg, transparent calc(50% - 0.5px), var(--line) 50%, transparent calc(50% + 0.5px));
  -webkit-mask: radial-gradient(circle at 50% 50%, transparent 33%, #000 33%);
          mask: radial-gradient(circle at 50% 50%, transparent 33%, #000 33%);
  pointer-events: none;
}

/* The keys sit above the spokes and cover them where they overlap. */
.dpad .dkey { position: relative; z-index: 1; }

/* A ROW OF KEYS: navigation, and the transport keys under the sound. Equal
   shares, because none of them is more important than the others and a row
   where one is wider invites the thumb to the wrong one. */
.keyrow {
  display: grid;
  grid-auto-flow: column;
  /* minmax(0, 1fr), NOT 1fr. A bare 1fr floors each track at the key's
     min-content width, and a key with aspect-ratio has a min-content width
     derived from its height - so four square keys measured themselves as
     100 px each and pushed the whole card off the right of a phone. */
  grid-auto-columns: minmax(0, 1fr);
  gap: 8px;
  margin-top: 12px;
}

/* IN A ROW, A KEY IS NOT SQUARE. Four squares across a phone are 85 px tall
   each, which is a row of tiles rather than a row of keys. The pad is the
   only place the square shape is doing work. */
.keyrow .dkey {
  aspect-ratio: auto;
  min-width: 0;
  min-height: 54px;
}

/* SOUND ON ITS OWN LINE. The rocker takes what is left after mute, which is
   a fixed width - a rocker that shrinks to match a word is a rocker nobody
   can aim at in the dark. */
.soundrow {
  display: flex;
  align-items: stretch;
  gap: 8px;
  margin-top: 12px;
}

.rocker {
  flex: 1 1 auto;
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 4px;
  border: 1px solid var(--line);
  background: var(--card-2);
  /* A pill, not a rounded rectangle. It is the one control here that is a
     range rather than a press, and the shape is the only thing saying so. */
  border-radius: 999px;
  padding: 4px;
}

/* Inside the pill the keys lose their own frame - the pill is the frame. */
.rocker .dkey {
  border: 0;
  background: transparent;
  border-radius: 50%;
  aspect-ratio: 1;
  width: 44px;
}
.rocker .dkey:active { background: var(--line); }

.rocker-label {
  text-align: center;
  font-size: 13px;
  color: var(--muted);
  user-select: none;
}

/* A KEY THAT CARRIES ITS NAME. The arrows do not need one; a cog does not say
   whose settings it opens, and a row of four bare glyphs is a puzzle at the
   moment somebody wants an answer. */
.dkey.labelled {
  aspect-ratio: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: 8px 4px;
}
.dkey.labelled .icon { width: 1.4em; height: 1.4em; }

.klabel {
  font-size: 11px;
  font-weight: 500;
  color: var(--muted);
  line-height: 1.2;
  text-align: center;
}

/* Mute keeps the rocker's height without being part of it. */
.soundrow .dkey.labelled { flex: 0 0 92px; }

/* THE APPS. A heading, because without one a row of four words under the
   transport keys reads as four more keys - and these are the only buttons on
   the card that leave where you are rather than move within it. */
.rowlabel {
  margin-top: 16px;
  margin-bottom: 7px;
  font-size: 13px;
  color: var(--muted);
}

/* A GRID THAT COUNTS ITS OWN COLUMNS, unlike the key rows. A key row is fixed
   at four; this one is as long as panels.yaml says. auto-fill puts as many
   tiles on a line as fit and wraps the rest, so adding a tenth app is a line
   of yaml and not a decision about layout. */
.approw {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(76px, 1fr));
  gap: 8px;
}

.app {
  appearance: none;
  border: 1px solid var(--line);
  background: var(--card-2);
  color: var(--ink);
  border-radius: var(--radius);
  padding: 12px 10px;
  min-width: 0;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}
.app:active { background: var(--line); }
.app:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* A TILE ONCE IT HAS A LOGO. Square, so nine of them read as one block the
   way they do on the television's own home row, and the logos all come out
   the same size however different their shapes are. */
.app.has-logo {
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  padding: 10px;
}

/* THE LOGOS KEEP THEIR OWN COLOURS. Everything else on this card is drawn in
   the panel's palette, and these deliberately are not: a Netflix red that has
   been tinted to match the card is no longer the mark anybody recognises,
   which is the only reason to use a logo instead of the word.
   Rounded like app icons, because that is what they are. */
.app .logo {
  width: 100%;
  height: 100%;
  object-fit: contain;
  border-radius: 10px;
  display: block;
}

.dkey {
  appearance: none;
  border: 1px solid var(--line);
  background: var(--card-2);
  color: var(--ink);
  border-radius: var(--radius);
  /* Square, so the four arrows read as one shape rather than four buttons
     that happen to be near each other. */
  aspect-ratio: 1;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  display: grid;
  place-items: center;
}

.dkey:active { background: var(--line); }
.dkey:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.dkey .icon { width: 1.8em; height: 1.8em; }

/* INSIDE THE RING the arrows keep a frame, but a quiet one. Borderless was
   tried first and the pad went hollow: the ring and two hairlines are not
   enough to say where a key begins, and a thumb aiming in the dark was
   aiming at nothing. A chip per arrow is what the real remote has. */
.dpad .dkey:not(.ok) {
  background: var(--card);
  width: 80%;
  height: 80%;
  aspect-ratio: auto;
}

/* OK carries the accent: it is the one press that commits to something, and
   in the dark it is the mark the thumb finds first. Round, and larger than
   the arrows - on the real remote it is the key you find by feel. */
.dkey.ok {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
  border-radius: 50%;
  /* Slightly over its own cell, so it breaks the spokes rather than sitting
     in the gap between them - on the remote OK is one key with the arrows
     around it, not a fifth key of the same size. */
  width: 104%;
  height: 104%;
  font-size: 16px;
}

@media (max-width: 380px) {
  .dpad { max-width: 210px; }
  .keyrow { gap: 6px; }
  .rocker .dkey { width: 38px; }
  .soundrow .dkey.labelled { flex-basis: 78px; }
}

/* Deadline and pause, side by side until the screen says no. */
.pair {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 18px;
}

.pair .control { margin-top: 12px; }

.options.compact { gap: 4px; }
.options.compact .option { padding: 7px 12px; min-width: 0; }

/* The car, given room to be recognised rather than guessed at. */
.card .top .thumb {
  width: 108px;
  height: 72px;
  object-fit: contain;
  flex: 0 0 auto;
  /* Above the glow, which is its own silhouette. Both are positioned, so
     document order would do it - but the photo losing that race is a very
     confusing bug to look at, and this costs nothing. */
  position: relative;
  z-index: 1;
}

@media (max-width: 380px) {
  .card .top .thumb { width: 84px; height: 56px; }
}

/* NO CABLE: the car is drained of colour and dimmed. The same language the
   light cards already use - state is carried by the thing itself, never by a
   badge in a corner that nobody sees from the hall.

   Colour means present; the halo below means current. Two signals, two
   meanings, and a dim steady halo was deliberately NOT used for "plugged in
   but idle" - it is too easy to mistake for no halo, and even easier to
   mistake for an image that failed to load. */
.card .top .thumb.unplugged {
  /* The contrast nudge is for the pale cars. The Renault's photo is nearly
     white to begin with, and grayscale plus opacity alone left it looking
     half-erased rather than grey - which reads as a broken image, not as a
     state. Darkening it slightly keeps it a CAR that is not here. */
  filter: grayscale(1) contrast(0.85) brightness(0.92);
  opacity: 0.45;
}

/* The transition is slow on purpose. A cable going in is a thing that happens
   in the room, and the card catching up over half a second reads as a
   response rather than as a redraw. */
.card .top .thumb {
  transition: filter 0.5s ease, opacity 0.5s ease;
}

@media (prefers-reduced-motion: reduce) {
  .card .top .thumb { transition: none; }
}

/* --- current flowing ------------------------------------------------------
   See chargeGlow. The car's own png used as a mask, so this is a halo in the
   shape of the car. Brightness and beat both come from the measured power. */

.card .top .thumb-wrap {
  position: relative;
  flex: 0 0 auto;
  display: flex;
}

.thumb-glow {
  position: absolute;
  inset: 0;
  background: var(--charging);
  -webkit-mask-size: contain;
          mask-size: contain;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-position: center;
          mask-position: center;
  /* Both from the measured power - see chargeGlow. The fallbacks are the
     low end, so a glow that somehow arrives without them is faint rather
     than a green slab. */
  filter: blur(var(--charge-blur, 5px));
  /* Just past the photo, so the light reads as coming from behind it. */
  transform: scale(var(--charge-spread, 1.04));
  pointer-events: none;
  animation: charge-breathe var(--charge-beat, 2s) ease-in-out infinite;
}

@keyframes charge-breathe {
  0%, 100% { opacity: calc(var(--charge) * 0.3); }
  50%      { opacity: var(--charge); }
}

/* Asked not to have things move: the light stays, it just stops breathing.
   What it is there to say - current is flowing, this hard - is carried by
   the brightness, and that survives. */
@media (prefers-reduced-motion: reduce) {
  .thumb-glow {
    animation: none;
    opacity: var(--charge);
  }
}

/* Pause, up beside the toggle. Short enough to stay on one line - down in
   the settings it wrapped and took the whole row with it. */
.card .top .chip {
  padding: 8px 14px;
  font-size: 13px;
  white-space: nowrap;
  flex: 0 0 auto;
}

/* Heat pump: planned offset + temperature ---------------------------- */

.heatpump-wrap { margin-top: 10px; }

/* One stacked chart per metric, offset above temperature, so each can carry
   its own y-scale and grid without the two lines fighting for the same
   vertical space. */
.heatpump-metric + .heatpump-metric { margin-top: 10px; }

.heatpump-metric-label {
  font-size: 12px;
  color: var(--muted);
  margin-bottom: 2px;
}

.heatpump-metric-label::before {
  content: "";
  display: inline-block;
  width: 10px; height: 2px;
  margin-right: 6px;
  vertical-align: middle;
}

.heatpump-metric-label.hp-offset::before { background: var(--accent); }
.heatpump-metric-label.hp-temperature::before { background: var(--shared); }
.heatpump-metric-label.hp-radiation::before { background: var(--radiation); }
.heatpump-metric-label.hp-price::before { background: var(--hp-price); }

/* Plot area: the SVG for lines/grid and a label column overlaid on top,
   since SVG text does not wrap or resize with the rest of the card's type. */
.heatpump-plot { position: relative; }

.heatpump-chart {
  width: 100%;
  height: 90px;
  display: block;
  background: var(--card-2);
  border-radius: 9px;
}

.hp-line {
  fill: none;
  stroke-width: 2;
  vector-effect: non-scaling-stroke;
}

/* --- sensor history ---------------------------------------------------------
   Temperature blue and humidity orange. Which scale belongs to which line is
   said by side, unit and legend - temperature left in degrees, humidity right
   in per cent - and never by colour alone: the numbers stay in the muted ink
   every other label on the panel uses. */
.sensor-line {
  fill: none;
  stroke: var(--sensor-temperature);
  stroke-width: 2;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}
.sensor-line.humidity { stroke: var(--sensor-humidity); }
.sensor-chart { height: 110px; }

.hp-grid-label.sensor-label-humidity { left: auto; right: 4px; }

/* The legend is also the switch: a box, the line's colour, its name. The
   whole label is the target - a 12-pixel box is too small for a thumb. It
   sits in the row above all the charts, beside the span, and rules them all. */
.sensor-legend { display: flex; flex-wrap: wrap; gap: 4px 18px; font-size: 14px; color: var(--muted); }
.sensor-legend label {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 0; cursor: pointer; user-select: none;
}
.sensor-legend input { margin: 0; accent-color: var(--muted); cursor: pointer; }
.sensor-legend input:disabled { cursor: default; }
.sensor-legend span::before {
  content: ""; display: inline-block; width: 12px; height: 2px;
  margin-right: 6px; vertical-align: middle; border-radius: 1px;
}
.sensor-legend .temperature span::before { background: var(--sensor-temperature); }
.sensor-legend .humidity span::before { background: var(--sensor-humidity); }

/* An unticked line takes its numbers and its hover dot with it - on charts
   that have two lines. One with a single line keeps it. */
.hide-temperature .dual .sensor-line.temperature,
.hide-temperature .dual .sensor-label-temperature,
.hide-temperature .dual .sensor-dot.temperature,
.hide-humidity .dual .sensor-line.humidity,
.hide-humidity .dual .sensor-label-humidity,
.hide-humidity .dual .sensor-dot.humidity { display: none; }

/* Zero degrees, a little stronger than the other grid lines: in a cabin it
   is the one value the chart is really about. Dashed rather than coloured,
   so it does not read as a second series. */
.sensor-chart .hp-grid.sensor-freezing { opacity: 1; stroke: var(--muted); stroke-dasharray: 3 3; }

/* A grid you can read a value and a time off: every line full strength in
   its own token rather than the heat pump's faded border colour. Major -
   a line with a number on it, or midnight - and minor for the rest. */
.sensor-chart .hp-grid { stroke: var(--grid-minor); opacity: 1; }
.sensor-chart .hp-grid.major { stroke: var(--grid-major); }

.sensor-range { margin: 6px 0 0; font-size: 12px; }

.sensor-hover { position: absolute; inset: 0; touch-action: pan-y; cursor: crosshair; }
.sensor-hover > * { position: absolute; display: none; pointer-events: none; }
.sensor-hover.on > * { display: block; }
.sensor-rule { top: 0; bottom: 0; width: 1px; background: var(--muted); }
.sensor-dot {
  width: 8px; height: 8px; margin: -4px 0 0 -4px; border-radius: 50%;
  background: var(--sensor-temperature); box-shadow: 0 0 0 2px var(--card);
}
.sensor-dot.humidity { background: var(--sensor-humidity); }
.sensor-tip {
  top: 4px; margin-left: 8px; white-space: nowrap;
  font-size: 12px; color: var(--ink);
  background: var(--card); border: 1px solid var(--line); border-radius: 6px;
  padding: 2px 6px;
}
.sensor-tip.flip { transform: translateX(-100%); margin-left: -8px; }

.hp-line.hp-offset { stroke: var(--accent); }
.hp-line.hp-temperature { stroke: var(--shared); }
.hp-line.hp-radiation { stroke: var(--radiation); }
.hp-line.hp-price { stroke: var(--hp-price); }

/* The estimated tail of a line - same colour, dashed, same device the
   "now" divider already uses to mean "not settled yet". */
.hp-line-est { stroke-dasharray: 4 3; }

/* Horizontal grid lines at round values, so a reader can tell "about 3
   degrees" from the drawing instead of only a shape with no scale. */
.hp-grid {
  stroke: var(--line);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
  opacity: 0.5;
}

/* Value labels, one per grid line, positioned to match in absolute HTML
   rather than SVG text so they stay crisp and themeable. */
.hp-grid-labels {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.hp-grid-label {
  position: absolute;
  left: 4px;
  transform: translateY(-50%);
  font-size: 10px;
  color: var(--muted);
  background: var(--card-2);
  padding: 0 3px;
  border-radius: 3px;
}

/* Where recorded history ends and the plan takes over. */
.hp-now { stroke: var(--line); stroke-width: 1; stroke-dasharray: 3 3; }

/* One tone per calendar day behind the plot, alternating - same device and
   same tokens the charging strip's day bands use, drawn first so everything
   else paints on top without needing a z-index. */
.hp-day { fill: var(--day-band); }
.hp-day.alt { fill: var(--day-band-alt); }

/* How far back the charts reach: one row above them all, the four choices
   side by side as one control - a segmented switch, not tabs, because it
   changes what every chart shows rather than which page is open. */
.chart-controls {
  grid-column: span 2;
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px 20px;
}
.span-bar {
  display: inline-flex;
  border: 1px solid var(--line); border-radius: 8px; overflow: hidden;
}
.span-option {
  border: 0; border-radius: 0; background: none; color: var(--muted);
  padding: 8px 14px; font-size: 14px; cursor: pointer;
}
.span-option + .span-option { border-left: 1px solid var(--line); }
.span-option[aria-pressed="true"] { background: var(--card-2); color: var(--ink); font-weight: 600; }
.span-option:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.span-bar.busy .span-option[aria-pressed="true"] { opacity: 0.6; }
