/* The floor plans (SPEC-LIGHTS.md §The map is the page).

   The plans are SVG documents under src/plans/, drawn in millimetres of the
   real house. They carry no colours of their own: every paint here comes
   from a theme variable, which is what lets one drawing serve four themes
   and is the rule theme.css states for the whole client.

   The plans are inlined into the page rather than loaded as <img>, because
   an <img> is opaque — its rooms cannot be clicked, its walls cannot take a
   theme, and a light drawn on it would have to be positioned over it rather
   than in it. Inlined, a room is an element and the lights are siblings of
   the walls in one coordinate system. */

.floor-plan {
  display: block;
  width: 100%;
  height: 100%;
  /* The drawing is the shape of the house, not of the box it is given, so
     it is fitted whole rather than cropped. */
  object-fit: contain;
}

/* --- Rooms: the click targets. ---

   A room is a filled rectangle under everything structural, so a press
   anywhere inside it — on the floor, on the furniture, between two lights —
   reaches the room rather than only the gaps. The walls draw on top. */
.plan-room {
  fill: var(--plan-room, #fff);
  stroke: none;
  cursor: pointer;
  transition: fill 120ms ease-out;
  /* A room that wraps another — the living room round the hall block — is one
     path with the enclosed room as a second subpath, so that the wrap is a
     single click target. Even-odd is what makes that subpath a hole rather
     than more of the same fill; under the default winding rule the outer room
     would cover the inner one and swallow its presses. */
  fill-rule: evenodd;
}

.plan-room:hover,
.plan-room:focus-visible {
  fill: var(--plan-room-hover, #eef2f6);
}

.plan-room:focus-visible {
  outline: none;
  stroke: var(--plan-glazing, #2f6ea8);
  stroke-width: 60;
}

/* A pointer that cannot hover — a phone, the kitchen tablet — gets no hover
   state at all. Without this the fill sticks after a tap, so the last room
   touched stays highlighted as though it were selected. */
@media (hover: none) {
  .plan-room:hover {
    fill: var(--plan-room, #fff);
  }
}

/* --- Structure. ---

   Walls are filled paths rather than stroked lines: a wall has a thickness
   in the house, and drawing it as a fill means that thickness is the real
   one at every zoom rather than a stroke width that has to be scaled. */
.plan-wall {
  fill: var(--plan-wall, #4a5560);
  stroke: none;
  /* The envelope is one path of two rectangles — outside, then inside — so
     the wall is the ring between them. That only reads as a ring under
     even-odd: the default non-zero winding fills both subpaths and floods
     the whole storey with wall colour. */
  fill-rule: evenodd;
}

.plan-glazing path {
  stroke: var(--plan-glazing, #2f6ea8);
  stroke-width: 120;
  stroke-linecap: square;
  fill: none;
}

.plan-door path {
  stroke: var(--plan-door, #8d99a4);
  stroke-width: 45;
  fill: none;
  stroke-linecap: round;
}

/* Roof and the floor below a knee wall: outside the room, drawn so the plan
   does not appear to stop at nothing. */
.plan-roof {
  fill: var(--plan-roof, #e9edf1);
  stroke: none;
}

/* --- Furniture. ---

   Subdued but visible, which is the whole brief: it tells a reader which end
   of a room they are looking at. It never takes a pointer — a press on the
   bed is a press on the bedroom. */
.plan-furniture {
  pointer-events: none;
}

.plan-fitting {
  fill: var(--plan-furniture, #e4e9ee);
  stroke: var(--plan-furniture-line, #c2ccd5);
  stroke-width: 25;
}

.plan-fitting-line {
  fill: none;
  stroke: var(--plan-furniture-line, #c2ccd5);
  stroke-width: 35;
}

.plan-stair-well {
  fill: var(--plan-furniture, #e4e9ee);
  stroke: var(--plan-furniture-line, #c2ccd5);
  stroke-width: 30;
}

.plan-stair-tread,
.plan-stair-arrow {
  fill: none;
  stroke: var(--plan-furniture-line, #c2ccd5);
  stroke-width: 35;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* --- Devices on the plan (SPEC-LIGHTS.md §What a device looks like on a
   plan). ---

   Added by the page rather than present in the drawings: a plan is the
   house, and which bulbs are screwed into it is not a fact about the
   building. */
.plan-devices {
  pointer-events: none;

  /* How much of the lit colour the dimmest lit lamp's symbol still carries
     (SPEC-LIGHTS.md §A lamp is drawn at its brightness). Declared here, on
     the layer, so the one figure reaches every device and there is no
     second copy to disagree with this one.

     It is a statement about the drawing rather than about the light, which
     is why it lives in the stylesheet beside the mix that spends it, while
     the glow's floor lives in `lightsCatalogue.js` beside the arithmetic
     that spends *that*. Two floors for two quantities — hue and opacity —
     and neither is derivable from the other. */
  --device-lit-floor: 0.45;
}

/* Only the press target takes a pointer. The symbol and the halo are
   drawn through, so a press near a lamp that misses it reaches the room
   underneath rather than being swallowed by a transparent glow. */
.plan-device-target {
  pointer-events: auto;
  fill: transparent;
  cursor: pointer;
}

/* The lamp: the electrician's circle with a cross through it. Off, it is
   the furniture's line colour — present, but claiming nothing. */
.plan-device-body {
  fill: none;
  stroke: var(--plan-lamp, #3a424a);
  stroke-width: 45;
}

.plan-device-cross {
  fill: none;
  stroke: var(--plan-lamp, #3a424a);
  stroke-width: 45;
  stroke-linecap: round;
}

.plan-device-halo {
  /* The gradient, so the glow falls off rather than sitting as a flat disc
     over the furniture. Its own stops carry the theme's colour. */
  fill: url(#plan-lamp-glow);
  stroke: none;
  /* Off, there is no glow at all. Fading it in is what makes a light
     switching on read as switching on rather than as a redraw. */
  opacity: 0;
  transition: opacity 180ms ease-out;
}

/* Lit at the brightness the backend reports, not merely lit
   (SPEC-LIGHTS.md §A lamp is drawn at its brightness). `--device-glow` is
   set on the group by `planDevices.js` and is already the drawn strength
   rather than the raw percentage — the floor that keeps a lamp at 1% still
   visibly on is applied there, because it is a statement about the drawing
   and belongs with the rest of that arithmetic rather than split across two
   files. A device that is not lit carries 0, which is the fallback here too,
   so a group the script has not reached yet is drawn unlit rather than at
   full strength. */
.plan-device.is-on .plan-device-halo {
  opacity: var(--device-glow, 0);
}

/* The whole symbol carries the brightness, outline and cross included.

   The wash alone was too subtle to read: it is the palest part of the
   symbol at full brightness, so scaling it moved the one element that was
   barely visible to begin with, while the outline and the cross — the
   strongest marks, and the ones the eye actually lands on — stayed
   identical at 10% and at 100%.

   **Dimmed by colour, not by opacity.** The stroke stays fully opaque and
   is mixed from the unlit line colour towards the lit amber, so a dim lamp
   is a duller amber rather than a fainter one. Fading the stroke instead
   would carry it towards the faint drawing that means *not known*
   (CF-LIT-035), which is a different state and must not be approachable by
   dimming; it would also fade the symbol against the furniture and cost
   the outline its legibility at plan scale. `color-mix` keeps the mark at
   full strength and changes only its hue, which is what "on, but low"
   looks like.

   Both tokens are defined in all four themes, so the whole range is the
   theme's own two colours and no third colour is introduced here.

   **The mix never reaches the unlit colour**, for the same reason the glow
   has a floor: a lit lamp must not be drawn as one that is off. The glow's
   own floor is not enough on its own, because it is spent on opacity while
   this is spent on hue — at `--device-glow: 0.35` a straight mix is still
   two thirds the unlit colour, which in the high-contrast theme is pure
   black and put "dimly on" within a shade of "off" in the one theme that
   exists to keep things apart. So the published figure is remapped onto
   the upper part of the range before it is spent as a proportion, leaving
   the dimmest lamp recognisably the lit colour. */
.plan-device.is-on {
  /* The published figure remapped onto `--device-lit-floor … 1`.

     Computed **on the device**, not on the layer: a custom property
     inherits as an already-resolved value, so a `calc` placed beside the
     floor on `.plan-devices` would be worked out once there — where
     `--device-glow` is not set — and every symbol would inherit that one
     answer. Which is exactly what happened: all six brightnesses came out
     the same colour. It has to resolve where the glow is set, and
     `planDevices.js` sets that on the device's own group.

     Named rather than written inline because the expression mentions the
     floor twice, and the rule below would otherwise carry both copies. */
  --device-lit: calc(
    var(--device-lit-floor, 0.45)
      + (1 - var(--device-lit-floor, 0.45)) * var(--device-glow, 0)
  );
}

.plan-device.is-on .plan-device-body,
.plan-device.is-on .plan-device-cross {
  /* The fallback is 1 — full lit colour — rather than the floor, because it
     is a safety net and not a second copy of the threshold. A `color-mix`
     whose percentage does not resolve is *invalid*, which makes an SVG
     stroke `none` and erases the symbol altogether; a lamp drawn too bright
     is a far better failure than a lamp that is not drawn. */
  stroke: color-mix(
    in srgb,
    var(--plan-lamp-on, #f5c542) calc(var(--device-lit, 1) * 100%),
    var(--plan-lamp, #3a424a)
  );
}

.plan-device.is-on .plan-device-body {
  fill: var(--plan-lamp-on, #f5c542);
  /* The wash follows the same figure, so the inside of the symbol dims with
     its outline rather than staying put and flattening the effect. */
  fill-opacity: calc(0.25 * var(--device-glow, 0));
}

/* Not known rather than off: the catalogue has a device the payload has
   not spoken about yet. Drawn faintly, because absent must not be shown as
   a state — an unknown light is neither lit nor dark. */
.plan-device.is-unknown .plan-device-body,
.plan-device.is-unknown .plan-device-cross {
  stroke-dasharray: 60 50;
  opacity: 0.55;
}

/* The sensor: a small body, and the wedge it watches. The wedge is always
   drawn — unlike a lamp's glow, it is not a state but a fact about where
   the device is aimed, and hiding it would hide the thing being placed. */
.plan-device-sensor .plan-device-body {
  fill: var(--plan-sensor, #6ba3d8);
  fill-opacity: 0.35;
  stroke: var(--plan-sensor, #6ba3d8);
  stroke-width: 45;
}

.plan-device-sensor .plan-device-halo {
  fill: var(--plan-sensor-field, rgb(107 163 216 / 0.16));
  opacity: 1;
}

/* A group: the lamp symbol inside an outer ring — a light, but more than
   one. The ring follows the lamp's colours exactly, so a group lights up
   and reads as unknown by the same rules a single lamp does. */
.plan-device-ring {
  fill: none;
  stroke: var(--plan-lamp, #3a424a);
  stroke-width: 38;
  opacity: 0.85;
}

.plan-device.is-on .plan-device-ring {
  stroke: var(--plan-lamp-on, #f5c542);
}

.plan-device.is-unknown .plan-device-ring {
  stroke-dasharray: 60 50;
  opacity: 0.55;
}

/* A group carries its name on the map; a lamp does not. A lamp is
   identified by where it hangs, while a group is an idea about several
   lamps whose position says only roughly where they are. */
.plan-device-label {
  /* **The text colour, not the symbol's.** `--plan-lamp` is a line colour
     for a drawn symbol — a dark grey against a dark room — and text in it
     measured as barely visible on the plan. A name is read rather than
     recognised, so it has its own token, themed for contrast against the
     room each theme paints. */
  fill: var(--plan-label, #c3ccd6);
  /* **Deliberately faded.** The name is a reminder of what a symbol is, not
     a thing to be read across the room, and at full strength it was the
     loudest mark on the plan — brighter than the walls and than the symbols
     it labels. Judged from the drawing rather than argued: *"I prefer the
     label to be much more subdued."*

     Faded rather than given a dimmer colour of its own, so it stays the
     page's text colour in every theme and there is one token to keep right.
     The token carries the contrast; this decides how much of it is spent.

     **How much is per theme, and that is not fussiness.** Fading is a move
     towards the background, and the two directions are not symmetrical: a
     pale label on a dark room keeps most of its contrast, while a dark
     label on a white room loses it fast. Measured at one shared 0.55 the
     dark theme held 6.2:1 and the light theme fell to 2.0:1 — and
     high-contrast, whose entire purpose is legibility, to 2.1:1. So the
     figure is a variable, set beside the colour it fades. */
  opacity: var(--plan-label-fade, 0.55);
  /* The symbol is what a press is aimed at. The label follows the pointer
     events of the target above it, so this only stops the text swallowing
     a press meant for something drawn beneath. */
  pointer-events: none;
  /* Outlined in the room's own colour, painted under the fill, so the name
     stays legible where it crosses furniture — which is drawn in between. */
  paint-order: stroke;
  stroke: var(--plan-room, #191c1f);
  stroke-width: 70;
  stroke-linejoin: round;

  /* **A lit group's name does not light up, and there is deliberately no
     rule giving it the lamp's amber.** There was one. It made the name
     compete with the symbol at the one moment the symbol is the thing worth
     seeing, and on a long name it was the brightest object on the plan. The
     symbol says what is lit; the name says which group it is, which is as
     true lit as unlit. Do not add `.is-on .plan-device-label` back. */
}

/* Being worked with in the Edit view: the one device a drag would move.

   **Not a ring on the body**, which is what it was until a group gained
   one: two different facts drawn as the same mark in the one view where
   both appear. The selection is the dashed halo around the press area
   instead — already drawn, already distinct, and it now carries the whole
   of the meaning rather than half of it. */
.plan-device.is-selected .plan-device-body {
  stroke-width: 60;
}

.plan-device.is-selected .plan-device-target {
  stroke: var(--plan-device-selected, #f5f7fa);
  /* Thicker than it was: this mark used to share the job with a ring on the
     body, and now carries it alone. Dashed, so it cannot be mistaken for a
     group's solid ring in the one view where both are drawn. */
  stroke-width: 45;
  stroke-dasharray: 90 70;
  fill: transparent;
  opacity: 0.9;
}

/* A sensor's aiming handle: a stalk out to a grip, along the direction it
   watches. Shown only while the sensor is the one being worked with —
   otherwise every sensor on the storey would sprout one, and the map is for
   reading as well as for editing.

   Outside the body on purpose: dragging the body moves the sensor and
   dragging the grip turns it, so the two are told apart by where the finger
   lands rather than by a mode that has to be entered and left. */
.plan-device-aim {
  display: none;
}

.plan-device.is-selected .plan-device-aim {
  display: block;
}

.plan-device-aim-stalk {
  stroke: var(--plan-device-selected, #f5f7fa);
  stroke-width: 30;
  stroke-dasharray: 70 50;
  opacity: 0.7;
}

.plan-device-aim-grip {
  fill: var(--plan-sensor, #6ba3d8);
  stroke: var(--plan-device-selected, #f5f7fa);
  stroke-width: 35;
  /* The one part of a device that takes a pointer besides its press target,
     and it has to, or the sensor could be moved but never aimed. */
  pointer-events: auto;
  cursor: grab;
}

/* While a device is being dragged, the plan must not also be selecting text
   or panning under the finger. */
.plan-devices.is-dragging {
  touch-action: none;
}
