/* haier — an instrument for one air conditioner.
 *
 * DIRECTION
 * This is not a dashboard. It is the face of an instrument, read one-handed in
 * a dark upstairs hallway, usually at night, by four people of very different
 * technical confidence. So: dark by default (a white card stack blinds you at
 * 1am), numbers set in a machined face, prose set quietly beside them.
 *
 * THE ONE IDEA: hue means temperature.
 * Every value that IS a temperature is tinted by its own reading, on a single
 * diverging scale that runs ice → comfort → warm. Nothing else uses those hues.
 * Colour is never the only encoding: the number is always right next to it.
 *
 *   hue        = temperature
 *   brightness = magnitude (energy, power) — no hue at all
 *   red/green  = fault state, reserved, always with an icon and a word
 *
 * Red is absent from the thermal scale on purpose. With an ember step in it, a
 * hot hallway and a critical fault measured deltaE 3.5 under deuteranopia:
 * indistinguishable. In this application red means something is wrong.
 *
 * Palette checked with the dataviz validator against the dark surface: the
 * thermal poles separate at deltaE 20.9 (deutan) / 29.1 (normal), and the warm
 * step clears the status yellow at 17.9.
 */

:root {
  color-scheme: dark;

  /* --- thermal scale: hue means temperature ------------------------------ */
  --t-cold: #4fc3e0;
  --t-cool: #86b4c6;
  --t-comfort: #a8a29a;
  --t-warm: #ce9a70;
  --t-hot: #d98a52;

  /* --- fault state: reserved, never a data hue --------------------------- */
  --ok: #5cc489;
  --warn: #f5d020;
  --bad: #f2545b;

  /* --- chrome ------------------------------------------------------------ */
  --ink: #0a0d12;
  --panel: #111721;
  --panel-2: #18202c;
  --raised: #1e2836;
  --rule: #222b39;
  --rule-soft: #1a2230;
  --text: #e7ebf1;
  --text-dim: #b3bccb;
  --muted: #8c97a8;

  /* The chrome carries NO hue. Every hue in this interface means something —
     a temperature, or a fault — so an accent colour would be a fourth meaning
     competing with the three that are real. The primary action is simply the
     brightest thing on screen, which is what "press this" actually looks like.
     It also keeps the cold end of the thermal scale from reading as "button". */
  --accent: #f2f5f9;
  --accent-ink: #0a0d12;
  --focus: #8fd8ee;

  /* The floor plan draws on the same chrome, named separately so the diagram
     can be read at a glance without hunting through the interface tokens. */
  --plan-floor: #0d131c;
  --plan-line: #2a3547;
  --plan-wall: #55647a;
  --plan-unit: #1e2836;
  --jet-near: var(--t-cold);
  --jet-far: var(--t-cold);

  /* --- form -------------------------------------------------------------- */
  --r-lg: 20px;
  --r-md: 14px;
  --r-sm: 10px;
  --tap: 48px;
  --gap: 14px;

  --mono: ui-monospace, "SF Mono", "Cascadia Mono", "JetBrains Mono",
          "Roboto Mono", Consolas, monospace;
  --sans: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;

  --shadow: 0 1px 0 rgba(255, 255, 255, 0.03) inset,
            0 12px 32px rgba(0, 0, 0, 0.45);
}

/* The light counterpart is a genuine second design, not an inverted token
   dump: the thermal hues are darkened so they still read as temperature
   against paper, and the chrome warms up so the cold end stays cold. */
@media (prefers-color-scheme: light) {
  :root {
    color-scheme: light;
    --t-cold: #0d8bab;
    --t-cool: #4d7f93;
    --t-comfort: #7c766c;
    --t-warm: #a4663a;
    --t-hot: #b25c25;

    --ok: #1a7f4b;
    --warn: #8a6a00;
    --bad: #c4303a;

    /* Cool paper, not cream. A warm beige would fight the thermal scale, where
       warmth has to mean a warm room, and it is the look every generated design
       reaches for by default. */
    --ink: #e8ecf0;
    --panel: #ffffff;
    --panel-2: #f4f7fa;
    --raised: #eef2f6;
    --rule: #d3dae2;
    --rule-soft: #e2e8ee;
    --text: #14181f;
    --text-dim: #3d454f;
    --muted: #6b7280;
    --accent: #14181f;
    --accent-ink: #ffffff;
    --focus: #0d6f8b;

    --plan-floor: #f7f9fb;
    --plan-line: #c6cfd9;
    --plan-wall: #8593a5;
    --plan-unit: #ffffff;
    --jet-near: var(--t-cold);
    --jet-far: var(--t-cold);

    --shadow: 0 1px 2px rgba(20, 24, 31, 0.05), 0 10px 26px rgba(20, 24, 31, 0.07);
  }
}

* { box-sizing: border-box; }

html {
  /* The fix for the page jumping a few pixels sideways between tabs: without a
     reserved gutter, a short page has no scrollbar and a long one does, so the
     viewport width changes and every centred element shifts. */
  scrollbar-gutter: stable;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  padding: 18px 16px calc(96px + env(safe-area-inset-bottom));
  background: var(--ink);
  color: var(--text);
  font: 15px/1.55 var(--sans);
  -webkit-tap-highlight-color: transparent;
  overflow-x: hidden;
}

/* Every measured value wears the machined face. The contrast between these
   numbers and the quiet prose around them is the typographic personality. */
.v, .big, .countdown, .ring-text, .strip dd, .rows dd, .t-value,
.power-value, .nudge-num, .detail-grid dd, .bar-label, .month-year,
.log-time, .run-day, .run-time, .evidence dd, .ev-time {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}

.shell { max-width: 560px; margin: 0 auto; }

/* --- temperature: one hue scale, used everywhere ------------------------- */

.t-cold    { color: var(--t-cold); }
.t-cool    { color: var(--t-cool); }
.t-comfort { color: var(--t-comfort); }
.t-warm    { color: var(--t-warm); }
.t-hot     { color: var(--t-hot); }
.t-none    { color: var(--muted); }

/* --- header -------------------------------------------------------------- */

.head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; margin-bottom: 20px;
}
.head h1 {
  margin: 0; font-size: 1.15rem; font-weight: 600; letter-spacing: -0.02em;
}
.sub { margin: 2px 0 0; color: var(--muted); font-size: 0.84rem; }

.btn-plain {
  background: none; border: 0; color: var(--muted);
  font: inherit; font-size: 0.84rem; padding: 10px 2px;
  min-height: var(--tap); cursor: pointer;
}
.btn-plain:hover { color: var(--text); }

/* --- panels -------------------------------------------------------------- */

.card {
  background: var(--panel);
  border: 1px solid var(--rule-soft);
  border-radius: var(--r-lg);
  padding: 18px;
  margin-bottom: var(--gap);
  box-shadow: var(--shadow);
}
.card h2 {
  margin: 0 0 14px;
  font-size: 0.68rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.12em;
  color: var(--muted);
}

.tag {
  display: inline-block; margin-left: 6px; padding: 2px 7px;
  border: 1px solid var(--rule); border-radius: 4px;
  color: var(--muted); font-size: 0.56rem; letter-spacing: 0.08em;
  font-weight: 600; vertical-align: middle; text-transform: uppercase;
}

/* --- the readout --------------------------------------------------------- */

.hero {
  position: relative;
  background:
    radial-gradient(120% 90% at 50% 0%, var(--panel-2), var(--panel) 70%);
  border: 1px solid var(--rule-soft);
  border-radius: var(--r-lg);
  padding: 22px 18px 0;
  margin-bottom: var(--gap);
  overflow: hidden;
  box-shadow: var(--shadow);
}

.hero-state {
  display: flex; align-items: center; justify-content: center; gap: 7px;
  font-size: 0.66rem; color: var(--muted);
  text-transform: uppercase; letter-spacing: 0.16em; font-weight: 700;
}
.dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--muted); flex: none;
}
.dot-on { background: var(--t-cold); animation: breathe 3.2s ease-in-out infinite; }
@keyframes breathe { 50% { opacity: 0.35; } }

.hero-temp {
  margin: 10px 0 0; line-height: 0.9; text-align: center;
}
.big {
  font-size: clamp(3.6rem, 21vw, 5.4rem);
  font-weight: 300; letter-spacing: -0.05em;
}
.unit { font-size: 1.1rem; color: var(--muted); margin-left: 4px; letter-spacing: 0; }
.hero-label {
  margin: 8px 0 0; color: var(--muted); font-size: 0.82rem; text-align: center;
}
.hint { display: block; font-size: 0.72rem; color: var(--warn); margin-top: 3px; }

/* The instrument cluster: hairline-divided, like a gauge row. */
.strip {
  display: grid; grid-template-columns: repeat(3, 1fr);
  margin: 18px -18px 0; padding: 0;
  border-top: 1px solid var(--rule-soft);
}
.strip div {
  min-width: 0; padding: 13px 8px; text-align: center;
  border-left: 1px solid var(--rule-soft);
}
.strip div:first-child { border-left: 0; }
.strip dt {
  color: var(--muted); font-size: 0.6rem;
  text-transform: uppercase; letter-spacing: 0.1em; font-weight: 700;
}
.strip dd { margin: 5px 0 0; font-size: 1.15rem; }

.freshness {
  margin: 0; padding: 9px 0 11px; text-align: center;
  font-size: 0.7rem; color: var(--muted);
  border-top: 1px solid var(--rule-soft);
}
.freshness.is-stale { color: var(--warn); }

/* --- trend: does the hallway respond ------------------------------------- */

.trend {
  margin: 0 -18px; border-top: 1px solid var(--rule-soft);
  /* A sparkline is conventionally scaled non-uniformly; clipping keeps the
     stroke from bleeding past the panel edge when it is. */
  overflow: hidden;
}
.trend svg { display: block; width: 100%; height: 74px; }
.trend-line {
  fill: none; stroke: currentColor; stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
}
.trend-fill { fill: url(#trendfade); }
.trend-target { stroke: var(--muted); stroke-width: 1; stroke-dasharray: 3 4; opacity: 0.6; }
.trend-caption {
  display: flex; justify-content: space-between; gap: 10px;
  padding: 0 18px 12px; font-size: 0.68rem; color: var(--muted);
}

/* --- the promise: the signature element ---------------------------------- */

.promise { text-align: center; }
.ring { display: block; margin: 2px auto 10px; width: 168px; height: 168px; }
.ring-track { fill: none; stroke: var(--rule); stroke-width: 6; }
/* The arc is time remaining; its colour is the temperature of the air actually
   being delivered. One object, two true readings. */
.ring-fill {
  fill: none; stroke: currentColor; stroke-width: 6; stroke-linecap: round;
  transform: rotate(-90deg); transform-origin: 50% 50%;
  transition: stroke-dashoffset 900ms linear;
}
.ring-text { font-family: var(--mono); font-size: 27px; font-weight: 300; fill: var(--text); }
.ring-sub {
  font-size: 8.5px; fill: var(--muted); letter-spacing: 0.16em;
  text-transform: uppercase; font-family: var(--sans); font-weight: 700;
}
.countdown { display: none; }
.promise-meta { margin: 0 0 16px; color: var(--muted); font-size: 0.78rem; }

/* --- power --------------------------------------------------------------- */

.power { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.power-value { font-size: 1.9rem; font-weight: 300; }
.power-rate { margin-left: auto; color: var(--muted); font-size: 0.84rem; }

/* Magnitude is brightness, never hue: hue is spoken for. */
.meter {
  height: 5px; border-radius: 99px; background: var(--rule);
  overflow: hidden; margin: 13px 0 9px;
}
.meter span {
  display: block; height: 100%; border-radius: 99px;
  background: linear-gradient(90deg, var(--muted), var(--text));
  transition: width 900ms cubic-bezier(0.22, 1, 0.36, 1);
}

/* --- controls ------------------------------------------------------------ */

.btn {
  display: block; width: 100%; min-height: var(--tap); padding: 13px 16px;
  border: 1px solid var(--rule); border-radius: var(--r-md);
  background: var(--raised); color: var(--text);
  font: inherit; font-weight: 600; cursor: pointer;
  transition: border-color 160ms ease, transform 110ms ease, background 160ms ease;
  /* Anchors carry an underline and their own colour by default, so a link
     wearing .btn came out looking like text with a box round it. Declared on
     the component rather than patched with an inline style at each call site. */
  text-decoration: none; text-align: center;
}
a.btn { color: var(--text); }
a.btn-primary { color: var(--accent-ink); }
.btn:hover { border-color: var(--accent); }
.btn:active { transform: translateY(1px); }
.btn-primary {
  background: var(--accent); border-color: var(--accent); color: var(--accent-ink);
  font-weight: 700;
}
.btn-primary:hover { filter: brightness(1.08); }
.btn-stop { background: transparent; border-color: var(--bad); color: var(--bad); }
.btn-stop:hover { background: var(--bad); color: var(--ink); border-color: var(--bad); }
.btn-small { width: auto; min-height: 40px; padding: 8px 14px; font-size: 0.84rem; }

.btn-start { display: grid; gap: 3px; padding: 17px; }
.start-main { font-size: 1.1rem; font-weight: 700; letter-spacing: -0.01em; }
.start-sub {
  font-family: var(--mono); font-size: 0.74rem; opacity: 0.8; font-weight: 500;
}
.mb { margin-bottom: 18px; }

.durations { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.durations form { margin: 0; }
.btn-duration {
  display: flex; flex-direction: column; align-items: center; gap: 1px;
  padding: 16px 6px; min-height: 74px;
}
.d-num { font-family: var(--mono); font-size: 1.45rem; font-weight: 500; }
.d-unit {
  font-size: 0.62rem; color: var(--muted); font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.1em;
}

.extend { display: flex; gap: 8px; justify-content: center; margin: 0 0 14px; flex-wrap: wrap; }
.extend form { margin: 0; }
.btn-extend {
  min-height: 40px; padding: 8px 15px;
  border: 1px solid var(--rule); border-radius: 99px;
  background: transparent; color: var(--text-dim);
  font: inherit; font-family: var(--mono); font-size: 0.8rem; cursor: pointer;
  transition: border-color 160ms ease, color 160ms ease;
}
.btn-extend:hover { border-color: var(--accent); color: var(--text); }

.nudge {
  display: flex; align-items: center; justify-content: center; gap: 20px;
  margin: 0 0 16px;
}
.nudge form { margin: 0; }
.btn-nudge {
  width: 48px; height: 48px; border-radius: 50%;
  border: 1px solid var(--rule); background: transparent;
  color: var(--text-dim); font-size: 1.35rem; line-height: 1; cursor: pointer;
  transition: border-color 160ms ease, color 160ms ease, transform 110ms ease;
}
.btn-nudge:hover { border-color: var(--accent); color: var(--text); }
.btn-nudge:active { transform: scale(0.93); }
.nudge-value { display: grid; place-items: center; min-width: 72px; }
.nudge-num { font-size: 1.6rem; font-weight: 300; }
.nudge-label {
  font-size: 0.56rem; color: var(--muted);
  text-transform: uppercase; letter-spacing: 0.12em; font-weight: 700;
}

.more { margin-top: 14px; }
.custom { display: grid; gap: 12px; margin-top: 8px; }

/* --- forms --------------------------------------------------------------- */

.form { display: grid; gap: 15px; }
.inline-form { display: grid; grid-template-columns: 1fr auto; gap: 10px; align-items: end; }
label {
  display: grid; gap: 6px; font-size: 0.72rem; color: var(--muted);
  text-transform: uppercase; letter-spacing: 0.08em; font-weight: 700;
}
label.check {
  display: flex; align-items: center; gap: 11px;
  text-transform: none; letter-spacing: 0; font-weight: 400;
  font-size: 0.85rem; color: var(--text-dim);
}
label.check input { width: 20px; height: 20px; accent-color: var(--accent); }

input[type="text"], input[type="email"], input[type="password"],
input[type="number"], select {
  width: 100%; min-height: var(--tap); padding: 12px 14px;
  border: 1px solid var(--rule); border-radius: var(--r-md);
  background: var(--ink); color: var(--text);
  /* 16px minimum, or iOS zooms the page on focus. */
  font: inherit; font-size: 16px; letter-spacing: 0; text-transform: none;
}
input[type="number"] { font-family: var(--mono); }
input:focus-visible, .btn:focus-visible, select:focus-visible,
.tab:focus-visible, .month:focus-visible, a:focus-visible,
button:focus-visible, summary:focus-visible {
  outline: 2px solid var(--focus); outline-offset: 2px;
}
.help {
  font-size: 0.7rem; color: var(--muted);
  text-transform: none; letter-spacing: 0; font-weight: 400;
}

/* --- banners ------------------------------------------------------------- */

.banner {
  border-radius: var(--r-md); padding: 12px 14px; margin-bottom: var(--gap);
  font-size: 0.86rem; border: 1px solid; border-left-width: 3px;
  background: var(--panel);
}
.banner-bad { border-color: var(--rule-soft); border-left-color: var(--bad); }
.banner-ok { border-color: var(--rule-soft); border-left-color: var(--ok); }
.banner-advice { border-color: var(--rule-soft); border-left-color: var(--warn); }

.muted { color: var(--muted); font-size: 0.82rem; }
.mt { margin-top: 14px; }
.ok-text { color: var(--ok); font-weight: 600; }
.bad-text { color: var(--bad); font-weight: 600; }

.rows { display: grid; grid-template-columns: 1fr auto; margin: 0; }
.rows dt, .rows dd {
  margin: 0; padding: 12px 0; border-top: 1px solid var(--rule-soft);
  min-width: 0; overflow-wrap: anywhere;
}
.rows dt:first-of-type, .rows dt:first-of-type + dd { border-top: none; }
.rows dt { color: var(--muted); font-size: 0.84rem; }
.rows dd { text-align: right; }

.plain { list-style: none; margin: 0; padding: 0; }
.plain li { padding: 11px 0; border-top: 1px solid var(--rule-soft); font-size: 0.86rem; }
.plain li:first-child { border-top: none; }

/* --- the room plan ------------------------------------------------------- */

.plan-card { padding-bottom: 12px; }
.plan { margin: 0 -4px 10px; }
.plan svg {
  width: 100%; height: auto; display: block;
  /* Capped: SVG text scales with the drawing, so at full desktop width the
     11px labels would render near 30px and the diagram would eat the page. */
  max-width: 430px; margin-inline: auto;
}
.plan-label {
  font-size: 9px; fill: var(--muted); font-family: var(--sans);
  text-transform: uppercase; letter-spacing: 0.1em; font-weight: 700;
}

.jet {
  transform-origin: 160px 46px;
  transform: rotate(var(--angle, 0deg));
  transition: transform 1.2s cubic-bezier(0.22, 1, 0.36, 1);
  opacity: 0.2;
}
.jet.is-live { opacity: 1; }
.jet.is-swinging.is-live { animation: sweep 9s ease-in-out infinite; }
@keyframes sweep {
  0%, 100% { transform: rotate(-55deg); }
  50%      { transform: rotate(30deg); }
}
.mote { fill: var(--t-cold); opacity: 0; }
.m1 { animation: drift 2.6s linear infinite; }
.m2 { animation: drift 2.6s linear infinite 0.85s; }
.m3 { animation: drift 2.6s linear infinite 1.7s; }
@keyframes drift {
  0%   { transform: translateY(0);    opacity: 0; }
  15%  { opacity: 0.8; }
  100% { transform: translateY(88px); opacity: 0; }
}

.door rect {
  fill: var(--panel-2); stroke: var(--rule); stroke-width: 1.4;
  transition: fill 300ms ease, stroke 300ms ease;
}
.door.is-target rect { fill: var(--raised); stroke: var(--t-cold); }
.door-name { font-size: 11px; fill: var(--text); font-weight: 600; font-family: var(--sans); }
.door-temp { font-size: 10px; fill: currentColor; font-family: var(--mono); }

.picks { display: grid; gap: 9px; }
.pick {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 14px; min-height: var(--tap);
  border: 1px solid var(--rule); border-radius: var(--r-md);
  background: var(--ink); cursor: pointer;
  text-transform: none; letter-spacing: 0; font-weight: 400; font-size: 1rem;
  transition: border-color 180ms ease;
}
.pick:has(input:checked) { border-color: var(--t-cold); }
.pick input { width: 20px; height: 20px; accent-color: var(--t-cold); }
.pick-body { display: grid; }
.pick-name { font-weight: 600; color: var(--text); }
.pick-meta { font-size: 0.72rem; color: var(--muted); font-family: var(--mono); }
.why { color: var(--muted); font-size: 0.82rem; margin: 13px 0 14px; }

.offset-row {
  display: grid; grid-template-columns: 1fr auto auto; gap: 8px;
  align-items: center; margin-bottom: 8px;
}
.offset-name { font-size: 0.9rem; }
.offset-row input { width: 96px; }

/* --- history ------------------------------------------------------------- */

.months {
  display: flex; gap: 6px;
  overflow-x: auto; overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  margin: 0 -18px 16px; padding: 2px 18px 10px;
  scrollbar-width: none;
}
.months::-webkit-scrollbar { display: none; }
.month {
  scroll-snap-align: center;
  flex: none; display: grid; place-items: center; gap: 0;
  min-width: 56px; min-height: 46px; padding: 7px 12px;
  border: 1px solid var(--rule); border-radius: var(--r-sm);
  background: transparent; color: var(--muted);
  text-decoration: none; line-height: 1.2;
  transition: border-color 160ms ease, color 160ms ease, background 160ms ease;
}
.month:hover { border-color: var(--accent); color: var(--text); }
.month.is-selected {
  background: var(--accent); border-color: var(--accent); color: var(--accent-ink);
  font-weight: 700;
}
.month-name { font-size: 0.82rem; text-transform: capitalize; }
.month-year { font-size: 0.56rem; opacity: 0.75; }

.month-head { display: flex; align-items: baseline; gap: 9px; margin-bottom: 16px; }
.month-title {
  margin: 0; font-size: 1rem; font-weight: 600;
  text-transform: capitalize; letter-spacing: -0.01em;
}

.totals {
  display: grid; grid-template-columns: repeat(3, 1fr);
  margin: 0 -18px 18px; border-top: 1px solid var(--rule-soft);
  border-bottom: 1px solid var(--rule-soft);
}
.totals div {
  text-align: center; padding: 14px 8px; border-left: 1px solid var(--rule-soft);
}
.totals div:first-child { border-left: 0; }
.t-value { display: block; font-size: 1.45rem; font-weight: 300; }
.t-unit {
  font-size: 0.58rem; color: var(--muted); text-transform: uppercase;
  letter-spacing: 0.1em; font-weight: 700;
}

.chart {
  display: flex; align-items: flex-end; gap: 3px;
  height: 128px; overflow-x: auto; padding-bottom: 4px;
  scrollbar-width: thin;
}
.bar-col { flex: 1 0 15px; display: flex; flex-direction: column; align-items: center; gap: 6px; }
.bar-track { flex: 1; width: 100%; display: flex; align-items: flex-end; }
.bar {
  width: 100%; border-radius: 3px 3px 1px 1px;
  background: var(--text-dim);
  height: var(--h);
  animation: grow 700ms cubic-bezier(0.22, 1, 0.36, 1) backwards;
}
@keyframes grow { from { height: 2%; opacity: 0.2; } }
.bar-col.is-today .bar { background: var(--accent); }
.bar-col.is-today .bar-label { color: var(--accent); font-weight: 700; }
.bar-col.is-future .bar-track { border-bottom: 1px dotted var(--rule); }
.bar-col.is-future .bar-label { opacity: 0.3; }
.bar-label { font-size: 0.58rem; color: var(--muted); }

.runs { list-style: none; margin: 0; padding: 0; display: grid; gap: 9px; }
.run {
  border: 1px solid var(--rule-soft); border-radius: var(--r-md);
  padding: 13px 14px; background: var(--panel-2);
}
.run-top { display: flex; justify-content: space-between; align-items: center; }
.run-day { font-weight: 500; font-size: 0.95rem; }
.run-badge {
  font-size: 0.6rem; padding: 3px 9px; border-radius: 4px;
  text-transform: uppercase; letter-spacing: 0.09em; font-weight: 700;
  border: 1px solid var(--rule); color: var(--muted);
}
.run-badge.ok { border-color: var(--ok); color: var(--ok); }
.run-badge.bad { border-color: var(--bad); color: var(--bad); }
.run-body { display: flex; justify-content: space-between; gap: 8px; margin-top: 7px; font-size: 0.82rem; }
.run-who { color: var(--text-dim); }
.run-time { color: var(--muted); font-size: 0.76rem; }
.run-proof { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 9px; }
.proof {
  font-size: 0.64rem; color: var(--muted);
  border: 1px solid var(--rule); padding: 2px 8px; border-radius: 99px;
}
.proof.ok { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 45%, transparent); }
.proof.late, .proof.pending { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 45%, transparent); }

.run-detail { margin-top: 10px; }

.detail-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(96px, 1fr));
  gap: 1px; margin: 4px 0 12px;
  background: var(--rule-soft); border: 1px solid var(--rule-soft);
  border-radius: var(--r-sm); overflow: hidden;
}
.detail-grid > div { background: var(--panel); padding: 10px 11px; }
.detail-grid dt {
  font-size: 0.56rem; color: var(--muted);
  text-transform: uppercase; letter-spacing: 0.09em; font-weight: 700;
}
.detail-grid dd { margin: 4px 0 0; font-size: 0.92rem; }

.timeline { list-style: none; margin: 0; padding: 0; }
.ev {
  display: flex; gap: 11px; padding: 7px 0 7px 13px;
  border-left: 2px solid var(--rule); font-size: 0.82rem;
}
.ev-started { border-left-color: var(--t-cold); }
.ev-stopped { border-left-color: var(--muted); }
.ev-extended, .ev-shortened { border-left-color: var(--accent); }
.ev-setpoint { border-left-color: var(--t-warm); }
.ev-time { color: var(--muted); font-size: 0.76rem; flex: none; }

.empty {
  text-align: center; padding: 26px 14px;
  border: 1px dashed var(--rule); border-radius: var(--r-md);
}
.empty p { margin: 0 0 8px; }
.empty p:first-child { font-weight: 600; }

/* --- diagnosis ----------------------------------------------------------- */

.verdict {
  display: flex; gap: 14px; align-items: flex-start;
  padding: 17px; border-radius: var(--r-md);
  background: var(--panel-2);
  border: 1px solid var(--rule-soft); border-left: 3px solid var(--ok);
}
.verdict-mark {
  display: grid; place-items: center;
  width: 30px; height: 30px; border-radius: 50%; flex: none;
  border: 1px solid var(--ok); color: var(--ok); font-weight: 700; font-size: 0.9rem;
}
.verdict-title { margin: 0 0 4px; font-weight: 600; }
.verdict p { margin: 0; }

.findings { list-style: none; margin: 0; padding: 0; display: grid; gap: 11px; }
.finding {
  border: 1px solid var(--rule-soft); border-left: 3px solid var(--muted);
  border-radius: var(--r-md); padding: 14px; background: var(--panel-2);
}
.finding.sev-warning { border-left-color: var(--warn); }
.finding.sev-critical { border-left-color: var(--bad); }
.finding.is-ack { opacity: 0.55; }

.finding-head { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.sev-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--muted); flex: none; }
.sev-warning .sev-dot { background: var(--warn); }
.sev-critical .sev-dot { background: var(--bad); animation: breathe 1.8s ease-in-out infinite; }
.finding-sev {
  font-size: 0.6rem; text-transform: uppercase; letter-spacing: 0.11em;
  font-weight: 700; color: var(--muted);
}
.sev-warning .finding-sev { color: var(--warn); }
.sev-critical .finding-sev { color: var(--bad); }
.finding-text { margin: 0 0 10px; font-size: 0.89rem; }

.evidence {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(90px, 1fr));
  gap: 1px; margin: 0 0 11px;
  background: var(--rule-soft); border-radius: var(--r-sm); overflow: hidden;
}
.evidence > div { background: var(--panel); padding: 9px 10px; }
.evidence dt {
  font-size: 0.56rem; color: var(--muted);
  text-transform: uppercase; letter-spacing: 0.08em; font-weight: 700;
}
.evidence dd { margin: 3px 0 0; font-size: 0.86rem; overflow-wrap: anywhere; }

.gauge {
  height: 5px; border-radius: 99px; background: var(--rule);
  overflow: hidden; margin-bottom: 11px;
}
.gauge-fill {
  height: 100%; width: var(--pct); border-radius: 99px;
  background: var(--text-dim);
  transition: width 800ms cubic-bezier(0.22, 1, 0.36, 1);
}
.gauge-fill.is-warn { background: var(--warn); }
.gauge-fill.is-over { background: var(--bad); }

.watchlist { margin: 0 0 12px; padding-left: 17px; font-size: 0.84rem; color: var(--text-dim); }
.watchlist li { margin-bottom: 6px; }
.watchlist strong { color: var(--text); }

.budget-bar {
  height: 6px; border-radius: 99px; background: var(--rule);
  overflow: hidden; margin-bottom: 9px;
}
.budget-fill {
  height: 100%; width: var(--pct); border-radius: 99px;
  background: var(--text-dim);
  transition: width 700ms cubic-bezier(0.22, 1, 0.36, 1);
}
.budget-fill.is-warn { background: var(--warn); }
.budget-fill.is-over { background: var(--bad); }

/* --- shortcuts ----------------------------------------------------------- */

.shortcuts { display: grid; gap: 9px; }
.shortcut {
  display: flex; align-items: center; gap: 13px;
  padding: 13px; min-height: var(--tap);
  border: 1px solid var(--rule-soft); border-radius: var(--r-md);
  background: var(--panel-2); color: var(--text); text-decoration: none;
  transition: border-color 180ms ease, transform 140ms ease;
}
.shortcut:hover { border-color: var(--accent); transform: translateX(2px); }
.s-ico {
  display: grid; place-items: center;
  width: 34px; height: 34px; border-radius: var(--r-sm);
  border: 1px solid var(--rule); color: var(--muted);
}
.s-ico .ico { width: 19px; height: 19px; }
.s-body { display: grid; }
.s-name { font-weight: 600; font-size: 0.92rem; }
.s-meta { font-size: 0.74rem; color: var(--muted); }

/* --- admin --------------------------------------------------------------- */

.people { list-style: none; margin: 0; padding: 0; }
.person {
  display: flex; justify-content: space-between; align-items: center;
  gap: 10px; padding: 13px 0; border-top: 1px solid var(--rule-soft); flex-wrap: wrap;
}
.person:first-child { border-top: none; }
.person.is-off { opacity: 0.45; }
.person-id { display: grid; min-width: 0; }
.person-name { font-weight: 600; }
.person-mail { font-size: 0.74rem; color: var(--muted); overflow-wrap: anywhere; }
.person-actions { display: flex; gap: 8px; align-items: center; }
.person-actions select { min-height: 38px; padding: 6px 10px; font-size: 0.82rem; width: auto; }

.invite-row { display: flex; justify-content: space-between; align-items: center; gap: 10px; }
.link-box {
  display: block; padding: 12px 14px; border-radius: var(--r-sm);
  background: var(--ink); border: 1px solid var(--rule);
  font-family: var(--mono); font-size: 0.74rem;
  overflow-wrap: anywhere; margin-bottom: 10px; color: var(--t-cold);
}
.invite-out { border-color: var(--accent); }

/* The audit trail. A list, not the table it used to be: three nowrap columns
   needed a horizontal scroller at 390px, and the page body never scrolls
   sideways. Here the time and the actor share a line under the event, so a long
   event name wraps instead of pushing the row off screen. */
.log { list-style: none; margin: 0; padding: 0; font-size: 0.78rem; }
.log-row {
  display: grid;
  grid-template-areas: "event time" "who who";
  grid-template-columns: 1fr auto;
  gap: 2px 12px;
  padding: 9px 0;
  border-top: 1px solid var(--rule-soft);
}
.log-row:first-child { border-top: none; }
.log-event { grid-area: event; overflow-wrap: anywhere; }
.log-time { grid-area: time; color: var(--muted); white-space: nowrap; }
.log-who { grid-area: who; color: var(--muted); font-size: 0.74rem; }
.log-row.is-bad .log-event { color: var(--bad); }

@media (min-width: 560px) {
  /* Room for one line: event, who, time. */
  .log-row {
    grid-template-areas: "event who time";
    grid-template-columns: 1fr auto auto;
    align-items: baseline;
  }
}

/* "Load more". One full-width target where the thumb already is, rather than a
   row of page numbers at 30px each. See src/web/pagination.py. */
.more-wrap:empty { display: none; }
.more-wrap { margin-top: 14px; }
.more { width: 100%; }

/* --- gates: see the entry pages block near the end ---------------------- */

/* --- navigation ---------------------------------------------------------- */

.tabbar-wrap { display: contents; }
.tabbar {
  position: fixed; left: 0; right: 0; bottom: 0;
  display: grid; grid-template-columns: repeat(5, 1fr);
  background: color-mix(in srgb, var(--panel) 85%, transparent);
  backdrop-filter: blur(16px) saturate(1.4);
  border-top: 1px solid var(--rule);
  padding-bottom: env(safe-area-inset-bottom);
  z-index: 20;
}
.tab {
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: 10px 4px; min-height: var(--tap);
  color: var(--muted); text-decoration: none; font-size: 0.62rem;
  letter-spacing: 0.04em; transition: color 160ms ease;
}
.tab.is-active { color: var(--accent); }
/* Drawn icons, not text glyphs. A glyph renders at whatever size and weight
   the system font decides, which is how U+2302 HOUSE ended up looking like a
   small triangle in the Camere tab. An SVG is the size we say it is. */
/* Sized in px on the icon itself, not as a percentage of its wrapper. A
   percentage only resolves because `.tab` happens to be a flex container, which
   makes the span a flex item with a real box; change that and the icons vanish
   with no obvious cause. `display: block` on the wrapper makes it independent
   of whatever the parent does. */
.ico { width: 20px; height: 20px; display: block; }
.tab-ico { display: block; line-height: 0; }

/* ==========================================================================
   Wider screens. The phone layout is the design; this gives it room and, at
   desktop width, sets the readout beside the things you press.
   ========================================================================== */

@media (min-width: 700px) {
  body { padding: 26px 24px 44px; }
  .shell { max-width: 740px; }

  .tabbar-wrap { display: flex; justify-content: center; }
  .tabbar {
    position: sticky; top: 14px; bottom: auto;
    display: inline-grid; grid-auto-flow: column; grid-template-columns: none;
    width: max-content; margin: 0 auto 22px;
    border: 1px solid var(--rule); border-radius: 99px;
    padding: 4px; box-shadow: var(--shadow);
  }
  .tab {
    flex-direction: row; gap: 8px; border-radius: 99px;
    font-size: 0.82rem; padding: 9px 17px; min-height: 40px;
  }
  .tab.is-active { background: var(--raised); }

  .durations { gap: 13px; }
}

@media (min-width: 1024px) {
  .shell { max-width: 1060px; }
  .layout {
    display: grid;
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
    gap: var(--gap); align-items: start;
  }
  .layout > * { min-width: 0; }
  .t-value { font-size: 1.7rem; }
  .chart { height: 168px; }
  .offset-row { grid-template-columns: 1fr 120px auto; }
  .trend svg { height: 92px; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}

/* --- added 2026-08-17 ----------------------------------------------------- */

/* A simulated appliance must never be mistakable for the real one. Its own
   banner colour, not one of the three status hues, because this is not a fault:
   it is a different machine. */
.banner-sim {
  border-color: var(--rule-soft);
  border-left-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 6%, var(--panel));
}

/* The age of a reading, sitting in a section heading. The watt figure is
   derived from the last payload and the cloud's timestamps lag, so this is the
   difference between an estimate and a claim. */
.h2-age {
  float: right; text-transform: none; letter-spacing: 0;
  font-weight: 400; font-size: 0.72rem; color: var(--muted);
}
.h2-age.is-stale { color: var(--warn); }

/* Trend stretches measured with the fan stopped: shown, because the shape is
   still information, but not asserted. Dashed as well as faint, so the
   distinction survives greyscale and colour-blind vision. */
.trend-line.is-estimate {
  opacity: 0.32; stroke-width: 1.5; stroke-dasharray: 2 5;
}
.trend-note {
  margin: 0; padding: 0 18px 12px;
  font-size: 0.7rem; line-height: 1.45; color: var(--muted);
}

/* --- one disclosure control, used everywhere -----------------------------
   "Detalii" read as a plain label, so it was not obviously a control. The first
   fix gave it a border and pushed the chevron to the far edge with
   space-between, which on a wide card left the word stranded on the left and
   the arrow stranded on the right. The owner was right that it looked broken.

   A disclosure is a BUTTON, and a button is as wide as its label. `max-content`
   with a fixed gap keeps the chevron next to the word at every width, from
   390px to a desktop card, so there is nothing to keep responsive.

   The chevron comes from CSS rather than markup, so every <details> on the site
   gets the same control by adding one class and none of them can drift. */

.disclosure > summary {
  display: inline-flex; align-items: center; gap: 9px;
  width: max-content; max-width: 100%;
  cursor: pointer; padding: 9px 14px; min-height: var(--tap);
  border: 1px solid var(--rule); border-radius: var(--r-sm);
  color: var(--muted); font-size: 0.78rem; font-weight: 600;
  /* Both are needed: the marker is a pseudo-element in WebKit and a ::marker
     everywhere else, and a stray triangle would sit outside our layout. */
  list-style: none;
}
.disclosure > summary::-webkit-details-marker { display: none; }
.disclosure > summary::marker { content: ""; }

.disclosure > summary::after {
  content: ""; flex: none;
  width: 7px; height: 7px; margin-top: -3px;
  border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
  transform: rotate(45deg);
  transition: transform 160ms ease;
}
.disclosure[open] > summary::after { transform: rotate(-135deg); margin-top: 2px; }

.disclosure > summary:hover { color: var(--text-dim); border-color: var(--text-dim); }
.disclosure[open] > summary { color: var(--text-dim); }

@media (prefers-reduced-motion: reduce) {
  .disclosure > summary::after { transition: none; }
}

.run-export {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  padding-top: 12px; border-top: 1px solid var(--rule-soft);
}
.run-export .muted { flex: 1 1 180px; font-size: 0.72rem; }

/* Settings groups. Two fields side by side above 420px, stacked below, because
   every screen is designed at 390 first. */
.sub-head {
  margin: 4px 0 0; font-size: 0.68rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.12em; color: var(--text-dim);
}
.field-row { display: grid; gap: 15px; }
@media (min-width: 420px) {
  .field-row { grid-template-columns: 1fr 1fr; }
}
input[type="time"] {
  width: 100%; min-height: var(--tap); padding: 12px 14px;
  border: 1px solid var(--rule); border-radius: var(--r-md);
  background: var(--ink); color: var(--text);
  font: inherit; font-size: 16px; font-family: var(--mono);
  letter-spacing: 0; text-transform: none;
}

.btn-block { width: 100%; text-align: center; text-decoration: none; }

/* --- the invite link ------------------------------------------------------
   A one-time secret. The card is the loudest thing on the page and the copy
   action is one tap, because selecting a long URL by hand on a phone is where
   somebody grabs half of it and sends a broken link. */

.invite-out {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 30%, transparent),
              var(--shadow);
}
.invite-lead { margin: 0 0 12px; font-size: 0.86rem; color: var(--text-dim); }

.invite-out .link-box {
  margin-bottom: 12px; font-size: 0.8rem; line-height: 1.55;
  cursor: text; user-select: all;
}

.invite-actions {
  display: grid; gap: 10px; margin-bottom: 12px;
}
@media (min-width: 420px) {
  .invite-actions { grid-auto-flow: column; grid-auto-columns: 1fr; }
}
/* Confirmation lands on the button that was pressed, where the thumb already
   is, rather than in a banner at the top of a scrolled page. */
.invite-actions .btn.is-done {
  border-color: var(--ok); color: var(--ok); background: transparent;
}

.invite-warn {
  margin: 0; padding-top: 12px; border-top: 1px solid var(--rule-soft);
  font-size: 0.78rem; line-height: 1.5; color: var(--muted);
}

/* --- the entry pages: the card IS the unit -------------------------------
   Login, first setup and invite. The owner's idea, and the right one: instead
   of an illustration above a form, which stacks too tall on a phone, the panel
   you type into is the indoor unit. Fascia on top, the form inside the case,
   the louver along the bottom edge, and cold air falling out of it.

   The only hue here is the cold of the air. That is not a new accent: hue means
   temperature everywhere in this interface, and the air genuinely is cold. Red
   stays reserved for the refusal, as it is everywhere else.

   ONE thing moves. The form does not animate in, nothing fades up, there is no
   shimmer. The louver answers what you are doing and that is the whole show. */

.gate {
  max-width: 430px; margin: 0 auto;
  padding-top: 6vh;
}

.gate-unit {
  background: var(--panel);
  border: 1px solid var(--rule-soft);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow);
  /* The louver slides out of sight behind this edge, so the case has to clip. */
  overflow: hidden;
}

/* Printed on the case, the way a wordmark is on the real one. Deliberately not
   a heading: the h1 below states what this particular page is for. */
.unit-fascia {
  padding: 13px 20px;
  border-bottom: 1px solid var(--rule-soft);
  background: linear-gradient(var(--panel-2), var(--panel));
}
.unit-fascia span {
  font-family: var(--mono);
  font-size: 0.62rem; letter-spacing: 0.34em; text-transform: uppercase;
  color: var(--muted);
}

.unit-body { padding: 22px 20px 24px; }
.unit-body h1 {
  margin: 0 0 6px;
  font-size: 1.42rem; font-weight: 600; letter-spacing: -0.025em;
  line-height: 1.15;
}
.unit-body .sub { margin: 0 0 20px; font-size: 0.88rem; line-height: 1.5; }
.unit-body .muted { margin: 14px 0 0; }
.unit-body .form { gap: 16px; }

/* --- the louver ---------------------------------------------------------- */

.unit-louver {
  position: relative; height: 30px;
  /* The case continues down past the outlet, so the slot reads as a hole cut
     into it rather than as a separate bar stuck on the bottom. */
  background: var(--panel);
  border-top: 1px solid var(--rule-soft);
  overflow: hidden;
}
/* The outlet: a recess, which needs depth or it reads as a flat stripe. */
.unit-louver::before {
  content: ""; position: absolute; left: 14px; right: 14px; top: 5px; bottom: 0;
  border-radius: 8px 8px 0 0;
  background: var(--ink);
  box-shadow: inset 0 3px 7px rgba(0, 0, 0, 0.55);
}
/* The blade sits in the recess and drops away from it. What is revealed above
   the blade is the dark slot, which is what an open louver looks like head on.
   Kept a shade under the case rather than over it: brighter than its
   surroundings and it stops being a part and starts being a highlight. */
.louver-flap {
  position: absolute; left: 14px; right: 14px; top: 5px; height: 22px;
  border-radius: 8px 8px 5px 5px;
  background: var(--panel-2);
  border-top: 1px solid var(--rule);
  transform: translateY(8px);
  transition: transform 520ms cubic-bezier(0.32, 0.9, 0.3, 1);
}
/* Shut: a dead invite link. There is genuinely no way in through this page and
   the drawing should say so rather than invite somebody to try. */
.gate.is-shut .louver-flap { transform: translateY(0); }
/* Refused: the machine said no. It opens again the moment a field is touched,
   which is the loop, and it is why the refusal needs no card of its own. */
.gate.is-refused .louver-flap { transform: translateY(0); }
.gate.is-live .louver-flap { transform: translateY(13px); }

/* --- the air ------------------------------------------------------------- */

.gate-air {
  position: relative;
  height: 104px;
  transition: opacity 420ms ease;
}
/* Five hairlines alone were nearly invisible against a near-black hallway. The
   cone is what actually reads as air pouring out; the streaks give it grain.
   It has to reach full transparency INSIDE the box on every side, or the box
   itself shows up as a lighter rectangle with hard edges. */
.gate-air::before {
  content: ""; position: absolute; inset: 0;
  background: radial-gradient(
    74% 96% at 50% 0%,
    color-mix(in srgb, var(--t-cold) 18%, transparent),
    color-mix(in srgb, var(--t-cold) 5%, transparent) 46%,
    transparent 76%
  );
}
.gate-air svg {
  display: block; width: 100%; height: 100%;
  position: relative;
}

.jet {
  fill: none;
  stroke: url(#fall);
  stroke-width: 2;
  stroke-linecap: round;
  /* A short travelling segment on a long invisible remainder: the dash IS the
     puff of air, and the gap is the pause between them. The gap is only a
     little longer than the run, so at any moment most of the streaks are
     visible; a very long gap left the page looking still. */
  stroke-dasharray: 46 116;
  animation: fall 3.2s linear infinite;
}
.j1 { animation-delay: -0.2s; }
.j2 { animation-delay: -1.5s; }
.j3 { animation-delay: -0.8s; }
.j4 { animation-delay: -2.4s; }
.j5 { animation-delay: -1.1s; }

@keyframes fall {
  from { stroke-dashoffset: 162; }
  to   { stroke-dashoffset: 0; }
}

/* No air when there is no way in, and none while the machine is refusing. The
   height collapses with it: 100px of reserved nothing under a shut louver
   reads as a layout bug rather than as a machine that has closed. */
.gate.is-shut .gate-air,
.gate.is-refused .gate-air { opacity: 0; height: 26px; }
.gate.is-live .jet { animation-duration: 2.6s; }

/* --- the refusal --------------------------------------------------------- */
/* No card above the box. It lands inside the form, directly above the button,
   which is where the eye already is at the moment it is pressed. */

.refusal {
  display: flex; align-items: flex-start; gap: 9px;
  margin: 0; padding-top: 14px;
  border-top: 1px solid color-mix(in srgb, var(--bad) 35%, transparent);
  color: var(--bad); font-size: 0.85rem; line-height: 1.45;
}
.refusal-mark {
  flex: none; width: 15px; height: 15px; margin-top: 2px;
  border: 1.5px solid currentColor; border-radius: 50%;
  position: relative;
}
/* A slash through the circle. Colour is never the only signal. */
.refusal-mark::after {
  content: ""; position: absolute; inset: 0;
  background: currentColor;
  height: 1.5px; top: 50%; margin-top: -0.75px;
  transform: rotate(-45deg);
}
input[aria-invalid="true"] { border-color: var(--bad); }

/* No tab bar on these pages, so nothing to leave room for at the bottom. */
body.is-gate { padding-bottom: calc(28px + env(safe-area-inset-bottom)); }

.gate-foot {
  margin: 14px 0 0; text-align: center;
  color: var(--muted); font-size: 0.78rem; line-height: 1.5;
}

@media (min-width: 600px) {
  .gate { max-width: 460px; padding-top: 8vh; }
  .unit-body { padding: 26px 26px 28px; }
  .unit-fascia { padding: 15px 26px; }
  .unit-body h1 { font-size: 1.6rem; }
  .gate-air { height: 140px; }
}

/* A hallway at night, a person half asleep. Motion that cannot be turned off is
   motion imposed on somebody. */
@media (prefers-reduced-motion: reduce) {
  .jet { animation: none; stroke-dasharray: none; stroke-opacity: 0.45; }
  .louver-flap, .gate-air { transition: none; }
}

/* On a screen with room to spare the unit sits in the middle of it rather than
   pinned to the top of a mostly empty page. Guarded on height as well as width:
   a short landscape phone has no room to centre anything and would just push
   the button off screen. */
@media (min-width: 600px) and (min-height: 720px) {
  body.is-gate {
    display: flex; align-items: center; justify-content: center;
    min-height: 100dvh;
  }
  body.is-gate .shell { width: 100%; }
  .gate { padding-top: 0; }
}
