:root {
  --paper: #f7f6f4;
  --paper-2: #eeece8;
  --card: #ffffff;
  --ink: #141414;
  --ink-soft: #3d3d3d;
  --muted: #6f6d6a;
  --line: rgba(20, 20, 20, 0.12);
  --line-soft: rgba(20, 20, 20, 0.07);
  --rose: #ff507d;
  --rose-deep: #d33d69;
  --night: #131313;
  --night-2: #1d1c1c;
  --night-line: rgba(255, 255, 255, 0.14);
  --night-muted: #9d9a97;

  --sans: "Manrope", system-ui, -apple-system, sans-serif;
  --mono: "JetBrains Mono", ui-monospace, "SF Mono", monospace;

  --cross: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6.6 6.6l10.8 10.8M17.4 6.6L6.6 17.4'/%3E%3C/svg%3E");
  --tick: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 12.8l5.4 5.2L20 6'/%3E%3C/svg%3E");

  --pad: clamp(1.15rem, 4vw, 4rem);
  --max: 74rem;
  --r: 14px;
  --r-lg: 22px;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
  overflow-x: hidden;
  overflow-x: clip;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 1.0625rem;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
  overflow-x: clip;
}

img,
svg,
video {
  display: block;
  max-width: 100%;
}

a {
  color: inherit;
  text-decoration: none;
}

p {
  margin: 0;
}

h1,
h2,
h3,
h4 {
  margin: 0;
  font-weight: 800;
  letter-spacing: -0.032em;
  line-height: 1.06;
  text-wrap: balance;
}

h1 {
  font-size: clamp(2.3rem, 6.4vw, 4.9rem);
}

h2 {
  font-size: clamp(1.85rem, 4.1vw, 3.1rem);
}

h3 {
  font-size: clamp(1.3rem, 2.2vw, 1.7rem);
  letter-spacing: -0.022em;
}

h4 {
  font-size: 1.06rem;
  letter-spacing: -0.014em;
  line-height: 1.3;
}

/* ---------- utilities ---------- */

.wrap {
  max-width: calc(var(--max) + var(--pad) * 2);
  margin-inline: auto;
  padding-inline: var(--pad);
}

.section {
  padding-block: clamp(3.5rem, 8vw, 7.5rem);
}

.skip {
  position: absolute;
  left: -9999px;
}

.skip:focus {
  left: 1rem;
  top: 1rem;
  z-index: 60;
  background: var(--ink);
  color: var(--paper);
  padding: 0.6rem 0.9rem;
  border-radius: 8px;
}

.label {
  font-family: var(--mono);
  font-size: 0.7rem;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--rose-deep);
}

.label--ico {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
}

.label--ico .ico {
  font-size: 1.15rem;
}

.lead {
  font-size: clamp(1.06rem, 1.55vw, 1.28rem);
  color: var(--ink-soft);
  max-width: 40rem;
}

/* ---------- icons ---------- */

.sprite {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

.ico {
  /* Sized in em so an icon always matches the text it sits next to. */
  width: 1em;
  height: 1em;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
  vertical-align: -0.135em;
}

.tile {
  display: grid;
  place-items: center;
  width: 2.6rem;
  height: 2.6rem;
  border-radius: 10px;
  background: rgba(255, 80, 125, 0.1);
  color: var(--rose-deep);
  font-size: 1.6rem;
}

.dark .tile,
.ai .tile {
  background: rgba(255, 80, 125, 0.16);
  color: var(--rose);
}

.muted {
  color: var(--muted);
}

.head {
  max-width: 46rem;
  margin-bottom: clamp(2rem, 4vw, 3.4rem);
}

.head .label {
  display: block;
  margin-bottom: 1rem;
}

.head p {
  margin-top: 1.1rem;
  color: var(--muted);
  max-width: 38rem;
}

/* ---------- buttons ---------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  height: 3rem;
  white-space: nowrap;
  padding-inline: 1.35rem;
  border: 1px solid transparent;
  border-radius: 999px;
  font: inherit;
  font-size: 0.95rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  cursor: pointer;
  transition: background 0.16s ease, color 0.16s ease, border-color 0.16s ease,
    transform 0.16s ease;
}

.btn:active {
  transform: translateY(1px);
}

.btn--primary {
  background: var(--rose);
  color: #fff;
}

.btn--primary:hover {
  background: var(--rose-deep);
}

.btn--dark {
  background: var(--ink);
  color: var(--paper);
}

.btn--dark:hover {
  background: #2e2e2e;
}

.btn--outline {
  border-color: var(--line);
  color: var(--ink);
}

.btn--outline:hover {
  border-color: var(--ink);
  background: rgba(20, 20, 20, 0.04);
}

.btn--light {
  background: #fff;
  color: var(--ink);
}

.btn--light:hover {
  background: var(--paper-2);
}

.btn--ghost-light {
  border-color: var(--night-line);
  color: #fff;
}

.btn--ghost-light:hover {
  background: rgba(255, 255, 255, 0.09);
}

.btn--lg {
  height: 3.4rem;
  padding-inline: 1.7rem;
  font-size: 1rem;
}

/* ---------- nav ---------- */

.nav {
  position: sticky;
  top: 0;
  z-index: 50;
  background: rgba(247, 246, 244, 0.82);
  backdrop-filter: blur(18px);
  border-bottom: 1px solid var(--line-soft);
}

.nav__inner {
  display: flex;
  align-items: center;
  gap: 1rem;
  height: 4.4rem;
}

.brand {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  flex-shrink: 0;
}

.brand img {
  width: 2.15rem;
  height: 2.15rem;
}

.brand__slogan {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.03em;
  color: var(--muted);
  padding-left: 0.65rem;
  border-left: 1px solid var(--line);
  white-space: nowrap;
}

.nav__links {
  display: flex;
  gap: 1.35rem;
  margin-left: auto;
  font-size: 0.95rem;
  font-weight: 500;
  color: var(--ink-soft);
}

.nav__links a {
  position: relative;
  padding-block: 0.2rem;
  white-space: nowrap;
}

.nav__links a::after {
  content: "";
  position: absolute;
  left: 0;
  right: 100%;
  bottom: 0;
  height: 1.5px;
  background: var(--rose);
  transition: right 0.2s ease;
}

.nav__links a:hover::after {
  right: 0;
}

.nav__actions {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.burger {
  display: none;
  width: 2.9rem;
  height: 2.9rem;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: transparent;
  cursor: pointer;
}

.burger i,
.burger i::before,
.burger i::after {
  display: block;
  width: 1.05rem;
  height: 1.6px;
  background: var(--ink);
  border-radius: 2px;
}

.burger i {
  margin-inline: auto;
  position: relative;
}

.burger i::before,
.burger i::after {
  content: "";
  position: absolute;
  left: 0;
}

.burger i::before {
  top: -5px;
}

.burger i::after {
  top: 5px;
}

/* ---------- hero ---------- */

.hero {
  padding-top: clamp(2.6rem, 5.5vw, 5rem);
  padding-bottom: clamp(2.5rem, 5vw, 4.5rem);
  position: relative;
}

.hero::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 34rem;
  background: radial-gradient(
      760px 340px at 12% 0%,
      rgba(255, 80, 125, 0.14),
      transparent 62%
    ),
    radial-gradient(520px 300px at 88% 12%, rgba(20, 20, 20, 0.05), transparent 60%);
  pointer-events: none;
}

.hero__inner {
  position: relative;
}

/* Контур Беларуси обводится один раз при загрузке и остаётся на месте.
   Живёт под контентом, в пустой зоне справа от заголовка. */
.hero__map {
  position: absolute;
  top: clamp(1rem, 3.5vw, 3rem);
  right: var(--pad);
  z-index: 0;
  width: clamp(16rem, 30vw, 27rem);
  height: auto;
  /* Заголовок заходит на контур, поэтому он остаётся водяным знаком:
     линия проходит под текстом и не спорит с ним за внимание. */
  opacity: 0.2;
  pointer-events: none;
}

.hero__map path {
  fill: none;
  stroke: var(--rose);
  stroke-width: 26;
  stroke-linejoin: round;
  stroke-linecap: round;
  stroke-dasharray: 1000;
  stroke-dashoffset: 1000;
  animation: hero-map-draw 5s cubic-bezier(0.45, 0, 0.3, 1) 0.3s forwards;
}

@keyframes hero-map-draw {
  to {
    stroke-dashoffset: 0;
  }
}

.hero__inner > *:not(.hero__map) {
  position: relative;
  z-index: 1;
}

.hero h1 {
  max-width: 30ch;
  margin-top: 1.3rem;
}

.hero h1 em {
  font-style: normal;
  color: var(--rose-deep);
}

.hero__lead {
  margin-top: 1.6rem;
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.7rem;
  margin-top: 2rem;
}

.hero__facts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 2.4rem;
  margin: 2.4rem 0 0;
  padding: 1.6rem 0 0;
  list-style: none;
  border-top: 1px solid var(--line-soft);
  font-family: var(--mono);
  font-size: 0.78rem;
  letter-spacing: 0.02em;
  color: var(--muted);
}

.hero__facts li {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}

.hero__facts b {
  color: var(--ink);
  font-weight: 500;
}

.hero__facts .ico {
  font-size: 1.15rem;
  color: var(--rose-deep);
}

.hero__facts a {
  color: var(--rose-deep);
  border-bottom: 1px solid rgba(211, 61, 105, 0.35);
}

.hero__facts a:hover {
  border-bottom-color: var(--rose-deep);
}

/* ---------- video ---------- */

.player {
  position: relative;
  margin-top: clamp(2.2rem, 5vw, 3.6rem);
  border-radius: var(--r-lg);
  overflow: hidden;
  background: var(--night);
  aspect-ratio: 16 / 9;
  border: 1px solid var(--line);
}

.player img,
.player iframe {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border: 0;
}

.player__btn {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  border: 0;
  background: linear-gradient(
    to top,
    rgba(0, 0, 0, 0.45),
    rgba(0, 0, 0, 0.05) 55%
  );
  cursor: pointer;
  padding: 0;
}

.player__play {
  width: 4.6rem;
  height: 4.6rem;
  border-radius: 50%;
  background: var(--rose);
  display: grid;
  place-items: center;
  box-shadow: 0 16px 40px rgba(0, 0, 0, 0.35);
  transition: transform 0.18s ease, background 0.18s ease;
}

.player__btn:hover .player__play {
  transform: scale(1.07);
  background: #fff;
}

.player__play::after {
  content: "";
  border-style: solid;
  border-width: 0.62rem 0 0.62rem 1.05rem;
  border-color: transparent transparent transparent #fff;
  margin-left: 0.28rem;
  transition: border-color 0.18s ease;
}

.player__btn:hover .player__play::after {
  border-left-color: var(--ink);
}

.player__cap {
  position: absolute;
  left: 1.3rem;
  bottom: 1.15rem;
  right: 1.3rem;
  color: #fff;
  font-size: 0.9rem;
  font-weight: 600;
  text-shadow: 0 1px 12px rgba(0, 0, 0, 0.6);
  pointer-events: none;
}

/* ---------- vision + ai ---------- */

.vision__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  /* Общие строки для обеих карточек: tag, h3, абзац, три пункта списка. */
  grid-template-rows: auto auto 1fr auto auto auto;
  gap: 1.2rem;
}

.vision__grid article {
  display: grid;
  grid-template-rows: subgrid;
  grid-row: span 6;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: clamp(1.5rem, 2.8vw, 2.2rem);
  background: var(--card);
}

/* Розовый на сайте значит «наше», но рядом с белой карточкой он читается как
   предупреждение — и колонки меняются местами по смыслу. Поэтому полярность
   держится не цветом, а знаком: слева гашеная карточка с крестами, справа
   живая с галочками. */
.vision__grid .vision__now {
  background: var(--paper-2);
}

.vision__now h3 {
  color: var(--ink-soft);
}

.vision__next {
  background: linear-gradient(
    165deg,
    rgba(255, 80, 125, 0.1),
    rgba(255, 80, 125, 0.02)
  ) !important;
  border-color: rgba(255, 80, 125, 0.38) !important;
}

.vision__grid .tag {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  font-family: var(--mono);
  font-size: 0.68rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 0.9rem;
}

.vision__next .tag {
  color: var(--rose-deep);
}

.tag__mark {
  display: grid;
  place-items: center;
  width: 1.3rem;
  height: 1.3rem;
  flex: none;
  border-radius: 50%;
}

.tag__mark::before {
  content: "";
  width: 0.68rem;
  height: 0.68rem;
}

.vision__now .tag__mark {
  background: rgba(20, 20, 20, 0.09);
}

.vision__now .tag__mark::before {
  background: #605d5a;
  -webkit-mask: var(--cross) center / contain no-repeat;
  mask: var(--cross) center / contain no-repeat;
}

.vision__next .tag__mark {
  background: var(--rose);
}

.vision__next .tag__mark::before {
  background: #fff;
  -webkit-mask: var(--tick) center / contain no-repeat;
  mask: var(--tick) center / contain no-repeat;
}

.vision__grid h3 {
  margin-bottom: 0.8rem;
}

.vision__grid p {
  color: var(--muted);
}

.vision__grid ul {
  display: contents;
  list-style: none;
  margin: 0;
  padding: 0;
}

.vision__grid li {
  padding: 0.62rem 0 0.62rem 1.45rem;
  border-top: 1px solid var(--line-soft);
  position: relative;
  font-size: 0.97rem;
}

.vision__grid li:first-child {
  margin-top: 1.4rem;
}

.vision__grid li::before {
  position: absolute;
  left: 0;
  top: 0.62rem;
  font-family: var(--mono);
}

.vision__now li::before,
.vision__next li::before {
  content: "";
  top: 0.98rem;
  width: 0.82rem;
  height: 0.82rem;
}

.vision__now li::before {
  background: #8c8884;
  -webkit-mask: var(--cross) center / contain no-repeat;
  mask: var(--cross) center / contain no-repeat;
}

.vision__next li::before {
  background: var(--rose-deep);
  -webkit-mask: var(--tick) center / contain no-repeat;
  mask: var(--tick) center / contain no-repeat;
}

.ai {
  display: grid;
  grid-template-columns: 1.05fr 0.95fr;
  gap: clamp(1.6rem, 4vw, 3.4rem);
  margin-top: 1.2rem;
  padding: clamp(1.7rem, 3.4vw, 3rem);
  border-radius: var(--r-lg);
  background: var(--night);
  color: #fff;
}

.ai h3 {
  color: #fff;
  margin: 1rem 0 0.9rem;
  font-size: clamp(1.5rem, 2.6vw, 2.05rem);
}

.ai .label {
  color: var(--rose);
}

.ai__copy p {
  color: var(--night-muted);
}

.ai__punch {
  margin-top: 1.6rem !important;
  color: #fff !important;
  font-size: clamp(1.2rem, 2vw, 1.55rem);
  font-weight: 800;
  letter-spacing: -0.025em;
  line-height: 1.15;
}

.ai__limits {
  list-style: none;
  margin: 0;
  padding: 0;
  align-self: center;
}

.ai__limits li {
  padding: 1rem 0;
  border-top: 1px solid var(--night-line);
  color: var(--night-muted);
  font-size: 0.95rem;
}

.ai__limits li:first-child {
  border-top: 0;
  padding-top: 0;
}

.ai__limits b {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  color: #fff;
  font-weight: 700;
  font-size: 1rem;
  letter-spacing: -0.012em;
  margin-bottom: 0.35rem;
}

.ai__limits .ico {
  font-size: 1.35rem;
  color: var(--rose);
}

/* ---------- manifest ---------- */

.manifest {
  background: var(--paper-2);
  border-block: 1px solid var(--line-soft);
}

.manifest__grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  overflow: hidden;
}

.manifest__grid article {
  background: var(--paper-2);
  padding: clamp(1.5rem, 2.4vw, 2.2rem);
}

.manifest__grid .tile {
  margin-bottom: 1.3rem;
}

.manifest__grid h3 {
  margin: 0 0 0.7rem;
}

.manifest__grid p {
  color: var(--muted);
  font-size: 0.97rem;
}

/* ---------- hardware (dark) ---------- */

.dark {
  background: var(--night);
  color: #fff;
}

.dark h2,
.dark h3,
.dark h4 {
  color: #fff;
}

.dark .label {
  color: var(--rose);
}

.dark .head p,
.dark .muted {
  color: var(--night-muted);
}

.hw__top {
  display: grid;
  grid-template-columns: 1.08fr 0.92fr;
  gap: clamp(1.6rem, 4vw, 4rem);
  align-items: end;
}

.hw__claim {
  font-size: clamp(1.05rem, 1.5vw, 1.24rem);
  color: var(--night-muted);
}

.hw__claim b {
  color: #fff;
  font-weight: 700;
}

.compare {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.1rem;
  margin-top: clamp(2.2rem, 4.5vw, 3.6rem);
}

.compare > div {
  border: 1px solid var(--night-line);
  border-radius: var(--r-lg);
  padding: clamp(1.4rem, 2.4vw, 2.1rem);
}

.compare .usual {
  background: rgba(255, 255, 255, 0.03);
}

.compare .ours {
  background: linear-gradient(
    165deg,
    rgba(255, 80, 125, 0.16),
    rgba(255, 80, 125, 0.03)
  );
  border-color: rgba(255, 80, 125, 0.42);
}

.compare h3 {
  font-size: 1.22rem;
  letter-spacing: -0.015em;
}

.compare .tag {
  font-family: var(--mono);
  font-size: 0.68rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--night-muted);
  display: block;
  margin-bottom: 0.9rem;
}

.compare .ours .tag {
  color: var(--rose);
}

.compare ul {
  list-style: none;
  margin: 1.3rem 0 0;
  padding: 0;
}

.compare li {
  padding: 0.72rem 0 0.72rem 1.5rem;
  border-top: 1px solid var(--night-line);
  position: relative;
  color: var(--night-muted);
  font-size: 0.97rem;
}

.compare li::before {
  position: absolute;
  left: 0;
  top: 0.72rem;
  font-family: var(--mono);
  font-size: 0.95rem;
}

.compare .usual li::before {
  content: "—";
  color: #6a6866;
}

.compare .ours li::before {
  content: "+";
  color: var(--rose);
}

.compare .ours li {
  color: #e9e6e4;
}

.specs {
  display: grid;
  grid-template-columns: 1.1fr 0.9fr;
  gap: clamp(1.6rem, 4vw, 3.4rem);
  margin-top: clamp(2.4rem, 5vw, 4rem);
  align-items: start;
}

.spec-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.98rem;
}

.spec-table th,
.spec-table td {
  text-align: left;
  padding: 0.9rem 0;
  border-top: 1px solid var(--night-line);
  vertical-align: top;
}

.spec-table th {
  width: 42%;
  font-weight: 500;
  font-family: var(--mono);
  font-size: 0.78rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--night-muted);
  padding-right: 1rem;
}

.spec-table td {
  color: #fff;
  font-weight: 600;
}

.price-card {
  border: 1px solid var(--night-line);
  border-radius: var(--r-lg);
  padding: clamp(1.5rem, 2.5vw, 2.1rem);
  background: rgba(255, 255, 255, 0.03);
}

.price-card .amount {
  font-size: clamp(2rem, 3.4vw, 2.7rem);
  font-weight: 800;
  letter-spacing: -0.03em;
  margin: 0.9rem 0 0.4rem;
}

.price-card .btn {
  margin-top: 1.5rem;
  width: 100%;
}

.price-card p {
  color: var(--night-muted);
  font-size: 0.92rem;
}

/* ---------- niche + pros ---------- */

.niche__grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  overflow: hidden;
}

.niche__grid article {
  background: var(--card);
  padding: clamp(1.4rem, 2.3vw, 2rem);
}

.niche__grid .n {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  color: var(--rose-deep);
}

.niche__grid h3 {
  margin: 1.4rem 0 0.6rem;
}

.niche__grid p {
  color: var(--muted);
  font-size: 0.95rem;
}

.pros {
  /* Блок лежит внутри секции, своих верхних отступов у него нет — без этого
     переход по ссылке из меню упирает бейдж прямо в липкую шапку. */
  scroll-margin-top: 5.4rem;
  display: grid;
  grid-template-columns: 1.05fr 0.95fr;
  gap: clamp(1.4rem, 3.5vw, 3.2rem);
  align-items: start;
  margin-top: 1.2rem;
  padding: clamp(1.7rem, 3.4vw, 3rem);
  border-radius: var(--r-lg);
  /* The one saturated band on the page: it speaks to a different audience
     than everything around it, so it should read as an aside. */
  background: var(--rose-deep);
  color: #fff;
}

.pros h3 {
  color: #fff;
  margin: 1rem 0 0.9rem;
}

/* Не обычная мелкая подпись секции, а бейдж: этот блок обращён к другой
   аудитории, и она должна узнать себя раньше, чем начнёт читать заголовок. */
.pros__badge {
  display: inline-block;
  padding: 0.4rem 0.9rem;
  border-radius: 999px;
  background: #fff;
  color: var(--rose-deep);
  font-family: var(--mono);
  font-size: 0.82rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.pros__copy p {
  color: rgba(255, 255, 255, 0.88);
}

.pros__case {
  margin-top: 1.3rem;
  padding-left: 1.1rem;
  border-left: 2px solid rgba(255, 255, 255, 0.45);
  font-size: 0.95rem;
}

.pros__case b {
  color: #fff;
}

.pros__list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.pros__list li {
  padding: 0.75rem 0 0.75rem 1.7rem;
  border-top: 1px solid rgba(255, 255, 255, 0.24);
  position: relative;
  font-size: 0.97rem;
  color: #fff;
}

.pros__list li:first-child {
  border-top: 0;
}

.pros__list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 1.1rem;
  width: 0.85rem;
  height: 0.85rem;
  background: #fff;
  -webkit-mask: var(--tick) center / contain no-repeat;
  mask: var(--tick) center / contain no-repeat;
}

.pros > .btn {
  grid-column: 1 / -1;
  justify-self: start;
}

/* ---------- directions ---------- */

.dirs {
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--card);
  overflow: hidden;
}

.dirs__tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.15rem;
  padding-inline: clamp(0.4rem, 1.4vw, 1rem);
  border-bottom: 1px solid var(--line);
}

.dirs__tab {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}

.dirs__panel {
  display: grid;
  grid-template-columns: 1.05fr 0.95fr;
  gap: clamp(1.5rem, 3.5vw, 3.2rem);
  padding: clamp(1.6rem, 3vw, 2.6rem);
}

/* display у панели сильнее браузерного [hidden]{display:none} — закрытые
   вкладки надо гасить явно, иначе они все окажутся на экране разом. */
.dirs__panel[hidden] {
  display: none;
}

.dirs h3 {
  margin: 0 0 0.8rem;
}

.dirs p {
  color: var(--muted);
}

.dirs ul {
  list-style: none;
  margin: 0;
  padding: 0;
  align-self: center;
}

.dirs li {
  padding: 0.62rem 0 0.62rem 1.35rem;
  border-top: 1px solid var(--line-soft);
  position: relative;
  font-size: 0.98rem;
}

.dirs li:first-child {
  border-top: 0;
}

.dirs li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 1.15rem;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--rose);
}

.subhead {
  max-width: 42rem;
  margin: clamp(2.6rem, 5vw, 4.2rem) 0 clamp(1.6rem, 3vw, 2.4rem);
}

.subhead .label {
  display: block;
  margin-bottom: 0.9rem;
}

.subhead h3 {
  font-size: clamp(1.4rem, 2.4vw, 1.9rem);
}

.subhead p {
  margin-top: 0.9rem;
  color: var(--muted);
}

/* Девять плиток с крупной иконкой-квадратом и абзацем занимали ровно столько
   же, сколько три главных направления. Смысл блока — широта, её читают по
   диагонали, поэтому иконка ушла в строку заголовка, а описание — в одну. */
.scope {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  overflow: hidden;
}

.scope article {
  background: var(--card);
  padding: 1.1rem 1.3rem 1.25rem;
}

.scope h4 {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  margin-bottom: 0.35rem;
  font-size: 1rem;
}

.scope h4 .ico {
  font-size: 1.25rem;
  color: var(--rose-deep);
}

.scope p {
  margin: 0;
  color: var(--muted);
  font-size: 0.92rem;
  line-height: 1.5;
}

/* ---------- fork: diy vs turnkey ---------- */

.fork {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.2rem;
}

.fork article {
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: clamp(1.6rem, 3vw, 2.4rem);
  background: var(--card);
  display: flex;
  flex-direction: column;
}

.fork article:last-child {
  background: var(--ink);
  color: #fff;
  border-color: var(--ink);
}

.fork article:last-child h3,
.fork article:last-child .label {
  color: #fff;
}

.fork article:last-child .label {
  color: var(--rose);
}

.fork article:last-child p,
.fork article:last-child li {
  color: var(--night-muted);
}

.fork h3 {
  margin: 1rem 0 0.8rem;
}

.fork ul {
  list-style: none;
  margin: 1.3rem 0 0;
  padding: 0;
}

.fork li {
  position: relative;
  padding: 0.6rem 0 0.6rem 1.7rem;
  border-top: 1px solid var(--line-soft);
  color: var(--muted);
  font-size: 0.97rem;
}

/* Masked SVG instead of a glyph so the tick keeps the icon set's stroke
   weight and can be recoloured per card. */
.fork li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.98rem;
  width: 0.85rem;
  height: 0.85rem;
  background: var(--rose-deep);
  -webkit-mask: var(--tick) center / contain no-repeat;
  mask: var(--tick) center / contain no-repeat;
}

.fork article:last-child li {
  border-top-color: var(--night-line);
}

.fork article:last-child li::before {
  background: var(--rose);
}

.fork .btn {
  margin-top: auto;
  align-self: flex-start;
}

.fork ul + .btn {
  margin-top: 1.7rem;
}

/* ---------- partners ---------- */

.partners {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 0.75rem;
}

.partner {
  display: block;
  background: var(--card);
  border: 1px solid var(--line-soft);
  border-radius: var(--r);
  padding: 1.15rem 1.05rem;
  transition: border-color 0.16s ease, transform 0.16s ease;
}

.partner:hover {
  border-color: var(--rose);
  transform: translateY(-2px);
}

/* Two of the three marks are white-on-transparent, so every logo sits on the
   same dark tile rather than half of them vanishing on the card. */
.partner__logo {
  display: grid;
  place-items: center;
  height: 4.8rem;
  margin-bottom: 0.95rem;
  border-radius: 10px;
  background: var(--night);
}

.partner__logo img {
  width: auto;
  max-width: 72%;
  max-height: 2.8rem;
  object-fit: contain;
}

.partner__word {
  font-size: 1.35rem;
  font-weight: 800;
  letter-spacing: -0.02em;
  color: #fff;
}

.partner__word b {
  color: var(--rose);
}

.partner p {
  color: var(--muted);
  font-size: 0.82rem;
  line-height: 1.4;
  margin: 0;
}

/* ---------- faq ---------- */

.faq {
  display: flex;
  flex-direction: column;
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  overflow: hidden;
  max-width: 48rem;
}

.faq__item {
  background: var(--card);
  padding: 0;
}

.faq__item summary {
  cursor: pointer;
  list-style: none;
  padding: 1.15rem 1.4rem;
  font-weight: 600;
  font-size: 1.05rem;
}

.faq__item summary::-webkit-details-marker {
  display: none;
}

.faq__item summary::after {
  content: "+";
  float: right;
  font-family: var(--mono);
  font-weight: 500;
  color: var(--rose-deep);
}

.faq__item[open] summary::after {
  content: "−";
}

.faq__item p {
  padding: 0 1.4rem 1.25rem;
  color: var(--muted);
  margin: 0;
  max-width: 40rem;
}

/* ---------- contact ---------- */

.contact {
  background: var(--night);
  color: #fff;
}

.contact__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(2rem, 5vw, 5rem);
}

.contact h2 {
  color: #fff;
}

.contact__lead {
  color: var(--night-muted);
  margin-top: 1.2rem;
  max-width: 32rem;
}

.ways {
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
  margin-top: 2rem;
}

.ways a {
  font-size: 1.16rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  display: inline-flex;
  align-items: center;
  gap: 0.7rem;
  width: fit-content;
}

.ways a:hover {
  color: var(--rose);
}

.ways .ico {
  font-size: 1.15rem;
  color: var(--rose);
}

.ways .k {
  font-family: var(--mono);
  font-size: 0.68rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--night-muted);
  width: 5.2rem;
  flex-shrink: 0;
}

.form {
  display: grid;
  gap: 1.1rem;
  align-content: start;
}

.form label {
  display: grid;
  gap: 0.45rem;
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--night-muted);
}

.form input,
.form textarea {
  width: 100%;
  border: 1px solid var(--night-line);
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.04);
  color: #fff;
  font: inherit;
  font-size: 1rem;
  padding: 0.75rem 0.9rem;
  outline: none;
  transition: border-color 0.16s ease;
}

.form textarea {
  resize: vertical;
}

.form input:focus,
.form textarea:focus {
  border-color: var(--rose);
}

.form .btn {
  margin-top: 0.3rem;
}

.form__note {
  color: var(--night-muted);
  font-size: 0.85rem;
}

.form__ok {
  color: #7ee0a8;
  font-size: 0.92rem;
}

.form__err {
  color: #ff8fa8;
  font-size: 0.92rem;
}

.form__err a {
  color: inherit;
  text-decoration: underline;
}

/* Ловушка для ботов. Не display:none: часть скриптов такие поля пропускает,
   а это поле должно выглядеть для них обычным. */
.form__trap {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* ---------- footer ---------- */

.footer {
  background: var(--night);
  border-top: 1px solid var(--night-line);
  color: var(--night-muted);
  padding-block: 2.2rem;
}

.footer__inner {
  display: flex;
  align-items: center;
  gap: 1.2rem;
  flex-wrap: wrap;
  font-size: 0.88rem;
}

.footer .brand__slogan {
  color: var(--night-muted);
  border-left-color: var(--night-line);
}

.footer nav {
  margin-left: auto;
  display: flex;
  gap: 1.3rem;
  flex-wrap: wrap;
}

.footer a:hover {
  color: #fff;
}

/* ---------- wall: switch + LCD ---------- */

#control {
  scroll-margin-top: 4.4rem;
}

.rooms {
  display: flex;
  flex-wrap: wrap;
  gap: 0.15rem;
  margin: 0 0 clamp(1.5rem, 3vw, 2.2rem);
  padding: 0;
  border-bottom: 1px solid var(--line);
}

.rooms__tab,
.dirs__tab {
  appearance: none;
  margin: 0 0 -1px;
  padding: 0.7rem 1rem;
  border: 0;
  border-bottom: 2px solid transparent;
  background: none;
  font: inherit;
  font-size: 0.95rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--muted);
  cursor: pointer;
}

.rooms__tab:hover,
.dirs__tab:hover {
  color: var(--ink);
}

.rooms__tab.is-on,
.dirs__tab.is-on {
  color: var(--ink);
  border-bottom-color: var(--rose);
}

/* Рамка вокруг вкладки выглядела наклейкой поверх макета. Подсвечиваем саму
   вкладку фоном: с клавиатуры видно, где ты, инородного контура нет. */
.rooms__tab:focus-visible,
.dirs__tab:focus-visible {
  outline: none;
  background: rgba(0, 0, 0, 0.05);
  border-radius: 8px 8px 0 0;
  color: var(--ink);
}

.wall__cap {
  margin: 0 0 1.5rem;
  color: var(--muted);
  font-size: 0.95rem;
}

/* Раньше здесь стояли три карточки .channels с одной мыслью на каждую.
   Мысль осталась, форма — обычный абзац под подписью комнаты. */
.wall__note {
  margin: 0;
  max-width: 44rem;
  color: var(--ink-soft);
}

.wall {
  --device-base: min(15.2rem, 22.5vw);
  --device: var(--device-base);
  --lcd: var(--device);
  --zoom: 1;
  --floor: 1.55rem;
  --l-main: 0;
  --l-amb: 0;
  --l-day: 0;
  --day-eff: min(1, calc(var(--l-day) * 1.8));
  --lit: max(var(--day-eff), var(--l-main), calc(var(--l-amb) * 0.35));
  position: relative;
  margin-bottom: clamp(2rem, 4vw, 3.4rem);
  /* Скрытые наборы остаются в потоке (равная высота комнат). Сауна — три
     поста, и на узком экране 3 × 40vw вылезает за край: iOS раздвигает
     вьюпорт и оставляет полосу справа на всей странице. */
  overflow-x: hidden;
  overflow-x: clip;
  /* Клавиши жмут часто — выделение «Свет» и «+/−» мешает и выглядит поломкой. */
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent;
}

html.js .wall {
  --zoom: 0;
}

/* Стена показана в лоб, без откоса сбоку: глубину дают окно, мебель и свет, а
   угол только отъедал ширину у самой комнаты. На узких экранах его и так
   приходилось прятать. */
.wall__scene {
  display: grid;
  min-height: min(78vh, 45rem);
}

.wall__face {
  position: relative;
  display: flex;
  flex-direction: column;
  background-color: #e8e0d2;
  background-image: radial-gradient(
      120% 70% at 16% 8%,
      rgba(255, 252, 246, 0.55),
      transparent 58%
    ),
    radial-gradient(90% 60% at 92% 78%, rgba(70, 50, 30, 0.06), transparent 55%);
}

.wall__face::before {
  content: "";
  position: absolute;
  inset: 0;
  background: url("../img/plaster-noise.png");
  opacity: 0.055;
  mix-blend-mode: multiply;
  pointer-events: none;
}

/* Наборы всех комнат лежат в одной клетке сетки друг на друге, а не сменяют
   друг друга в потоке. Высота области от этого — максимум по всем комнатам и
   не зависит от того, какая комната открыта.

   Так сделано ради одинаковой высоты сцены. .wall__scene задаёт только
   min-height, а дальше его распирает содержимое, и комната с крупной панелью
   вырастала выше остальных: гараж на узком экране ставит щит под планшет
   (см. медиазапрос 820px), от чего он становится вдвое выше детской — вкладки
   переключались с прыжком страницы. Резервировать максимум приходится
   заранее, потому что в потоке видна всегда одна комната и знать про соседей
   она не может. */
.wall__devices {
  flex: 1;
  display: grid;
  grid-template-areas: "stack";
  /* Колонка на всю ширину: у наборов width: 100%, и в auto-колонке процент
     считался бы от их же max-content — то есть сам от себя. */
  grid-template-columns: minmax(0, 1fr);
  align-items: center;
  justify-content: center;
  gap: clamp(1.4rem, 3vw, 2.6rem);
  /* Отступы одни на все комнаты и комнатами не переопределяются: клетка уже
     держит максимум по наборам, и если сверху добавить свой padding, высота
     снова разъедется — ровно на разницу отступов. Кому нужна панель выше,
     правит выравнивание набора в клетке (align-items), а не размер коробки. */
  padding: 2.5rem var(--pad) 2.2rem;
  position: relative;
  z-index: 3;
}

html.js .wall__devices {
  transform: translate3d(0, calc((1 - var(--zoom)) * 2rem), 0)
    scale(calc(0.4 + 0.6 * var(--zoom)));
  transform-origin: center 48%;
  will-change: transform;
}

/* iOS не рисует <video> и не обновляет canvas внутри предка с transform —
   даже когда это translate3d(0,0,0) scale(1). Как только зум дошёл до 1,
   слой снимаем. На телефоне зум и не нужен: панель и так во весь кадр. */
html.js .wall.is-still .wall__devices {
  transform: none;
  will-change: auto;
}

@media (pointer: coarse) {
  html.js .wall {
    --zoom: 1;
  }

  html.js .wall__devices {
    transform: none;
    will-change: auto;
  }
}

/* Рамка, поле экрана и скругление заданы долей корпуса, а не пикселями. При
   жёстких 9px рамка на телефоне съедала десятую часть модуля вместо трёх
   сотых, и корпус выглядел разбухшим, хотя менялся только его размер. Нижняя
   граница нужна, чтобы на самых мелких корпусах рамка не выродилась в волосок,
   верхняя — чтобы на большом экране всё осталось как было. Живёт на .wall__set,
   потому что гараж переопределяет --device именно здесь. */
.wall__set {
  --frame: clamp(4px, calc(var(--device) * 0.037), 9px);
  --bezel: clamp(6px, calc(var(--device) * 0.053), 13px);
  --radius: clamp(7px, calc(var(--device) * 0.049), 12px);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: inherit;
  width: 100%;
  grid-area: stack;
}

.wall__set[hidden] {
  /* display: none убрал бы набор из потока, и высота области снова поехала бы
     за открытой комнатой. Скрываем видимостью: из дерева доступности и из-под
     курсора набор уходит так же, но место за собой держит. */
  visibility: hidden;
  pointer-events: none;
}

/* Набор остался в потоке — значит и его анимации никуда не делись. Раньше их
   снимал display: none, теперь вентилятор на экране гостиной крутился бы и
   пока открыт гараж, а точка записи в гараже мигала бы из гостиной. Селектор
   ищет только внутри скрытых наборов, поэтому звёздочка здесь дешёвая. */
.wall__set[hidden] *,
.wall__set[hidden] *::before,
.wall__set[hidden] *::after {
  animation-play-state: paused;
}

.wall__skirting {
  height: 3.1rem;
  flex: none;
  background: linear-gradient(#f4efe6, #ece6db);
  border-top: 1px solid rgba(255, 255, 255, 0.55);
  box-shadow: 0 -1px 0 rgba(40, 30, 20, 0.12);
  position: relative;
  z-index: 1;
}

.wall__skirting::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 7px;
  background: #d8d0c4;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.35);
}

.wall__floor {
  height: var(--floor);
  background:
    repeating-linear-gradient(
      90deg,
      #c4ae8d 0 11px,
      #bba582 11px 12px,
      #cbb896 12px 23px,
      #bba582 23px 24px
    );
  box-shadow: inset 0 8px 12px rgba(40, 24, 8, 0.18);
}

/* Обстановка комнат — предметы, слои света и тон стен — вынесена в
   assets/css/rooms.css. Здесь остаётся сама стена: откос, лицо, плинтус, пол
   и раскладка устройств на них. */

/* Рамка собирается под задачу: --posts — посты по ширине, --tall — множитель
   высоты. Один класс покрывает и 1 пост в парной, и высокий корпус спальни. */
.gang {
  --posts: 2;
  --tall: 1;
  position: relative;
  display: grid;
  grid-template-columns: repeat(var(--posts), 1fr);
  grid-template-rows: 1fr;
  gap: var(--frame);
  width: calc(var(--device) * var(--posts));
  height: calc(var(--device) * var(--tall));
  flex: none;
  padding: var(--frame);
  border-radius: var(--radius);
  background: linear-gradient(145deg, #323236 0%, #1c1c20 42%, #121214 100%);
  box-shadow:
    inset 1.5px 1.5px 0 rgba(255, 255, 255, 0.32),
    inset -1px -1px 0 rgba(0, 0, 0, 0.55),
    0 2px 2px rgba(0, 0, 0, 0.28),
    0 12px 22px rgba(0, 0, 0, 0.2),
    0 22px 36px rgba(20, 12, 8, 0.18);
}

.gang--1 {
  --posts: 1;
}

/* Рамка, где каждый пост сам по себе: плита не растягивается на всю ширину, а
   занимает свою колонку. В других комнатах плита одна и потому тянется через
   все посты (grid-column: 1 / -1) — здесь постов четыре, и каждому нужен свой. */
.gang--row {
  --posts: 4;
}

.gang--row .gang__plate {
  grid-column: auto;
}

/* LCD — квадрат, равный одному модулю. */
.lcd {
  width: var(--device);
  height: var(--device);
  flex: none;
  border-radius: var(--radius);
}

/* Вертикальный комбо-модуль спальни: термостат и клавиши в одном корпусе.
   Рамка та же, что у .gang, но в один пост шириной и в два высотой — экран
   и плита выходят квадратными, как розетка рядом. */
.combo {
  position: relative;
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: 1fr 1fr;
  gap: var(--frame);
  width: var(--device);
  height: calc(var(--device) * 2 - var(--frame));
  flex: none;
  padding: var(--frame);
  border-radius: var(--radius);
  background: linear-gradient(145deg, #323236 0%, #1c1c20 42%, #121214 100%);
  box-shadow:
    inset 1.5px 1.5px 0 rgba(255, 255, 255, 0.32),
    inset -1px -1px 0 rgba(0, 0, 0, 0.55),
    0 2px 2px rgba(0, 0, 0, 0.28),
    0 12px 22px rgba(0, 0, 0, 0.2),
    0 22px 36px rgba(20, 12, 8, 0.18);
}

/* Плита по умолчанию занимает всю рамку; при наличии розетки — все посты,
   кроме последнего (розетка всегда последний пост). */
.gang__plate {
  grid-column: 1 / -1;
}

.gang--outlet .gang__plate {
  grid-column: 1 / -2;
}

/* Экран может стоять в том же посту, где в других комнатах розетка: корпус
   один, и оба поста собраны одинаково — то же поле, то же скругление, та же
   посадка вглубь. Иначе два соседних поста выглядят из разных приборов. */
.gang--screen .gang__plate {
  grid-column: 1 / -2;
}


/* Сауна: три поста в один ряд — свет, экран сауны, экран вентиляции. Посты
   равны модулю, и оба экрана выходят того же размера, что LCD в гостиной.
   Базовое правило .gang--screen отдаёт плите всё, кроме последнего поста, —
   здесь плите нужен только первый, остальные два занимают экраны. */
.gang--sauna {
  --posts: 3;
}

/* Три поста должны влезать в набор, а не в вьюпорт: на телефоне шаг
   поднимают до 40vw ради гостиной в столбик, и 3 × 40vw уже шире экрана.

   НЕЛЬЗЯ писать --device: min(…, calc(100% / 3)) на наборе: кастомные
   свойства подставляются буквально, и когда потомок использует --device
   в font-size, «100%» означает «процент от шрифта родителя» (~16 px),
   а не от ширины контейнера — текст на карточках становится ≈ 0.3 px.

   Вместо % используем cqw: эта единица всегда отсылает к ширине
   контейнера-предка, в каком бы свойстве ни стояла. */
.wall__set[data-room="sauna"] {
  container-type: inline-size;
}

.gang--sauna {
  --device: min(var(--device-base), calc(100cqw / 3));
}

.gang--sauna .gang__plate {
  grid-column: 1 / 2;
}

/* У экранов в корпусе нет обечайки .lcd__bezel — её роль играет рамка, — так
   что поле стеклу сауны нужно своё, как климату в детской. Вентиляции поле не
   нужно: её плейсхолдер рисует картинку от края до края. */
.gang__screen.sauna {
  padding: 5%;
}

.gang__screen {
  position: relative;
  min-width: 0;
  min-height: 0;
  border-radius: 6px;
  overflow: hidden;
  box-shadow:
    inset 0 2px 4px rgba(0, 0, 0, 0.7),
    inset 0 -1px 0 rgba(255, 255, 255, 0.04);
}

/* Здесь у экрана нет обечайки .lcd__bezel — её роль играет рамка корпуса, —
   поэтому стекло занимает пост целиком, и поле термостату нужно своё. На
   прежних 4% кольцо упиралось в кромку, а −/+ садились на нижний край. */
.gang__screen.air {
  padding: 7%;
}

.gang::before,
.combo::before,
.lcd__bezel::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  border-radius: inherit;
  padding: inherit;
  pointer-events: none;
  background: linear-gradient(
    145deg,
    rgba(255, 255, 255, 0.42) 0%,
    rgba(255, 255, 255, 0.12) 26%,
    rgba(255, 255, 255, 0) 52%
  );
  -webkit-mask:
    linear-gradient(#000 0 0) content-box,
    linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask:
    linear-gradient(#000 0 0) content-box,
    linear-gradient(#000 0 0);
  mask-composite: exclude;
}

.gang__plate {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
  gap: 2px;
  min-width: 0;
  min-height: 0;
  padding: 3px;
  background: #070708;
  border-radius: 6px;
  overflow: hidden;
  box-shadow:
    inset 0 2px 4px rgba(0, 0, 0, 0.7),
    inset 0 -1px 0 rgba(255, 255, 255, 0.04);
}

/* --- Раскладки плиты --- */

/* 8 клавиш 2×4. Зазор один и тот же по обеим осям и по краю плиты — иначе
   сетка читается перекошенной. От --device, чтобы держаться при --fit. */
.gang__plate--2x4 {
  --gap: calc(var(--device) * 0.016);
  grid-template-columns: 1fr 1fr;
  grid-template-rows: repeat(4, 1fr);
  gap: var(--gap);
  padding: var(--gap);
}

.gang__plate--2x4 .gang__key {
  border-radius: 4px;
  padding: 2% 4%;
  justify-content: center;
  gap: 0.2em;
  font-size: calc(var(--device) * 0.04);
}

/* em от уменьшенной подписи, поэтому множитель больше: иконка сохраняет размер. */
.gang__plate--2x4 .gang__ico {
  width: 1.5em;
  height: 1.5em;
}

/* Подпись в восемь клавиш может занимать две строки. */
.gang__plate--2x4 .gang__key span {
  white-space: normal;
  line-height: 1.04;
  text-align: center;
}

/* 4 клавиши 2×2. Зазор считается тем же способом, что в 2×4: одно число идёт
   и на щели между клавишами, и на поле по краю плиты. На волосяной линии в 1px
   поле плиты (3px из .gang__plate) выходило втрое шире щели, и сетка читалась
   перекошенной, хотя клавиши были ровные. */
.gang__plate--2x2 {
  --gap: calc(var(--device) * 0.016);
  gap: var(--gap);
  padding: var(--gap);
}

.gang__plate--2x2 .gang__key {
  justify-content: center;
  gap: 0.34em;
  border-radius: 4px;
  font-size: calc(var(--device) * 0.058);
}

.gang__plate--2x2 .gang__leds {
  display: none;
}

/* em считается от уменьшенной подписи, поэтому множитель больше базового. */
.gang__plate--2x2 .gang__ico {
  width: 1.7em;
  height: 1.7em;
}

/* Одна клавиша на пост. Зазор берётся тем же способом и тем же числом, что в
   2×2 у детской: одно значение от --device идёт и на щели, и на поле плиты.
   Раньше гараж собирался на --split с жёстким gap: 1px при унаследованном
   padding: 3px — поле выходило втрое шире щели, ровно та перекошенность, от
   которой 2×2 уже избавились. */
.gang__plate--one {
  --gap: calc(var(--device) * 0.016);
  grid-template-columns: 1fr;
  grid-template-rows: 1fr;
  gap: var(--gap);
  padding: var(--gap);
}

.gang__plate--one .gang__key {
  justify-content: center;
  gap: 0.34em;
  border-radius: 4px;
  font-size: calc(var(--device) * 0.058);
}

.gang__plate--one .gang__ico {
  width: 1.7em;
  height: 1.7em;
}

/* «Ворота въезд» и «Ворота гараж» — по два слова, в одну строку не встают. */
.gang__plate--one .gang__key span {
  white-space: normal;
  line-height: 1.06;
  text-align: center;
}

/* Живёт в этой секции, а не рядом с корпусом сауны: у .gang__plate--stack3 и
   базового .gang__plate одинаковая специфичность, и решает порядок в файле.
   Стоя выше базы, вариант терял grid-template-columns — клавиши оставались в
   два столбца и занимали половину поста, а вторая половина чернела. */
/* Три клавиши в столбик: пост узкий, и рядом друг с другом они выходят
   полосками, в которые не встаёт «Подсветка». Зазор считается тем же способом
   и тем же числом, что в 2×2 у детской. */
.gang__plate--stack3 {
  --gap: calc(var(--device) * 0.016);
  grid-template-columns: 1fr;
  /* minmax(0, 1fr), а не 1fr: обычный 1fr не даёт ряду сжаться ниже
     min-content клавиши, и три ряда по 69 px не влезали в плиту на 172 px —
     третья клавиша уезжала за рамку корпуса. */
  grid-template-rows: repeat(3, minmax(0, 1fr));
  gap: var(--gap);
  padding: var(--gap);
}

/* Поля и кегль мельче, чем в двухклавишном столбике: на трёх клавишах в посту
   на каждую остаётся около 55 px, и прежние 12% поля съедали их целиком. */
.gang__plate--stack3 .gang__key {
  justify-content: center;
  gap: 0.3em;
  min-height: 0;
  padding: 6% 8%;
  border-radius: 4px;
  font-size: calc(var(--device) * 0.048);
}

.gang__plate--stack3 .gang__ico {
  width: 1.4em;
  height: 1.4em;
}

.gang__plate--stack3 .gang__key span {
  white-space: normal;
  line-height: 1.06;
  text-align: center;
}

/* Широкие клавиши-строки: жмут левый и правый край, середина — подпись. */
.gang__plate--rows {
  grid-template-columns: 1fr;
  grid-template-rows: repeat(4, 1fr);
  gap: 1px;
  overflow: hidden;
  perspective: calc(var(--device) * 2.2);
  perspective-origin: 50% 50%;
}

.gang__row {
  display: grid;
  grid-template-columns: 0.9fr minmax(0, 2.2fr) 0.9fr;
  align-items: stretch;
  min-width: 0;
  min-height: 0;
  color: #5a5e66;
  background: linear-gradient(180deg, #2e2e32 0%, #1c1c20 52%, #161618 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.14),
    inset 0 -1px 0 rgba(0, 0, 0, 0.45);
  font-family: var(--sans);
  font-size: calc(var(--device) * 0.042);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  transform-origin: 50% 50%;
  transition:
    transform 0.09s ease,
    box-shadow 0.09s ease,
    filter 0.09s ease;
}

.gang__row:first-child {
  border-radius: 3px 3px 0 0;
}

.gang__row:last-child {
  border-radius: 0 0 3px 3px;
}

/* Качалка поворачивается вокруг вертикальной оси, а не кренится в плоскости
   стены: нажатый край уходит вглубь, противоположный чуть выступает, а
   горизонтальные грани остаются горизонтальными. Точка схода общая на всю
   плиту — раньше perspective вешали на строку, у каждой получался свой
   ракурс, и планка «подпрыгивала».

   На реальном масштабе (строка ~150 px) поворот даёт меньше пикселя, поэтому
   нажатие читается светом: блик верхней грани гаснет со стороны нажатия,
   туда же наваливается тень, а содержимое этого края уходит на волосок
   внутрь. Ровно так ведёт себя настоящая клавиша с ходом 1,5 мм. */
.gang__row:has(.gang__side:first-child:active) {
  transform: rotateY(-5deg);
}

.gang__row:has(.gang__side:last-child:active) {
  transform: rotateY(5deg);
}

.gang__row:has(.gang__side:first-child:active),
.gang__row:has(.gang__side:last-child:active) {
  background:
    /* Блик верхней грани уходит от нажатого края к дальнему и там ярче
       обычного: этот край выступает из плиты и ловит больше света. */
    linear-gradient(var(--press-dir), transparent 42%, rgba(255, 255, 255, 0.22) 100%)
      0 0 / 100% 1px no-repeat,
    /* Тень тугая и короткая — за середину планки она не заходит, иначе
       читается, что вдавили всю клавишу, а не её край. */
    linear-gradient(var(--press-dir), rgba(0, 0, 0, 0.62) 0%, transparent 46%),
    linear-gradient(180deg, #2e2e32 0%, #1c1c20 52%, #161618 100%);
  box-shadow:
    inset 0 -1px 0 rgba(0, 0, 0, 0.5),
    var(--press-cut);
}

.gang__row:has(.gang__side:first-child:active) {
  --press-dir: 90deg;
  --press-cut: inset calc(var(--device) * 0.017) 0 calc(var(--device) * 0.021)
    calc(var(--device) * -0.01) rgba(0, 0, 0, 0.95);
}

.gang__row:has(.gang__side:last-child:active) {
  --press-dir: 270deg;
  --press-cut: inset calc(var(--device) * -0.017) 0 calc(var(--device) * 0.021)
    calc(var(--device) * -0.01) rgba(0, 0, 0, 0.95);
}

/* Значок уходит вместе с краем — без этого клавиша выглядит так, будто под
   стеклом просто сменилась картинка. Смещение горизонтальное, к оси поворота:
   край отъезжает вглубь, а не вниз, и в проекции это читается как сдвиг
   внутрь клавиши. */
.gang__row:has(.gang__side:first-child:active) .gang__side:first-child {
  transform: translateX(calc(var(--device) * 0.006));
  opacity: 0.7;
}

.gang__row:has(.gang__side:last-child:active) .gang__side:last-child {
  transform: translateX(calc(var(--device) * -0.006));
  opacity: 0.7;
}

/* Нажали середину — планка уходит вглубь целиком, без перекоса. */
.gang__row:has(.gang__mid:active) {
  background: linear-gradient(180deg, #1a1a1d 0%, #121214 100%);
  box-shadow:
    inset 0 8px 14px rgba(0, 0, 0, 0.5),
    inset 0 -1px 0 rgba(255, 255, 255, 0.04);
}

.gang__side,
.gang__mid {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.35em;
  min-width: 0;
  transition:
    transform 0.09s ease,
    opacity 0.09s ease;
}

.gang__side,
.gang__mid {
  appearance: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  cursor: pointer;
  touch-action: manipulation;
}

.gang__side {
  font-size: calc(var(--device) * 0.11);
  font-weight: 500;
  letter-spacing: 0;
  line-height: 1;
}

.gang__side .gang__ico {
  width: 0.72em;
  height: 0.72em;
}

.gang__mid span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.gang__row .gang__ico {
  width: 1.45em;
  height: 1.45em;
  flex: none;
}

.gang__row .gang__side .gang__ico {
  width: calc(var(--device) * 0.12);
  height: calc(var(--device) * 0.12);
  stroke-width: 2.4;
}

/* Ховер не подсвечиваем: клавиша-качалка должна отзываться только на нажатие,
   иначе при простом проведении мышью ряд «вспыхивает» без причины. */
.gang__side:hover,
.gang__mid:hover,
.gang__row.is-on .gang__side:hover,
.gang__row.is-on .gang__mid:hover {
  color: inherit;
}

.gang__side:focus-visible,
.gang__mid:focus-visible {
  outline: 1.5px solid rgba(220, 230, 255, 0.6);
  outline-offset: -3px;
}

.gang__row.is-on {
  color: #f7f8fa;
  text-shadow: 0 0 8px rgba(220, 230, 255, 0.9);
}

.gang__row.is-on .gang__mid .gang__ico {
  filter: drop-shadow(0 0 4px rgba(220, 230, 255, 0.95));
}

/* 3 вертикальные колонки: точки у верхнего края, подпись у нижнего. */
.gang__plate--cols {
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: 1fr;
  gap: 1px;
}

.gang__plate--cols .gang__key {
  justify-content: space-between;
  padding: 14% 6% 13%;
  border-radius: 3px;
}

.gang__plate--cols .gang__ico {
  display: none;
}

/* Асимметрия: высокая клавиша слева, две обычные справа. */
.gang__plate--split {
  grid-template-columns: 1.08fr 1fr;
  grid-template-rows: 1fr 1fr;
  gap: 1px;
}

.gang__plate--split .gang__key {
  justify-content: center;
  border-radius: 3px;
  font-size: calc(var(--device) * 0.058);
  letter-spacing: 0.08em;
}

.gang__plate--split .gang__key--tall {
  grid-row: 1 / -1;
  justify-content: center;
  gap: 0.9em;
}

.gang__plate--split .gang__key--tall .gang__ico {
  width: 2.3em;
  height: 2.3em;
}

.outlet {
  display: grid;
  place-items: center;
  min-width: 0;
  min-height: 0;
  border-radius: 6px;
  background: linear-gradient(180deg, #2e2e32 0%, #1c1c20 52%, #161618 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.16),
    inset 0 -1px 0 rgba(0, 0, 0, 0.45),
    inset 0 2px 5px rgba(0, 0, 0, 0.28);
}

.outlet__face {
  position: relative;
  width: 54%;
  aspect-ratio: 1;
  border-radius: 50%;
  background: linear-gradient(180deg, #2a2a2e 0%, #121214 100%);
  box-shadow:
    inset 0 3px 8px rgba(0, 0, 0, 0.55),
    inset 0 1px 0 rgba(255, 255, 255, 0.08),
    0 1px 0 rgba(255, 255, 255, 0.05);
}

.outlet__pin {
  position: absolute;
  top: 50%;
  width: 13.5%;
  height: 13.5%;
  border-radius: 50%;
  background: #070709;
  box-shadow:
    inset 0 1px 2px rgba(0, 0, 0, 0.9),
    0 0 0 1px rgba(255, 255, 255, 0.04);
  transform: translate(-50%, -50%);
}

.outlet__pin--l {
  left: 32%;
}

.outlet__pin--r {
  left: 68%;
}

.outlet__earth {
  position: absolute;
  left: 50%;
  width: 20%;
  height: 4.5%;
  border-radius: 2px;
  background: linear-gradient(
    180deg,
    #7a5a22 0%,
    #c4a04a 28%,
    #f3e0a0 48%,
    #d4b05c 62%,
    #8a6428 100%
  );
  transform: translateX(-50%);
  box-shadow:
    0 1px 1px rgba(0, 0, 0, 0.45),
    inset 0 1px 0 rgba(255, 240, 200, 0.55);
}

.outlet__earth--l {
  top: 8%;
}

.outlet__earth--r {
  top: auto;
  bottom: 8%;
}

/* ---------- силовой разъём под машину ---------- */

/* Пост под зарядку собран не как Schuko: круглый синий корпус CEE, три штыря
   по окружности и паз-ключ сверху. Рядом с бытовой розеткой разница читается
   с одного взгляда — иначе два соседних поста выглядели бы одинаково, а
   отличались бы только подписью. */
/* Ничего не переопределяет: разъём центрируется тем же place-items, что лицо
   Schuko в соседнем посту, и потому садится на ту же ось. Подпись в этом не
   участвует — она выведена из потока, см. .outlet__tag. */
.outlet--cee {
  position: relative;
}

.cee {
  position: relative;
  /* Ровно как .outlet__face: приборы в соседних постах должны быть одного
     размера. Раньше здесь стояло 62% при padding на самом посту — от узкого
     бокса это давало те же 86 px, что 54% от полного, но совпадение держалось
     на двух числах, подогнанных друг под друга. */
  width: 54%;
  aspect-ratio: 1;
  border-radius: 50%;
  background:
    radial-gradient(circle at 36% 28%, #4c82cc 0%, #2f5f9e 44%, #1b3c69 100%);
  box-shadow:
    inset 0 2px 6px rgba(0, 0, 0, 0.45),
    inset 0 1px 0 rgba(255, 255, 255, 0.22),
    0 1px 0 rgba(255, 255, 255, 0.06);
}

/* Утопленное поле, в котором сидят штыри: без него они лежат прямо на корпусе
   и разъём читается плоской наклейкой. */
.cee::after {
  content: "";
  position: absolute;
  inset: 17%;
  border-radius: 50%;
  background: linear-gradient(180deg, #16305a 0%, #0d1e3b 100%);
  box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.6);
}

/* Паз-ключ: по нему вилка садится единственным правильным поворотом. */
.cee__key {
  position: absolute;
  top: 4%;
  left: 50%;
  width: 20%;
  height: 15%;
  border-radius: 0 0 40% 40%;
  background: linear-gradient(180deg, #16305a 0%, #0e2244 100%);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.55);
  transform: translateX(-50%);
}

.cee__pin {
  position: absolute;
  width: 15%;
  height: 15%;
  border-radius: 50%;
  background: #060a12;
  box-shadow:
    inset 0 1px 2px rgba(0, 0, 0, 0.9),
    0 0 0 1px rgba(150, 190, 240, 0.16);
  /* Над утопленным полем: .cee::after идёт после штырей в порядке отрисовки. */
  z-index: 1;
}

/* Земля сверху, фаза и ноль — снизу по бокам: та же посадка, что у настоящего
   16-амперного CEE. */
.cee__pin--e {
  top: 27%;
  left: 42.5%;
}

.cee__pin--l {
  bottom: 27%;
  left: 26%;
}

.cee__pin--n {
  bottom: 27%;
  right: 26%;
}

/* Подпись лежит поверх поста, а не строкой под разъёмом. В потоке она делила
   с ним центр, и синий круг вставал на 10 px выше гнезда Schuko — оси четырёх
   приборов в одной рамке расходились на глаз. */
.outlet__tag {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 5%;
  font-size: calc(var(--device) * 0.042);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #5a5e66;
  text-align: center;
  white-space: nowrap;
}

.gang__key {
  appearance: none;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-between;
  gap: 0.35em;
  margin: 0;
  padding: 12% 8% 11%;
  border: 0;
  border-radius: 7px;
  color: #5a5e66;
  background: linear-gradient(180deg, #2e2e32 0%, #1c1c20 52%, #161618 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.16),
    inset 0 -1px 0 rgba(0, 0, 0, 0.45),
    0 1px 1px rgba(0, 0, 0, 0.25);
  font-family: var(--sans);
  font-size: calc(var(--device) * 0.072);
  /* Гравировка на клавише мелкая и не подсвечена: на 700 она читалась вялой,
     на 800 набирает вес, но ещё не заплывает в тёмной сцене. */
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  cursor: pointer;
  touch-action: manipulation;
  transition: background 0.16s ease, color 0.16s ease, box-shadow 0.16s ease,
    text-shadow 0.16s ease, transform 0.12s ease, filter 0.16s ease;
}

.gang__leds {
  display: flex;
  gap: 3px;
  height: 3px;
}

.gang__leds i {
  display: block;
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: #3a3d44;
  box-shadow: inset 0 0.5px 0 rgba(255, 255, 255, 0.18);
}

.gang__key--wide {
  grid-column: 1 / -1;
}

.gang__key span {
  white-space: nowrap;
}

.gang__ico {
  width: 1.85em;
  height: 1.85em;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.55;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.gang__key:hover {
  color: #8b8f97;
}

.gang__key:active {
  transform: translateY(1px);
  background: linear-gradient(180deg, #1a1a1d 0%, #121214 100%);
  box-shadow:
    inset 0 2px 5px rgba(0, 0, 0, 0.55),
    inset 0 1px 0 rgba(255, 255, 255, 0.05);
}

.gang__key.is-on,
.gang__key[aria-pressed="true"] {
  color: #f7f8fa;
  text-shadow: 0 0 8px rgba(220, 230, 255, 0.9);
}

.gang__key.is-on .gang__ico,
.gang__key[aria-pressed="true"] .gang__ico {
  filter: drop-shadow(0 0 4px rgba(220, 230, 255, 0.95));
}

.gang__key.is-on .gang__leds i,
.gang__key[aria-pressed="true"] .gang__leds i {
  background: #eef2f8;
  box-shadow: 0 0 5px 1px rgba(220, 230, 255, 0.7);
}

/* Шторы едут ~5 секунд: клавиша не просто горит, а пульсирует, пока полотно
   движется, и гаснет сама по таймеру (см. main.js). */
.gang__key.is-on[data-group="curtains"],
.gang__key[aria-pressed="true"][data-group="curtains"] {
  animation: curtain-pulse 1.1s ease-in-out infinite;
}

@keyframes curtain-pulse {
  0%,
  100% {
    text-shadow: 0 0 6px rgba(220, 230, 255, 0.7);
    filter: brightness(1);
  }
  50% {
    text-shadow: 0 0 15px rgba(220, 230, 255, 1);
    filter: brightness(1.4);
  }
}

/* Отрицательный отступ уводит кольцо внутрь клавиши — читается как подсветка
   её собственной кромки, а не как розовая рамка на тёмной панели. */
.gang__key:focus-visible {
  outline: 1.5px solid rgba(220, 230, 255, 0.6);
  outline-offset: -4px;
}

.lcd {
  border-radius: 12px;
}

.lcd__bezel {
  position: relative;
  width: 100%;
  height: 100%;
  padding: var(--bezel);
  border-radius: var(--radius);
  background: linear-gradient(145deg, #323236 0%, #1a1a1c 42%, #0e0e10 100%);
  box-shadow:
    inset 1.5px 1.5px 0 rgba(255, 255, 255, 0.32),
    inset -1px -1px 0 rgba(0, 0, 0, 0.55),
    0 2px 2px rgba(0, 0, 0, 0.28),
    0 12px 22px rgba(0, 0, 0, 0.2),
    0 22px 36px rgba(20, 12, 8, 0.18);
}

.lcd__glass {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
  border-radius: 3px;
  background: #111;
  cursor: pointer;
}

.lcd__glass canvas {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  touch-action: none;
}

/* ---------- комбо-термостат спальни ---------- */

.combo__glass {
  position: relative;
  display: flex;
  min-width: 0;
  min-height: 0;
  padding: 4%;
  border-radius: 6px;
  background:
    radial-gradient(90% 70% at 50% 12%, rgba(70, 82, 100, 0.16), transparent 60%),
    linear-gradient(180deg, #1b1e24 0%, #0e1013 100%);
  box-shadow:
    inset 0 2px 6px rgba(0, 0, 0, 0.7),
    inset 0 -1px 0 rgba(255, 255, 255, 0.05);
  overflow: hidden;
}

.combo__glass .thermo {
  --screen: #15181d;
  width: 100%;
  height: 100%;
  aspect-ratio: auto;
}

/* --- термостат: геометрия ha-control-circular-slider из Home Assistant --- */

/* Дуга 270° радиусом 145 в поле 320×320, повёрнутая на 135°, — поэтому
   разрыв смотрит вниз и в него встают кнопки. Толщины штрихов заданы в
   единицах viewBox, а не в пикселях: кольцо остаётся тем же при любом
   --device. Порядок слоёв тоже от HA — подложка, дуга уставки в половину
   прозрачности, поверх активный участок между измеренной и заданной. */
.thermo {
  --heat: #ff8100;
  --cool: #2b9af9;
  --idle: #8a8a8a;
  /* Цветная дуга идёт в половину прозрачности, поэтому под ней сначала
     «прорезаем» серую подложку цветом стекла — иначе оттенок мутнеет. */
  --screen: #15181d;
  position: relative;
  width: 100%;
  aspect-ratio: 1;
  flex: none;
  align-self: center;
  min-height: 0;
  color: #e8edf2;
  /* Подписи внутри кольца заданы в cqw, поэтому термостат одинаково выглядит
     и на всю ширину экрана в детской, и в остатке высоты корпуса спальни. */
  container-type: inline-size;
}

/* Подсветка активного режима, как в карточке HA. */
.thermo::after {
  content: "";
  position: absolute;
  inset: -9%;
  border-radius: 50%;
  background: radial-gradient(50% 50% at 50% 50%, var(--glow, transparent), transparent 70%);
  opacity: 0.18;
  pointer-events: none;
  transition: background 0.18s ease-in-out;
}

.thermo__ring {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* Было visible, и зона захвата обводкой 48px выходила из вьюбокса на 9px —
     то есть кольцо можно было тянуть, кликнув по стене рядом с панелью.
     Видимая геометрия в границы влезает, обрезка ничего не съедает. */
  overflow: hidden;
  pointer-events: none;
}

.thermo__ring g {
  fill: none;
}

/* Тянуть можно за всё кольцо — кроме тач-устройств, где широкая зона
   перехватывала бы скролл страницы: там остаётся только сама точка. */
.thermo__hit {
  stroke: transparent;
  stroke-width: 48px;
  stroke-linecap: round;
  pointer-events: stroke;
  cursor: pointer;
  touch-action: none;
}

.thermo__grab {
  fill: transparent;
  pointer-events: none;
  cursor: grab;
  touch-action: none;
}

@media (pointer: coarse) {
  .thermo__hit {
    pointer-events: none;
  }

  .thermo__grab {
    pointer-events: auto;
  }
}

.thermo__bg {
  stroke: var(--idle);
  stroke-width: 24px;
  stroke-linecap: round;
  opacity: 0.26;
}

.thermo__arc,
.thermo__edge,
.thermo__dot,
.thermo__cur {
  stroke-linecap: round;
  transition:
    stroke-dasharray 0.3s ease-in-out,
    stroke-dashoffset 0.3s ease-in-out,
    stroke 0.18s ease-in-out,
    stroke-width 0.3s ease-in-out;
}

/* На перетаскивании дуга должна успевать за пальцем, а не догонять его. */
.thermo.is-drag .thermo__arc,
.thermo.is-drag .thermo__edge,
.thermo.is-drag .thermo__dot,
.thermo.is-drag .thermo__cur {
  transition:
    stroke 0.18s ease-in-out,
    stroke-width 0.3s ease-in-out;
}

.thermo__arc {
  stroke-width: 24px;
}

.thermo__arc--clear,
.thermo__cur--clear {
  stroke: var(--screen);
}

.thermo__arc--dim {
  opacity: 0.5;
}

.thermo__arc--active {
  outline: none;
}

.thermo__arc--active:focus-visible {
  stroke-width: 28px;
}

/* Измеренная температура — тонкая засечка на шкале. */
.thermo__cur {
  stroke: #e8edf2;
  stroke-width: 8px;
  opacity: 0.5;
}

/* Ручка: цветной кружок с белой серединой. */
.thermo__edge {
  stroke-width: 24px;
}

.thermo__dot {
  stroke: #fff;
  stroke-width: 18px;
}

.is-heat {
  stroke: var(--heat);
}

.is-cool {
  stroke: var(--cool);
}

.thermo__info {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.2em;
  pointer-events: none;
}

.thermo__act {
  margin: 0;
  font-size: 6.5cqw;
  color: rgba(232, 237, 242, 0.6);
}

.thermo__val {
  margin: 0;
  font-size: 21cqw;
  font-weight: 800;
  letter-spacing: -0.045em;
  line-height: 1;
  text-shadow: 0 0 12px rgba(190, 210, 240, 0.25);
}

.thermo__val small {
  font-size: 0.5em;
  font-weight: 600;
  vertical-align: top;
  letter-spacing: 0;
}

.thermo__sub {
  margin: 0;
  display: flex;
  align-items: center;
  gap: 0.35em;
  font-size: 5.8cqw;
  color: rgba(232, 237, 242, 0.5);
  white-space: nowrap;
}

.thermo__sub .ico {
  width: 1.15em;
  height: 1.15em;
}

/* Диапазон: два числа рядом, активное выбирают нажатием. */
.thermo__dual {
  display: flex;
  align-items: baseline;
  gap: 0.3em;
}

.thermo__pick {
  appearance: none;
  margin: 0;
  padding: 0 0.1em;
  border: 0;
  border-radius: 4px;
  background: none;
  color: inherit;
  font: inherit;
  font-size: 15cqw;
  font-weight: 800;
  letter-spacing: -0.045em;
  line-height: 1;
  opacity: 0.42;
  cursor: pointer;
  pointer-events: auto;
  transition: opacity 0.18s ease-in-out;
}

.thermo__pick.is-on {
  opacity: 1;
}

.thermo__pick small {
  font-size: 0.5em;
  font-weight: 600;
  vertical-align: top;
  letter-spacing: 0;
}

.thermo__pick:focus-visible {
  outline: 2px solid var(--rose);
  outline-offset: 1px;
}

/* Кнопки живут в разрыве дуги внизу. */
.thermo__btns {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  justify-content: center;
  gap: 12cqw;
}

.thermo__b {
  appearance: none;
  display: grid;
  place-items: center;
  width: 17cqw;
  height: 17cqw;
  margin: 0;
  padding: 0;
  border: 1px solid currentColor;
  border-radius: 50%;
  background: none;
  color: var(--tone, rgba(232, 237, 242, 0.6));
  font: inherit;
  font-size: 9cqw;
  line-height: 1;
  cursor: pointer;
  transition: color 0.18s ease-in-out;
}

.thermo__b:hover {
  color: #eef2f8;
}

.thermo__b:focus-visible {
  outline: 2px solid var(--rose);
  outline-offset: 2px;
}

/* ---------- экран воздуха детской ---------- */

.air {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 4%;
  background:
    radial-gradient(90% 80% at 50% 30%, #1e2226, #0d0f12);
  color: #e8edf2;
  cursor: default;
  --co2: #59c26a;
}

.air--warn {
  --co2: #e4b23c;
}

.air--bad {
  --co2: #e05b4a;
}

.air .thermo {
  --screen: #181c20;
}

/* Точка CO₂ красится по той же шкале, что и раньше: зелёный, жёлтый, красный. */
.air__co2 {
  display: inline-flex;
  width: 0.5em;
  height: 0.5em;
  border-radius: 50%;
  background: var(--co2);
  box-shadow: 0 0 8px var(--co2);
}

/* ---------- планшет гаража ---------- */

/* Гараж не влезает в общий масштаб: камера отъезжает, но выключатель
   остаётся честными 86 мм относительно планшета. */
/* Щит всегда под рамкой, на любой ширине. Плечом к плечу они делили бы ширину,
   и на неё пришлось бы ужимать шаг: рамка на четыре поста просит 4 модуля,
   планшет 4.13, зазор 0.42 — 8.55 модуля в строке против 4.13 в столбик. В
   столбик ширина перестаёт быть узким местом, и шаг держится на 0.55, а не
   уползает к 0.478, как пришлось бы по самому тесному месту строки.

   По высоте столбик умещается в min-height сцены: 3.72 модуля (пост + зазор +
   планшет 2.58) на шаге 0.55 дают 622 px против 640 px запаса, поэтому равная
   высота комнат от этого не поехала. Выше 0.569 гараж начнёт распирать сцену и
   тянуть за собой все комнаты — там придётся выбирать между размером планшета
   и высотой страницы. */
.wall__set[data-room="garage"] {
  --fit: 0.55;
  --device: calc(var(--device-base) * var(--fit));
  flex-direction: column;
  gap: calc(var(--device) * 0.14);
}

/* На большом экране планшет можно поднять: в столбик ширины хватает с большим
   запасом (при шаге 0.65 щит просит 653 px из 1792 доступных на 1920), и
   упирается он не в неё, а в высоту сцены. Гараж — самый высокий набор, и его
   высоту резервируют все комнаты, поэтому шаг ограничен сверху: 3.72 модуля
   (пост + зазор + планшет 2.58) плюс 125 px отступов и плинтуса должны влезть
   в min-height сцены. При упоре min-height в 45rem это даёт ровно 0.658 — выше
   гараж начнёт тянуть за собой высоту всех комнат.

   Запрос по высоте, а не только по ширине: на широком, но низком окне
   min-height считается от 78vh и становится меньше, и тот же шаг там уже
   распирал бы сцену. */
@media (min-width: 1200px) and (min-height: 920px) {
  .wall__set[data-room="garage"] {
    --fit: 0.65;
  }
}

/* 15.6″ настенная панель — 355×222 мм. При посте 86 мм это ровно 4.13 модуля;
   врать в пропорциях нельзя, на них держится весь раздел. */
/* Рамка планшета считается от него самого, а не от поста: он шире модуля вчетверо,
   и общая мерка выключателей дала бы ему поле в палец толщиной. */
.tablet {
  --t-frame: clamp(5px, calc(var(--device) * 0.075), 10px);
  --t-radius: clamp(8px, calc(var(--device) * 0.105), 14px);
  width: min(calc(var(--device) * 4.13), 100%);
  aspect-ratio: 16 / 10;
  flex: none;
  border-radius: var(--t-radius);
}

.tablet__bezel {
  position: relative;
  width: 100%;
  height: 100%;
  padding: var(--t-frame);
  border-radius: var(--t-radius);
  background: linear-gradient(145deg, #323236 0%, #1a1a1c 42%, #0e0e10 100%);
  box-shadow:
    inset 1.5px 1.5px 0 rgba(255, 255, 255, 0.32),
    inset -1px -1px 0 rgba(0, 0, 0, 0.55),
    0 2px 2px rgba(0, 0, 0, 0.28),
    0 12px 22px rgba(0, 0, 0, 0.2),
    0 22px 36px rgba(20, 12, 8, 0.18);
}

/* Кегли внутри стекла считаются от --device, а не в rem: иначе при
   масштабировании стены и в медиа-запросах текст уплывает от макета. */
.gar {
  /* Пара графика проверена валидатором на этой подложке: полоса яркости,
     порог цветности, CVD ΔE 27 при цели 8. Менять — только парой. */
  --sun: #c98500;
  --home: #3987e5;
  --tone: rgba(233, 238, 244, 0.52);
  --txt: #e9eef4;
  --txt-2: rgba(233, 238, 244, 0.56);
  --surf: #101419;
  --tile: rgba(255, 255, 255, 0.05);

  display: flex;
  flex-direction: column;
  gap: 2.4%;
  width: 100%;
  height: 100%;
  padding: 3.4% 3.4% 3%;
  border-radius: 5px;
  background: radial-gradient(120% 90% at 50% 0%, #171c23, var(--surf) 72%);
  box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.6);
  color: var(--txt);
  font-variant-numeric: tabular-nums;
}

.gar__bar {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex: none;
  font-size: calc(var(--device) * 0.062);
  color: var(--txt-2);
}

.gar__where {
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--txt);
}

.gar__time {
  font-family: var(--mono);
}

.gar__body {
  display: flex;
  gap: 3.2%;
  flex: 1;
  min-height: 0;
}

.gar__col {
  display: flex;
  flex-direction: column;
  gap: calc(var(--device) * 0.05);
  flex: 0 0 46%;
  min-width: 0;
}

.gar__col--wide {
  flex: 1;
}

/* ---------- окно камеры въезда ---------- */

.cam {
  position: relative;
  margin: 0;
  flex: none;
  aspect-ratio: 3 / 2;
  border-radius: calc(var(--device) * 0.028);
  overflow: hidden;
  background: #0b0e12;
}

/* Пока ролика нет, кадр рисуется градиентами: фонарь, створки, асфальт.
   Так планшет не зияет чёрным прямоугольником ни секунды. */
.cam__draw {
  position: absolute;
  inset: 0;
  filter: saturate(0.42) contrast(1.14);
  background-color: #12161c;
  background-image:
    radial-gradient(34% 30% at 50% 4%, rgba(255, 216, 158, 0.34), transparent 72%),
    linear-gradient(180deg, transparent 0 57%, #191d24 57%, #0f1318 100%),
    repeating-linear-gradient(
      90deg,
      rgba(255, 255, 255, 0.05) 0 2.6%,
      transparent 2.6% 6.4%
    ),
    linear-gradient(180deg, #1d2430 0%, #171c24 57%, #101419 100%);
}

.cam__vid {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  object-fit: cover;
  opacity: 0;
  transition: opacity 0.4s ease;
}

.cam.is-live .cam__vid {
  opacity: 1;
}

/* Виньетка и подсветка сверху — то, чем кадр с камеры отличается от картинки. */
.cam__scrim {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(180deg, rgba(0, 0, 0, 0.45), transparent 26%),
    radial-gradient(120% 120% at 50% 50%, transparent 55%, rgba(0, 0, 0, 0.5));
}

/* Зерно матрицы и построчная развёртка. Без blend-mode: он ломает захват
   этой секции в отладочных браузерах, а разницы в кадре не даёт. */
.cam__grain {
  position: absolute;
  inset: 0;
  opacity: 0.14;
  background-image:
    repeating-linear-gradient(
      180deg,
      rgba(255, 255, 255, 0.05) 0 1px,
      transparent 1px 3px
    ),
    url("../img/plaster-noise.png");
  background-size: auto, 96px 96px;
  pointer-events: none;
}

.cam__tag {
  position: absolute;
  top: 4.5%;
  left: 3.5%;
  display: flex;
  align-items: center;
  gap: 0.5em;
  font-size: calc(var(--device) * 0.05);
  color: rgba(255, 255, 255, 0.82);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);
}

.cam__stamp {
  position: absolute;
  top: 4.5%;
  right: 3.5%;
  font-family: var(--mono);
  font-size: calc(var(--device) * 0.046);
  letter-spacing: -0.01em;
  color: rgba(255, 255, 255, 0.9);
  text-shadow:
    0 0 2px rgba(0, 0, 0, 0.9),
    0 1px 3px rgba(0, 0, 0, 0.8);
  pointer-events: none;
}

.cam__rec {
  width: 0.5em;
  height: 0.5em;
  border-radius: 50%;
  background: #e05b4a;
  box-shadow: 0 0 6px rgba(224, 91, 74, 0.8);
  animation: cam-rec 2.4s steps(1, end) infinite;
}

@keyframes cam-rec {
  0%,
  55% {
    opacity: 1;
  }
  56%,
  100% {
    opacity: 0.15;
  }
}

/* Плашка распознавания: до события её нет вовсе, иначе номер читается как
   декор, а не как результат работы камеры. */
.cam__anpr {
  position: absolute;
  left: 50%;
  bottom: 8%;
  display: flex;
  align-items: center;
  gap: 0.55em;
  padding: 0.3em 0.6em;
  border-radius: calc(var(--device) * 0.02);
  border: 1px solid rgba(255, 255, 255, 0.22);
  background: rgba(12, 16, 21, 0.82);
  backdrop-filter: blur(2px);
  font-size: calc(var(--device) * 0.052);
  white-space: nowrap;
  opacity: 0;
  transform: translate(-50%, 40%) scale(0.94);
  transition: opacity 0.25s ease, transform 0.25s ease;
  pointer-events: none;
}

.gar.is-seen .cam__anpr {
  opacity: 1;
  transform: translate(-50%, 0) scale(1);
}

.cam__anpr b {
  font-family: var(--mono);
  font-weight: 700;
  letter-spacing: 0.04em;
}

.cam__anpr i {
  font-style: normal;
  color: #6ecb8b;
}

/* ---------- лента событий ---------- */

/* ---------- вода и газ ---------- */

/* Под кадром камеры — расход воды и газа. Раньше здесь шла лента событий, но
   она пересказывала то, что и так видно на самом кадре (плашка распознавания)
   и на плитках справа; счётчики же показывают то, чего на дашборде больше
   нигде нет. Цвета берём из уже проверенной пары графика: синий за воду,
   янтарный за газ — новых оттенков на этой подложке не заводим. */
.util {
  display: flex;
  gap: 0.9em;
  flex: 1;
  min-height: 0;
  font-size: calc(var(--device) * 0.052);
  color: var(--txt-2);
}

.util__item {
  display: flex;
  flex-direction: column;
  gap: 0.45em;
  flex: 1;
  min-width: 0;
}

.util__item--water {
  --accent: var(--home);
}

.util__item--gas {
  --accent: var(--sun);
}

.util__head {
  display: flex;
  align-items: baseline;
  gap: 0.4em;
  min-width: 0;
}

.util__ico {
  flex: none;
  width: 1.15em;
  height: 1.15em;
  align-self: center;
  color: var(--accent);
}

.util__name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.util__val {
  margin-left: auto;
  font-family: var(--mono);
  font-size: 1.3em;
  font-weight: 700;
  color: var(--txt);
}

/* Столбики забирают всю высоту, что осталась от подписей. На фиксированной
   высоте график лежал полоской у верхнего края, а под ним чернела пустая
   половина экрана: места под кадром камеры вдвое больше, чем нужно строкам.
   Высота столбика приходит из --v на нём самом — рисунок недели правится в
   разметке, а не пересчитывается здесь. */
.util__bars {
  position: relative;
  display: flex;
  align-items: flex-end;
  gap: 0.3em;
  flex: 1;
  min-height: 0;
}

/* Две линии отсчёта: верх шкалы и половина. Без них высокий столбик не с чем
   сравнить, и график читается набором брусков. */
.util__bars::before,
.util__bars::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  pointer-events: none;
}

.util__bars::before {
  top: 0;
  border-top: 1px dashed rgba(233, 238, 244, 0.14);
}

.util__bars::after {
  top: 50%;
  border-top: 1px dashed rgba(233, 238, 244, 0.08);
}

.util__bars i {
  position: relative;
  flex: 1;
  height: calc(var(--v) * 100%);
  min-height: 3px;
  border-radius: 2px 2px 1px 1px;
  background: linear-gradient(
    180deg,
    rgba(233, 238, 244, 0.28),
    rgba(233, 238, 244, 0.12)
  );
}

.util__bars i.is-now {
  background: var(--accent);
  box-shadow: 0 0 10px -2px var(--accent);
}

/* Подписи дней идут тем же flex с тем же зазором, что столбики, — иначе
   колонки разъезжаются с подписями. */
.util__days {
  display: flex;
  gap: 0.3em;
  font-size: 0.82em;
  color: rgba(233, 238, 244, 0.3);
}

.util__days span {
  flex: 1;
  min-width: 0;
  text-align: center;
}

.util__days span.is-now {
  color: var(--accent);
  font-weight: 600;
}

.util__foot {
  display: flex;
  justify-content: space-between;
  gap: 0.5em;
  color: rgba(233, 238, 244, 0.4);
}

.util__foot b {
  font-weight: 600;
  color: var(--txt-2);
}

/* ---------- плитки ---------- */

.gar__tiles {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
  gap: calc(var(--device) * 0.05);
  flex: 1.05;
  min-height: 0;
}

.gt {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-rows: auto 1fr auto;
  align-content: center;
  column-gap: 0.55em;
  row-gap: 0.1em;
  padding: calc(var(--device) * 0.05) calc(var(--device) * 0.06);
  border-radius: calc(var(--device) * 0.028);
  background: var(--tile);
  min-width: 0;
  transition: background 0.3s ease;
}

.gt.is-on {
  --tone: #e8b64c;
  background: rgba(232, 182, 76, 0.13);
}

.gt__ico {
  grid-column: 1;
  grid-row: 1;
  width: calc(var(--device) * 0.072);
  height: calc(var(--device) * 0.072);
  align-self: center;
  fill: none;
  stroke: var(--tone);
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: stroke 0.3s ease;
}

.gt__name {
  grid-column: 2;
  grid-row: 1;
  align-self: center;
  font-size: calc(var(--device) * 0.056);
  color: var(--txt-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.gt__state {
  grid-column: 1 / -1;
  grid-row: 2;
  align-self: center;
  font-size: calc(var(--device) * 0.094);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.05;
  white-space: nowrap;
}

.gt__state small {
  font-size: 0.5em;
  font-weight: 600;
  color: var(--txt-2);
  margin-left: 0.15em;
}

.gt__sub {
  grid-column: 1 / -1;
  grid-row: 3;
  font-size: calc(var(--device) * 0.05);
  color: var(--txt-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.gt__track {
  grid-column: 1 / -1;
  grid-row: 3;
  align-self: center;
  height: calc(var(--device) * 0.022);
  border-radius: 99px;
  background: rgba(255, 255, 255, 0.1);
  overflow: hidden;
}

.gt__track i {
  display: block;
  width: 0;
  height: 100%;
  border-radius: 99px;
  background: var(--tone);
  transition: width 0.4s linear;
}

.gt__track--batt i {
  background: #3fae6a;
}

/* Пока створка едет — плитка дышит. Останавливается ровно тогда, когда
   ворота встали в крайнее положение. */
.gt.is-moving {
  animation: gt-move 1.4s ease-in-out infinite;
}

@keyframes gt-move {
  50% {
    background: rgba(232, 182, 76, 0.24);
  }
}

/* ---------- график суток ---------- */

.nrg {
  display: flex;
  flex-direction: column;
  gap: 0.3em;
  flex: 1;
  min-height: 0;
  margin: 0;
}

/* Заголовок работает и легендой, и прямыми подписями: цвет несёт только
   точка, сам текст — обычной краской. */
.nrg__head {
  display: flex;
  gap: 0.9em;
  flex: none;
  font-size: calc(var(--device) * 0.052);
  color: var(--txt-2);
  white-space: nowrap;
}

.nrg__k {
  display: flex;
  align-items: center;
  gap: 0.35em;
}

.nrg__k b {
  font-family: var(--mono);
  font-size: 1.12em;
  font-weight: 700;
  color: var(--txt);
}

.nrg__k--grid {
  margin-left: auto;
}

.nrg__dot {
  width: 0.55em;
  height: 0.55em;
  border-radius: 50%;
  background: currentColor;
}

.nrg__dot--sun {
  color: var(--sun);
}

.nrg__dot--home {
  color: var(--home);
}

.nrg__plot {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}

/* Сетка — сплошной волосок на тон от подложки. Пунктир читался бы как
   «прогноз» или «порог», хотя это просто отметка 4 кВт. */
.nrg__grid {
  stroke: rgba(255, 255, 255, 0.17);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

/* Волосок сетки без подписи не даёт прочитать масштаб. Подпись лежит вне
   svg: внутри её растянуло бы preserveAspectRatio="none". */
.nrg__wrap {
  position: relative;
  flex: 1;
  min-height: 0;
}

.nrg__unit {
  position: absolute;
  top: calc(50% - 1.15em);
  left: 0;
  font-family: var(--mono);
  font-size: calc(var(--device) * 0.044);
  color: rgba(233, 238, 244, 0.42);
  pointer-events: none;
}

.nrg__sun {
  fill: var(--sun);
  fill-opacity: 0.5;
  stroke: none;
}

.nrg__home {
  fill: none;
  stroke: var(--home);
  stroke-width: 2;
  stroke-linejoin: round;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
  transition: d 0.6s ease;
}

/* Точка «сейчас» лежит поверх заливки, поэтому ей нужно кольцо подложки. */
.nrg__now {
  fill: var(--home);
  stroke: var(--surf);
  stroke-width: 2;
  vector-effect: non-scaling-stroke;
  transition: cy 0.6s ease;
}

.nrg__axis {
  display: flex;
  justify-content: space-between;
  flex: none;
  font-family: var(--mono);
  font-size: calc(var(--device) * 0.044);
  color: rgba(233, 238, 244, 0.42);
}

/* ---------- панель сауны (CSS-макет по sauna2_lcd.yaml) ---------- */

.sauna {
  padding: 3%;
  background: #241a12;
  cursor: default;
}

/* Фон панели — только после открытия вкладки «Сауна», иначе браузер
   тянет картинку на первом экране вместе с hero. */
.sauna.is-bg {
  background:
    url("../lcd/main_bg.webp") center / cover no-repeat,
    #241a12;
}

/* Пять плиток: парная крупно на всю ширину, под ней четыре в два столбца.
   Раньше здесь была сетка 2×4 на восемь плиток — половина из них повторяла
   друг друга («Вент Парная» и «Вент Хамам»), и ни одна не была главной. */
.sauna__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1.2fr 1fr 1fr;
  gap: 2.5%;
  width: 100%;
  height: 100%;
}

.sauna__card--hero {
  grid-column: 1 / -1;
}

.sauna__card {
  position: relative;
  display: grid;
  grid-template-columns: auto 1fr auto;
  grid-template-rows: 1fr 1fr;
  align-items: center;
  column-gap: 6%;
  padding: 3% 4%;
  border-radius: calc(var(--device) * 0.033);
  background: linear-gradient(180deg, rgba(26, 30, 40, 0.62), rgba(16, 19, 26, 0.62));
  border: 1px solid rgba(255, 255, 255, 0.08);
  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.45);
  color: rgba(240, 230, 210, 0.88);
  overflow: hidden;
}

/* Бриз, пар и пол — кнопки, поэтому им надо снять вид кнопки: карточка уже
   несёт и фон, и рамку, и сетку. */
button.sauna__card {
  appearance: none;
  margin: 0;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

button.sauna__card:focus-visible {
  outline: 2px solid #ffcc00;
  outline-offset: 1px;
}

/* Парная показывает главное число экрана, поэтому её температура крупнее. */
.sauna__card--hero .sauna__t {
  font-size: calc(var(--device) * 0.105);
}

.sauna__card--hero .gang__ico {
  width: calc(var(--device) * 0.09);
  height: calc(var(--device) * 0.09);
}

/* Печь — единственное место, где 400° это норма, а не авария: держим её
   янтарной, как огонь, и не красим в красный. */
.sauna__card--oven.is-on .sauna__t {
  color: #ffb347;
}

/* На узкой плитке значение не встаёт рядом с подписью: под имя остаётся пара
   десятков пикселей, и «Тёплый пол» режется по слогу. Ставим значение под
   подписью — тогда колонка целиком под текст. Парная и вентиляция идут во всю
   ширину, им места хватает, и число остаётся сбоку. */
.sauna__card:not(.sauna__card--hero) .sauna__t {
  grid-column: 2;
  grid-row: 2;
  align-self: start;
  min-width: 0;
  text-align: left;
}

.sauna__card .gang__ico {
  grid-column: 1;
  grid-row: 1 / -1;
  width: calc(var(--device) * 0.075);
  height: calc(var(--device) * 0.075);
  color: rgba(255, 255, 255, 0.28);
}

.sauna__card.is-on .gang__ico {
  color: #ffcc00;
  filter: drop-shadow(0 0 5px rgba(255, 204, 0, 0.6));
}

.sauna__name {
  grid-column: 2;
  grid-row: 1;
  align-self: end;
  font-size: calc(var(--device) * 0.04);
  font-weight: 700;
  letter-spacing: 0.01em;
  line-height: 1.05;
  white-space: normal;
  min-width: 0;
}

.sauna__rh {
  grid-column: 2;
  grid-row: 2;
  align-self: start;
  white-space: nowrap;
  font-family: var(--mono);
  font-size: calc(var(--device) * 0.046);
  color: #9ad0e8;
}

/* Ширина по содержимому, а не жёсткие 3em: в них влезает «400°», но не
   влезает «подать» — слово уезжало за край плитки. */
.sauna__t {
  grid-column: 3;
  grid-row: 1 / -1;
  min-width: 2.4em;
  text-align: right;
  white-space: nowrap;
  font-family: var(--mono);
  font-size: calc(var(--device) * 0.054);
  color: #ffc864;
  text-align: right;
  white-space: nowrap;
}

.lcd__ph {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  pointer-events: none;
  transition: filter 0.4s ease;
}

/* «Ушли» на постере: панель гаснет. Нужно, пока WASM не поднялся — в живой
   панели то же состояние приходит из прошивки через ui_vent_stop(). */
.wall__set.is-away .lcd__ph {
  filter: brightness(0.42) saturate(0.55);
}

/* ---------- responsive ---------- */

@media (max-width: 1240px) {
  /* The phone number is the first thing to go: the nav links and the main
     call to action matter more than a tappable number on a laptop. */
  .nav__actions .btn--outline {
    display: none;
  }
}

@media (max-width: 1000px) {
  .manifest__grid {
    grid-template-columns: 1fr 1fr;
  }

  .hw__top,
  .specs {
    grid-template-columns: 1fr;
    align-items: start;
  }

  .scope,
  .niche__grid,
  .partners {
    grid-template-columns: 1fr 1fr;
  }

  .wall {
    --device-base: min(13.6rem, 21vw);
  }
}

/* Шестой пункт в меню («Партнёрам») перестаёт помещаться в строку раньше, чем
   включался бургер, и шапка распирала страницу по горизонтали. Сначала жмём шаг
   и кегль, а с 980px уходим в бургер — раньше это происходило только на 820px. */
@media (max-width: 1080px) {
  .nav__links {
    gap: 0.95rem;
    font-size: 0.9rem;
  }
}

@media (max-width: 980px) {
  .nav__links {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    display: none;
    flex-direction: column;
    gap: 0;
    font-size: 0.95rem;
    padding: 0.5rem var(--pad) 1.2rem;
    background: var(--paper);
    border-bottom: 1px solid var(--line);
  }

  .nav.open .nav__links {
    display: flex;
  }

  .nav__links a {
    padding-block: 0.75rem;
  }

  .nav__actions .btn--outline {
    display: none;
  }

  .burger {
    display: grid;
    place-items: center;
  }
}

@media (max-width: 820px) {
  .brand__slogan {
    display: none;
  }

  .compare,
  .fork,
  .vision__grid,
  .ai,
  .pros,
  .dirs__panel,
  .contact__grid {
    grid-template-columns: 1fr;
  }

  .vision__grid {
    grid-template-rows: none;
  }

  .vision__grid article {
    display: block;
    grid-row: auto;
  }

  .vision__grid ul {
    display: block;
    margin: 1.4rem 0 0;
  }

  .vision__grid li:first-child {
    margin-top: 0;
  }

  .wall__scene {
    min-height: min(58vh, 27rem);
  }

  /* Узкая сцена: панель мелкая, и поднимать её под потолок нечем — между ней
     и кроватью повисает пустая стена. Возвращаем панель в центр, а кровати
     отдаём больше ширины, иначе обстановка не читается. Центрирование делает
     всю работу: padding-top здесь стоять не может, он ломает равную высоту
     комнат — коробка отступами не правится. */
  .wall[data-room="bedroom"] .wall__devices {
    align-items: center;
  }

  /* Геометрии окна здесь больше нет. Блок .room__stage с --win-* стоял на этом
     месте, но не работал ни дня: rooms.css подключён после styles.css и держит
     то же .room__stage с той же специфичностью, а медиазапрос специфичность не
     повышает — побеждал всегда rooms.css. Окно правят только там. */

  .bed {
    width: 96%;
  }

  /* Ряд из трёх модулей занимал здесь две трети ширины, а треть оставалась
     пустой стеной — при том, что подписи на клавишах уже не читались. Отдаём
     устройствам место, которое всё равно пустовало. */
  .wall {
    --device-base: min(13rem, 24vw);
  }

  /* Столбик и зазор гараж держит на всех ширинах (см. базовое правило), здесь
     остаётся только шаг: на узком экране база и так мелкая, и на 0.55 подписи
     в плитках планшета уходят в кашу. */
  .wall__set[data-room="garage"] {
    --fit: 0.86;
  }
}

@media (max-width: 620px) {
  .manifest__grid,
  .scope,
  .dirs,
  .niche__grid,
  .partners {
    grid-template-columns: 1fr;
  }

  .hero__actions .btn {
    width: 100%;
  }

  /* На узких экранах заголовок занимает всю колонку, поэтому контур
     уходит ещё глубже в фон. */
  .hero__map {
    opacity: 0.13;
  }

  .ways .k {
    display: none;
  }

  /* На телефоне ширины хватает ровно на один корпус в строку. Ставим экран под
     выключатель — тогда на выключатель работает вся ширина, и корпус выходит
     в полтора раза крупнее, чем когда они делили строку втроём. Спальне, сауне
     и детской разворот не нужен: у них один корпус, он и так помещается. */
  .wall {
    --device-base: min(13rem, 40vw);
  }

  .wall__set[data-room="living"] {
    flex-direction: column;
    gap: calc(var(--device) * 0.14);
  }

  /* Гараж считает шаг сам: его планшет — жёстко 4.13 поста, и общая мерка
     комнат, рассчитанная на два поста в строку, разносит его далеко за экран.
     Врать в пропорциях планшета нельзя, поэтому уступает шаг. */
  .wall__set[data-room="garage"] {
    --device: min(8.2rem, 21vw);
  }

  /* Столбики воды и газа оставляем: без них на телефоне «нет графиков».
     Подписи дней и итог недели на этой высоте не читаются — прячем их,
     место отдаём самим столбикам под кадром камеры. */
  .gar .util {
    display: flex;
    font-size: clamp(8px, 2.3vw, 11px);
    gap: 0.55em;
  }

  .gar .util__days,
  .gar .util__foot {
    display: none;
  }

  .gar .util__bars {
    min-height: 2.4em;
  }

  .gar__col {
    justify-content: flex-start;
  }

  .wall__devices {
    padding-inline: 1rem;
    gap: 0.9rem;
  }

  .gang__key {
    letter-spacing: 0.1em;
  }
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  * {
    transition: none !important;
  }

  .hero__map path {
    animation: none;
    stroke-dashoffset: 0;
  }

  html.js .wall {
    --zoom: 1;
  }

  html.js .wall__devices,
  .wall__devices {
    opacity: 1;
    transform: none;
    filter: none;
    transition: none;
    will-change: auto;
  }

  /* Точка записи мигает сама по себе, бесконечно и ни о чём не сообщая —
     это чистое оформление, при reduce его быть не должно.

     А вот «дыхание» плитки створки (.gt.is-moving) и пульс клавиши штор
     отсюда убраны намеренно: они включаются только на время реального хода
     и гаснут, когда привод встал. Раз сам ход при reduce мы показываем
     (см. блок ниже), то и признак «идёт» должен быть виден — иначе
     непонятно, приняли команду или нет. */
  .cam__rec {
    animation: none;
  }
}

/* Ход шторы, полотна ворот и подача пара — не оформление, а само содержание:
   в них показано, что делает привод. reduce-motion гасит паразитное движение
   (зум от скролла, параллакс, мигание индикаторов), но если убрать и эти три,
   от раздела остаётся статичная картинка, и клавиша перестаёт что-либо
   значить. Поэтому возвращаем их вручную.

   !important здесь обязателен: выше в том же медиавыражении стоит
   `* { transition: none !important }`, а important-объявление перебивается
   только другим important. Специфичность .curtain (0,1,0) против * (0,0,0)
   решает порядок между ними. */
@media (prefers-reduced-motion: reduce) {
  .curtain {
    transition: transform 5s cubic-bezier(0.33, 0, 0.67, 1) !important;
  }

  .gar-door__leaf {
    transition: transform 2.4s cubic-bezier(0.45, 0.05, 0.35, 1) !important;
  }

  /* Запотевание стекла идёт вместе с клубами: сами клубы — animation, её
     общий сброс не трогает, а вот дымку на стекле ведёт transition. */
  .sau-door__steam,
  .sau-door__glass {
    transition: opacity 0.7s ease, background 0.7s ease !important;
  }
}


/* ========== rooms.css (merged) ========== */

/* ============================================================================
   Обстановка комнат: предметы, стены и свет.

   Файл отделён от styles.css намеренно. Механику прохода по комнатам правят
   в styles.css и main.js, а здесь живёт только картинка — так две работы не
   спорят за одни и те же строки.

   ---------------------------------------------------------------------------
   Договор с механикой

   Механика выставляет на .wall числа от 0 до 1, остальное считает CSS:

     --l-main   основной свет
     --l-amb    подсветка
     --l-day    дневной свет из окна, он же открытость штор
     --l-lamp   торшер, настольная лампа
     --l-tv     отсвет телевизора со стороны зрителя

   и атрибут data-scene на .wall для готовых сцен. Число задаёт силу источника,
   сцена — его окраску.

   Клавиши сами говорят, чем управляют: data-lit="main|amb" — одиночный
   источник, data-scene="movie|party|relax|away|study|night" — готовая сцена,
   data-day — положение штор. Раскладка сцен гостиной:

     сцена    main   amb    day    lamp   tv
     нет      0      0      0.55   0      0
     away     0      0      0.20   0      0
     party    0.16   1      0      0      0.30
     relax    0      0.55   0.30   1      0
     movie    0      0.24   0      0      1

   Раскладка сцен детской:

     сцена    main   amb    day    lamp   tv
     нет      0      0      0.55   0      0
     study    0.08   0      0.40   1      0
     night    0      0.24   0      0      0

   В детской --l-amb — это ночник у изголовья, а не карниз, поэтому в «Ночнике»
   он стоит на 0.24, а не на 0.55, как лента в «Отдыхе» гостиной. Число это не
   про яркость пятна, а про --room-lit: он берёт --l-amb с весом 0.4, и на 0.55
   темнота отступала по всей комнате разом. Выходил ровный мутный налёт вместо
   тёмной детской с одним тёплым пятном у колыбели — источник переставал
   читаться источником. Настольная лампа в этой сцене погашена совсем: изнанка
   абажура и так не гаснет в ноль, а второе пятно у стола спорило с ночником.
   «Шторы» одна
   клавиша: нажата — день 0, отпущена — день комнаты по умолчанию.
   Уровень CO₂ читается с .air[data-co2]: классы air--warn / air--bad,
   которые уже ставит main.js, включают слой .lit--air.

   Сцена и одиночные клавиши взаимно исключают друг друга: включив «Кино»,
   нельзя остаться в «Вечеринке», а включив главный свет — в «Кино».
   Развязку и раскладку сцен держит applyScene() в main.js.

   Друг друга одиночные источники при этом не гасят: --l-main и --l-amb — это
   независимые контуры, и в сауне «Свет» с «Подсветкой» штатно горят вместе.

   Часть сцены живёт не в числах, а в разметке: вечеринке нужен .lit--disco с
   пятнами света внутри. Он висит в гостиной постоянно и включается одним
   лишь data-scene="party" — трогать его из JS не нужно, но и выкидывать из
   .room__stage--living нельзя, иначе сцена останется без движения.

   ---------------------------------------------------------------------------
   Смена комнаты — смена декораций

   Оболочка (стена, окно, шторы, плинтус, пол) одна на все комнаты.
   Меняется мебель и набор на стене. showRoom() в main.js ведёт три такта:

     1. data-turn="out" на .wall и уходящем .room__stage — предметы уезжают,
        панель .wall__devices растворяется с первого кадра
     2. голая стена (--bare), в ней мгновенно меняются data-room, набор
        клавиш (пока панель невидима), подпись и свет
     3. data-turn="in" — предметы въезжают, панель проявляется

   Направление пишет --dir на .wall: 1 вперёд по списку комнат, −1 назад.
   Уход разгоняется, заход тормозит; прозрачность мебели не трогаем — клип
   даёт .room { overflow: hidden }. Панель гаснет opacity, не уезжает.

   Мебель каждой комнаты лежит в .furn (container-type: inline-size, чтобы
   путь улёта в cqw работал и для дивана, и для растения). Предметы —
   .furn__item с --i (порядок ухода). На .furn стоит --n (сколько предметов):
   заход идёт задержкой (n − 1 − i), от крупного к мелкому.

   Правило для детской, гаража и сауны: три–пять самостоятельных предметов
   на комнату, каждый в .furn со своим --i. Меньше — такт пустой, больше —
   рябь. Пока у комнаты нет .room__stage, переход всё равно работает: мебель
   уезжает, заезжать нечему, остаётся голая стена с её набором клавиш.

   Каждая комната лежит в своём .room__stage--<имя> и показывается, когда
   data-room на .wall совпадает с именем. Opacity этапа переключается
   мгновенно в пустом такте — пересечения этапов больше нет.

   Спальня — исключение по свету: её слои висят на .room и ими управляет
   syncLights() из main.js. Остальные комнаты считают свет сами из переменных,
   поэтому им не нужен ни один байт JS.
   ============================================================================ */

/* Слои света: внутри .wall__face, под устройствами. Низ уходит за плинтус на
   высоту пола, чтобы мебель стояла на полу, а не висела на линии стены. */
.room {
  position: absolute;
  inset: 0 0 calc(-1 * var(--floor)) 0;
  /* Выше плинтуса: кровать стоит перед ним, а свет ложится и на него тоже. */
  z-index: 2;
  pointer-events: none;
  overflow: hidden;
}

.room > span {
  position: absolute;
  inset: 0;
}

/* Комнаты гасятся прозрачностью, а не display: так этап остаётся в дереве
   и может держать мебель за кадром до конца ухода. Visibility тут не нужна —
   вся .room и так aria-hidden. */
.room__stage {
  position: absolute;
  inset: 0;
  opacity: 0;
}

.wall[data-room="bedroom"] .room__stage--bedroom,
.wall[data-room="living"] .room__stage--living,
.wall[data-room="kids"] .room__stage--kids,
.wall[data-room="garage"] .room__stage--garage,
.wall[data-room="sauna"] .room__stage--sauna {
  opacity: 1;
}

/* Времена смены декораций. JS читает их через getComputedStyle — крутить
   только здесь. --out-total / --in-total считает сам showRoom() из --n. */
.wall {
  --dir: 1;
  --out-dur: 380ms;
  --out-step: 70ms;
  --bare: 140ms;
  --in-dur: 460ms;
  --in-step: 80ms;
}

/* Обёртка мебели: container-type даёт cqw, чтобы путь улёта был один и для
   дивана на 47%, и для растения на 8.5%. На .room__stage класть нельзя —
   containment сделает его контекстом наложения и сломает mix-blend-mode у
   диско-пятен. */
.furn {
  position: absolute;
  inset: 0;
  container-type: inline-size;
  pointer-events: none;
}

.room__stage--living .furn {
  --n: 3;
}

.room__stage--bedroom .furn {
  --n: 4;
}

.room__stage--kids .furn {
  --n: 3;
}

.room__stage--sauna .furn {
  --n: 3;
}

@media (prefers-reduced-motion: no-preference) {
  /* Этап без transition: в пустом такте data-room меняется мгновенно.
     Слои света и шторы по-прежнему плавно. */
  .room > span,
  .lit,
  .bed__glow,
  .desk__glow,
  .crib__night {
    transition: opacity 0.45s ease, filter 0.45s ease;
  }

  /* Уход разгоняется, заход тормозит. fill: both держит предмет за кадром
     до его очереди и не даёт откатиться после. Селектор на этапе, а не на
     .wall — иначе анимация гоняла бы невидимые комнаты. */
  .room__stage[data-turn="out"] .furn__item {
    animation: furn-out var(--out-dur) cubic-bezier(0.5, 0, 0.9, 0.35) both;
    animation-delay: calc(var(--i, 0) * var(--out-step));
  }

  .room__stage[data-turn="in"] .furn__item {
    animation: furn-in var(--in-dur) cubic-bezier(0.16, 1, 0.3, 1) both;
    animation-delay: calc((var(--n) - 1 - var(--i, 0)) * var(--in-step));
  }

  /* Панель растворяется с первого кадра ухода и проявляется с заходом:
     подмена hidden идёт в пустом такте, пока opacity уже ноль. */
  .wall[data-turn="out"] .wall__devices {
    animation: devices-out var(--out-dur) ease both;
    pointer-events: none;
  }

  .wall[data-turn="in"] .wall__devices {
    animation: devices-in var(--in-dur) ease both;
    pointer-events: none;
  }
}

/* --dir: 1 — уезжает влево, новое справа; −1 — зеркало при ходе назад. */
@keyframes furn-out {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(calc(var(--dir) * -125cqw));
  }
}

@keyframes furn-in {
  from {
    transform: translateX(calc(var(--dir) * 125cqw));
  }
  to {
    transform: translateX(0);
  }
}

@keyframes devices-out {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}

@keyframes devices-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

/* ---------- окно и шторы: общие для всех комнат ---------- */

/* Геометрия окна собрана из переменных: на узком экране меняются только они,
   а карниз и шторы едут за окном сами. Этот же прямоугольник в гараже целиком
   занимает подъёмная дверь — см. .gar-door.

   Окно панорамное и в кадр входит не целиком: левый край уходит за кромку
   сцены (--win-x отрицательный), и его срезает .room со своим overflow. Так
   стена перестаёт быть коробкой с картинкой посередине — комната продолжается
   за кадром. Правый край при этом остался там же, где был, иначе мебель и
   панель пришлось бы переставлять вслед за окном. */
.room__stage {
  --win-x: -9%;
  --win-w: 35%;
  --win-t: 6%;
  /* Низ садится на линию пола, а не на плинтус: окно в пол его перебивает.
     Считаем от --floor, а не процентом от сцены — процент увёл бы низ с этой
     линии на любой другой высоте сцены, а рем держится за неё. */
  --win-b: var(--floor);
  /* Половина окна плюс запах: закрытые шторы сходятся с перехлёстом, а не
     встык, иначе по центру видно щель. */
  --cur-w: calc(var(--win-w) / 2 + 3.4%);
}

/* Панорамное окно: рама, переплёт и небо. Вид за окном добавим позже. */
.win {
  position: absolute;
  left: var(--win-x);
  width: var(--win-w);
  top: var(--win-t);
  bottom: var(--win-b);
  border: 10px solid #efe9df;
  border-radius: 4px;
  background: linear-gradient(180deg, #b4d0e8 0%, #cfe0ec 54%, #e4e7e0 100%);
  box-shadow:
    0 16px 32px rgba(40, 28, 16, 0.16),
    inset 0 0 0 1px rgba(70, 52, 34, 0.22),
    inset 0 10px 24px rgba(255, 255, 255, 0.3);
}

.win__grid {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(
      90deg,
      transparent calc(50% - 3px),
      #efe9df calc(50% - 3px),
      #efe9df calc(50% + 3px),
      transparent calc(50% + 3px)
    ),
    linear-gradient(
      180deg,
      transparent calc(38% - 3px),
      #efe9df calc(38% - 3px),
      #efe9df calc(38% + 3px),
      transparent calc(38% + 3px)
    );
}

.win__rod {
  position: absolute;
  left: calc(var(--win-x) - 2.6%);
  width: calc(var(--win-w) + 5%);
  top: calc(var(--win-t) - 1.9%);
  height: 7px;
  border-radius: 4px;
  background: linear-gradient(#c2b19c, #8a7862);
  box-shadow: 0 4px 9px rgba(40, 28, 16, 0.22);
}

/* Шторы не уезжают за кадр, а собираются в сборку у края окна: панель
   сжимается по ширине, складки от этого становятся плотнее. */
.curtain {
  position: absolute;
  top: calc(var(--win-t) - 1.1%);
  /* Ровно по низу окна: раньше шторы висели на 3% ниже, чтобы свисать за
     подоконник, но теперь низ окна — сама линия пола, и этот запас уводил
     ткань на половицы. */
  bottom: var(--win-b);
  width: var(--cur-w);
  border-radius: 0 0 3px 3px;
  /* Без JS — calc-фолбэк; с JS — paintCurtains() пишет inline scale3d().
     Safari iOS не интерполирует calc(var()), но конкретные scale3d-числа —
     может. Переход ≈5 с, почти линейный (hermite S-curve в main.js давала
     тот же характер). JS ставит transition: none при смене комнаты, чтобы
     шторы не ехали мимоходом. */
  transform: scaleX(calc(1 - 0.8 * var(--l-day)));
  transition: transform 5s cubic-bezier(0.33, 0, 0.67, 1);
  will-change: transform;
  background: repeating-linear-gradient(
    90deg,
    #e6ddd0 0 7px,
    #d2c6b4 7px 13px,
    #ece4d8 13px 20px
  );
  box-shadow: 0 14px 26px rgba(40, 28, 16, 0.16);
}

.curtain::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
}

.curtain--l {
  left: calc(var(--win-x) - 2.4%);
  transform-origin: left center;
}

.curtain--l::after {
  background: linear-gradient(90deg, rgba(255, 255, 255, 0.22), transparent 40%),
    linear-gradient(270deg, rgba(58, 42, 28, 0.22), transparent 26%);
}

.curtain--r {
  left: auto;
  right: calc(100% - var(--win-x) - var(--win-w) - 2.4%);
  transform-origin: right center;
}

.curtain--r::after {
  background: linear-gradient(270deg, rgba(255, 255, 255, 0.22), transparent 40%),
    linear-gradient(90deg, rgba(58, 42, 28, 0.22), transparent 26%);
}

/* ============================================================================
   Спальня
   ============================================================================ */

/* Кровать и тумбочка делят один viewBox и один CSS-бокс: наложение двух
   прозрачных SVG даёт прежний кадр без арифметики координат. --i задаёт
   порядок ухода: сначала мелочь (тумбочка), потом крупное. */
.bed,
.bed-side {
  position: absolute;
  right: -1%;
  bottom: 0;
  width: 52%;
  height: auto;
  display: block;
}

.bed {
  --i: 3;
  filter: drop-shadow(0 -8px 22px rgba(40, 28, 16, 0.16));
}

.bed-side {
  --i: 2;
  filter: drop-shadow(0 -6px 16px rgba(40, 28, 16, 0.14));
}

/* Комод и картина занимают простенок между окном (правый край 26%) и
   кроватью с тумбочкой (левый край 49%). Крупное уходит из кадра последним,
   поэтому --i у комода и картины меньше, чем у кровати. */
.dresser {
  --i: 1;
  position: absolute;
  left: 27%;
  bottom: 0;
  width: 15%;
  height: auto;
  display: block;
  filter: drop-shadow(0 -6px 16px rgba(40, 28, 16, 0.16));
}

.bed-art {
  --i: 0;
  position: absolute;
  left: 29.5%;
  bottom: 46%;
  width: 10.5%;
  height: auto;
  display: block;
  filter: drop-shadow(0 6px 14px rgba(40, 28, 16, 0.18));
}

/* На узком экране кровать разъезжается на 96% ширины и накрывает простенок
   целиком — комоду с картиной там просто негде стоять. */
@media (max-width: 900px) {
  .dresser,
  .bed-art {
    display: none;
  }
}

.wall[data-room="bedroom"] .room__night {
  background:
    linear-gradient(
      180deg,
      rgba(8, 12, 30, 0.96) 0%,
      rgba(18, 16, 28, 0.9) 45%,
      rgba(30, 22, 22, 0.78) 100%
    );
}

.wall[data-room="bedroom"] .room__day {
  background:
    radial-gradient(85% 95% at 8% 18%, rgba(210, 228, 255, 0.55), transparent 62%),
    radial-gradient(55% 35% at 12% 88%, rgba(180, 200, 230, 0.22), transparent 70%);
}

.wall[data-room="bedroom"] .room__main {
  background: radial-gradient(
    95% 75% at 50% -8%,
    rgba(255, 236, 210, 0.72),
    rgba(255, 220, 180, 0.28) 38%,
    transparent 68%
  );
}

.wall[data-room="bedroom"] .room__cove::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 5px;
  background: linear-gradient(
    90deg,
    rgba(255, 210, 160, 0.15),
    rgba(255, 230, 190, 0.95) 22%,
    rgba(255, 235, 200, 0.98) 50%,
    rgba(255, 230, 190, 0.95) 78%,
    rgba(255, 210, 160, 0.15)
  );
  filter: blur(1.5px);
  box-shadow: 0 0 18px rgba(255, 210, 150, 0.55);
}

.wall[data-room="bedroom"] .room__cove {
  background: linear-gradient(
    180deg,
    rgba(255, 216, 166, 0.6) 0%,
    rgba(255, 202, 148, 0.3) 16%,
    rgba(255, 196, 140, 0.1) 38%,
    transparent 62%
  );
}

/* Спальня: тёплая стена, изголовье в тени, откос и пол реагируют на свет. */
.wall[data-room="bedroom"] .wall__face {
  background-color: #ddd2c4;
  background-image: radial-gradient(
      110% 65% at 18% 10%,
      rgba(255, 248, 238, 0.4),
      transparent 55%
    ),
    radial-gradient(80% 50% at 88% 82%, rgba(60, 42, 28, 0.08), transparent 58%);
}


/* Панель поднята под потолок: ниже её живёт кровать, и на реальной стене
   выключатель тоже висит над изголовьем, а не за ним. */
.wall[data-room="bedroom"] .wall__devices {
  align-items: flex-start;
}

.wall__set[data-room="bedroom"] {
  --device: calc(var(--device-base) * 0.92);
}

.wall[data-room="bedroom"] .combo .gang__plate {
  filter: brightness(calc(0.7 + 0.3 * var(--lit)));
}

.wall[data-room="bedroom"] .gang__row.is-on {
  text-shadow: 0 0 calc(6px + 4px * (1 - var(--lit))) rgba(220, 230, 255, 0.95);
}

.wall[data-room="bedroom"] .gang__row.is-on .gang__mid .gang__ico {
  filter: drop-shadow(0 0 calc(3px + 3px * (1 - var(--lit))) rgba(220, 230, 255, 0.95));
}

/* ============================================================================
   Гостиная

   Слои света лежат внутри сцены, поверх предметов, и берут непрозрачность
   прямо из переменных. Это отвязывает комнату от syncLights(): чтобы «Кино»
   заработало, механике достаточно поставить одно число.
   ============================================================================ */

/* Производные величины считаются на самой стене, а не в сцене: их одинаково
   ждут и слои света внутри комнаты, и откос с рамкой снаружи неё. */
.wall {
  --l-lamp: 0;
  --l-tv: 0;

  /* Глаз адаптируется к темноте, поэтому лента на 30% видна сильнее, чем
     треть от максимума. В main.js это pow(v, 0.55); в CSS степени нет, и
     кривая v * (1.75 - 0.75v) повторяет её с точностью до сотых. */
  --seen-main: calc(var(--l-main) * (1.75 - 0.75 * var(--l-main)));
  --seen-amb: calc(var(--l-amb) * (1.75 - 0.75 * var(--l-amb)));
  --seen-lamp: calc(var(--l-lamp) * (1.75 - 0.75 * var(--l-lamp)));

  /* Насколько комната освещена в целом: тьма отступает по самому сильному
     источнику, а не по их сумме. */
  --room-lit: min(
    1,
    max(
      var(--l-day) * 1.7,
      var(--l-main),
      var(--l-amb) * 0.4,
      var(--l-lamp) * 0.45,
      var(--l-tv) * 0.18
    )
  );
}

.lit {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

/* Темнота лежит первой, под источниками: свет пробивает её, а не ложится
   поверх серого. */
.lit--dark {
  opacity: calc((1 - var(--room-lit)) * 0.94);
  background: linear-gradient(
    180deg,
    rgba(6, 9, 24, 0.98) 0%,
    rgba(13, 12, 24, 0.94) 45%,
    rgba(24, 18, 20, 0.86) 100%
  );
}

/* День приходит слева, из окна, и отбивается от пола. */
.lit--day {
  opacity: var(--l-day);
  background:
    radial-gradient(76% 95% at 13% 20%, rgba(208, 227, 255, 0.58), transparent 62%),
    radial-gradient(50% 34% at 16% 90%, rgba(178, 199, 230, 0.24), transparent 70%);
}

.lit--main {
  opacity: var(--seen-main);
  background: radial-gradient(
    100% 78% at 50% -10%,
    rgba(255, 238, 214, 0.74),
    rgba(255, 222, 186, 0.3) 40%,
    transparent 70%
  );
}

/* Подсветка карниза: узкая полоса под потолком плюс мягкий скат по стене. */
.lit--cove {
  opacity: var(--seen-amb);
  background: linear-gradient(
    180deg,
    rgba(255, 216, 166, 0.58) 0%,
    rgba(255, 202, 148, 0.28) 16%,
    rgba(255, 196, 140, 0.1) 38%,
    transparent 62%
  );
}

.lit--cove::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 5px;
  background: linear-gradient(
    90deg,
    rgba(255, 210, 160, 0.15),
    rgba(255, 230, 190, 0.95) 22%,
    rgba(255, 235, 200, 0.98) 50%,
    rgba(255, 230, 190, 0.95) 78%,
    rgba(255, 210, 160, 0.15)
  );
  filter: blur(1.5px);
  box-shadow: 0 0 18px rgba(255, 210, 150, 0.55);
}

/* Торшер стоит справа от дивана — пятно привязано к его абажуру. */
.lit--lamp {
  opacity: var(--seen-lamp);
  background: radial-gradient(
    30% 40% at 83% 33%,
    rgba(255, 216, 154, 0.9),
    rgba(255, 202, 138, 0.34) 42%,
    transparent 72%
  );
}

/* Телевизор висит за спиной у зрителя, поэтому в кадре не он, а его отсвет:
   холодная волна снизу и блик на спинке дивана. */
.lit--tv {
  opacity: var(--l-tv);
  background:
    radial-gradient(96% 46% at 50% 114%, rgba(140, 182, 255, 0.34), transparent 64%),
    radial-gradient(46% 22% at 52% 78%, rgba(178, 208, 255, 0.2), transparent 70%);
}

@media (prefers-reduced-motion: no-preference) {
  .wall[data-scene="movie"] .lit--tv {
    animation: tv-flicker 5.5s ease-in-out infinite;
  }
}

/* Кадр меняется — меняется и заливка комнаты. Дыхание слабое: заметно на
   стене, но не мигает в глаза. */
@keyframes tv-flicker {
  0%,
  100% {
    opacity: calc(var(--l-tv) * 0.86);
  }
  17% {
    opacity: var(--l-tv);
  }
  38% {
    opacity: calc(var(--l-tv) * 0.72);
  }
  61% {
    opacity: calc(var(--l-tv) * 0.95);
  }
  80% {
    opacity: calc(var(--l-tv) * 0.78);
  }
}

/* ---------- кино ---------- */

/* Тёплый карниз спорил с холодным отсветом экрана: в кадре оказывались два
   источника разной температуры, и комната переставала читаться как затемнённая.
   Уводим подсветку в голубое — она подхватывает экран, а не спорит с ним.
   Заодно подняли --l-amb сцены с 0.14 до 0.24: на прежней силе было не понять,
   включилась подсветка или нет, а сцена обещает, что включилась. Выше не идём —
   в затемнённой комнате карниз не должен спорить с экраном. */
.wall[data-scene="movie"] .lit--cove {
  background: linear-gradient(
    180deg,
    rgba(126, 180, 255, 0.6) 0%,
    rgba(104, 156, 242, 0.26) 16%,
    rgba(92, 134, 226, 0.09) 38%,
    transparent 62%
  );
}

.wall[data-scene="movie"] .lit--cove::before {
  background: linear-gradient(
    90deg,
    rgba(134, 188, 255, 0.16),
    rgba(168, 212, 255, 0.92) 22%,
    rgba(190, 226, 255, 0.96) 50%,
    rgba(168, 212, 255, 0.92) 78%,
    rgba(134, 188, 255, 0.16)
  );
  box-shadow: 0 0 18px rgba(120, 176, 255, 0.5);
}

/* ---------- вечеринка ---------- */

/* Вечеринка — единственная сцена с чужим цветом. Одного подкрашенного карниза
   ей мало: неподвижный цвет читается как «в комнате поменяли лампочку». Тут
   работает не насыщенность, а движение, поэтому по стене бродят цветные пятна
   света. Стена и мебель под ними остаются прежними — светит свет, а не
   перекрашивается комната. */
.wall[data-scene="party"] .lit--cove {
  background: linear-gradient(
    180deg,
    rgba(226, 132, 196, 0.42) 0%,
    rgba(150, 130, 226, 0.2) 16%,
    rgba(110, 160, 226, 0.07) 34%,
    transparent 56%
  );
}

.wall[data-scene="party"] .lit--cove::before {
  background: linear-gradient(
    90deg,
    rgba(238, 140, 200, 0.2),
    rgba(240, 120, 190, 0.95) 20%,
    rgba(150, 130, 240, 0.95) 52%,
    rgba(110, 190, 240, 0.95) 82%,
    rgba(110, 190, 240, 0.2)
  );
  box-shadow: 0 0 20px rgba(200, 130, 230, 0.55);
}

/* Карниз не стоит на одном цвете, а гуляет от розового к синему. Поворот тона
   ограничен: полный круг увёл бы подсветку в жёлтый и зелёный, а это уже не
   вечеринка, а гирлянда. */
@keyframes party-hue {
  0%,
  100% {
    filter: hue-rotate(-26deg);
  }
  50% {
    filter: hue-rotate(44deg);
  }
}

/* Свет складывается с картинкой, а не закрашивает её: на обычной прозрачности
   пятно затягивает комнату ровной серо-сиреневой плёнкой, а на screen оно
   высветляет то, на что попало, и растворяется на тёмном. */
.lit--disco {
  opacity: 0;
  overflow: hidden;
  mix-blend-mode: screen;
}

.wall[data-scene="party"] .lit--disco {
  opacity: 1;
}

/* Пятно света, а не луч. Клин с чёткими краями читался как нарисованный
   треугольник поверх комнаты: у него была геометрия, которой у света в жилой
   комнате нет. Мягкий круг ведёт себя честнее — виден только след на стене,
   а откуда он взялся, зритель додумывает сам.

   Смещение задаём в процентах от самого пятна: они считаются от его размера,
   а он привязан к ширине сцены — значит один и тот же путь работает и на
   телефоне, и на большом экране. */
.disco__spot {
  position: absolute;
  left: 0;
  top: 0;
  width: 46%;
  aspect-ratio: 1;
  border-radius: 50%;
  background: radial-gradient(
    closest-side,
    rgb(var(--glow) / 0.85),
    rgb(var(--glow) / 0.34) 46%,
    rgb(var(--glow) / 0.08) 72%,
    transparent 88%
  );
  filter: blur(clamp(12px, 2.4vw, 38px));
  will-change: transform, opacity;
}

/* Три пятна разного размера и цвета: сходясь, они дают на стене четвёртый цвет,
   а расходясь — оставляют её тёмной. Ради этого и вся затея. Смещение прописано
   и в покое: при выключенной анимации пятна иначе слиплись бы в одной точке в
   углу сцены. */
.disco__spot--a {
  --glow: 246 62 200;
  transform: translate(-6%, 0%);
}

.disco__spot--b {
  --glow: 58 156 255;
  width: 38%;
  transform: translate(120%, 20%);
}

.disco__spot--c {
  --glow: 150 96 255;
  width: 30%;
  transform: translate(90%, 90%);
}

/* Пятно не бегает по прямой и не держит одну яркость: оно то наплывает ближе,
   то уходит вглубь. Длительности взяты несоразмерными, чтобы три пути не
   сходились в общую петлю и рисунок не начал повторяться на глазах. */
@keyframes disco-spot-a {
  0%,
  100% {
    transform: translate(-24%, 4%) scale(1);
    opacity: 0.72;
  }
  27% {
    transform: translate(28%, -12%) scale(1.18);
    opacity: 1;
  }
  53% {
    transform: translate(104%, 10%) scale(0.9);
    opacity: 0.6;
  }
  78% {
    transform: translate(52%, 26%) scale(1.1);
    opacity: 0.92;
  }
}

@keyframes disco-spot-b {
  0%,
  100% {
    transform: translate(148%, 18%) scale(1.05);
    opacity: 0.9;
  }
  24% {
    transform: translate(64%, 40%) scale(0.86);
    opacity: 0.62;
  }
  58% {
    transform: translate(-16%, -6%) scale(1.22);
    opacity: 1;
  }
  81% {
    transform: translate(72%, -14%) scale(0.96);
    opacity: 0.78;
  }
}

@keyframes disco-spot-c {
  0%,
  100% {
    transform: translate(46%, 62%) scale(1.1);
    opacity: 0.65;
  }
  33% {
    transform: translate(196%, 8%) scale(0.88);
    opacity: 0.95;
  }
  66% {
    transform: translate(8%, 34%) scale(1.3);
    opacity: 0.8;
  }
}

/* Анимации навешены на саму сцену, а не на слой: иначе пятна бродили бы
   круглые сутки под нулевой прозрачностью. */
@media (prefers-reduced-motion: no-preference) {
  .wall[data-scene="party"] .lit--cove {
    animation: party-hue 11s ease-in-out infinite;
  }

  .wall[data-scene="party"] .disco__spot--a {
    animation: disco-spot-a 12.5s ease-in-out infinite;
  }

  .wall[data-scene="party"] .disco__spot--b {
    animation: disco-spot-b 9.3s ease-in-out infinite;
  }

  .wall[data-scene="party"] .disco__spot--c {
    animation: disco-spot-c 16.1s ease-in-out infinite;
  }

}

/* Гостиная: стена чуть холоднее спальни, обратный откос и рамка гаснут вместе
   с комнатой. */
.wall[data-room="living"] {
  /* Комната по умолчанию — светлый день при выключенном свете. Ноль во всех
     источниках дал бы чёрный кадр там, где человек ещё ничего не нажимал. */
  --l-day: 0.55;
}

.wall[data-room="living"] .wall__face {
  background-color: #e4dccd;
}

.wall[data-room="living"] .wall__devices {
  align-items: flex-start;
}

.wall[data-room="living"] .gang__plate,
.wall[data-room="living"] .lcd__bezel {
  filter: brightness(calc(0.72 + 0.28 * var(--room-lit)));
}

/* ---------- предметы гостиной ---------- */

.sofa {
  --i: 2;
  position: absolute;
  left: 29.5%;
  bottom: 0;
  width: 47%;
  height: auto;
  display: block;
  filter: drop-shadow(0 -8px 24px rgba(40, 28, 16, 0.18));
}

.torch {
  --i: 1;
  position: absolute;
  left: 77.5%;
  bottom: 0;
  width: 11%;
  height: auto;
  display: block;
  filter: drop-shadow(0 -6px 18px rgba(40, 28, 16, 0.14));
}

/* Свечение абажура живёт в самой лампе, а не в слое света: так торшер
   остаётся источником, даже когда общий слой уже погашен. */
.torch__glow {
  opacity: var(--seen-lamp);
}

.torch__shade {
  filter: brightness(calc(1 + 0.5 * var(--seen-lamp)));
}

/* Изнанка абажура светлая и при выключенной лампе — это ткань, а не лампочка,
   поэтому она не гаснет в ноль, а только перестаёт светиться. */
.torch__bulb {
  opacity: calc(0.3 + 0.7 * var(--seen-lamp));
}

.plant {
  --i: 0;
  position: absolute;
  left: 88.5%;
  bottom: 0;
  width: 8.5%;
  height: auto;
  display: block;
  filter: drop-shadow(0 -6px 16px rgba(40, 28, 16, 0.16));
}

@media (max-width: 900px) {
  /* На узком экране торшер и цветок съедают диван. Оставляем окно и диван —
     комната читается и без них. */
  .torch,
  .plant {
    display: none;
  }

  .sofa {
    left: 34%;
    width: 58%;
  }
}

/* ============================================================================
   Детская

   Три предмета и четыре клавиши. Лампа живёт на столе, ночник — у изголовья
   (--l-amb). CO₂ с экрана не остаётся цифрой: слой .lit--air подмешивает
   оттенок всей комнате, когда .air уже в warn/bad.
   ============================================================================ */

.wall[data-room="kids"] {
  --l-day: 0.55;
}

.wall[data-room="kids"] .wall__face {
  background-color: #e9dccf;
  background-image: radial-gradient(
      100% 60% at 16% 12%,
      rgba(255, 244, 232, 0.45),
      transparent 55%
    ),
    radial-gradient(70% 48% at 86% 78%, rgba(72, 96, 78, 0.06), transparent 60%);
}

.wall[data-room="kids"] .wall__devices {
  align-items: flex-start;
}

.wall[data-room="kids"] .gang__plate,
.wall[data-room="kids"] .gang__screen {
  filter: brightness(calc(0.72 + 0.28 * var(--room-lit)));
}

/* Лампа на столе, ночник — шар на мобиле. Сдвинешь .desk или .crib —
   сдвинь и эти проценты. */
.wall[data-room="kids"] .lit--lamp {
  background: radial-gradient(
    38% 46% at 38% 65%,
    rgba(255, 216, 154, 0.92),
    rgba(255, 202, 138, 0.36) 44%,
    transparent 74%
  );
}

/* Ночник не карниз и не прожектор: мелкое тёплое пятно у шара, screen
   только добавляет свет, а не красит стену оранжевым кругом. Прозрачность
   слоя — обычная --seen-amb, без множителя: сцена уже держит --l-amb на 0.24. */
.wall[data-room="kids"] .lit--cove {
  mix-blend-mode: screen;
  background: radial-gradient(
    9% 12% at 62% 54%,
    rgba(255, 196, 140, 0.38),
    rgba(255, 168, 110, 0.1) 42%,
    transparent 72%
  );
}

.wall[data-room="kids"] .lit--cove::before {
  content: none;
}

/* Ночник: тёплый минимум. Темнота не синяя, как в «Кино», а янтарная —
   иначе комната читается как гостиная без телевизора, а не как ночь. */
.wall[data-scene="night"] .lit--dark {
  background: linear-gradient(
    180deg,
    rgba(18, 12, 20, 0.96) 0%,
    rgba(28, 16, 18, 0.92) 48%,
    rgba(36, 20, 14, 0.86) 100%
  );
}

/* Воздух комнаты подхватывает шкалу с экрана. warn — тёплый налёт,
   bad — сухой красноватый; без класса слой молчит. */
.lit--air {
  opacity: 0;
  pointer-events: none;
  mix-blend-mode: soft-light;
  background: radial-gradient(
    90% 70% at 50% 40%,
    rgba(228, 178, 60, 0.55),
    rgba(228, 178, 60, 0.12) 55%,
    transparent 78%
  );
}

.wall[data-room="kids"]:has(.air--warn) .lit--air {
  opacity: 0.55;
}

.wall[data-room="kids"]:has(.air--bad) .lit--air {
  opacity: 0.7;
  background: radial-gradient(
    90% 70% at 50% 40%,
    rgba(224, 91, 74, 0.58),
    rgba(224, 91, 74, 0.14) 55%,
    transparent 78%
  );
}

@media (prefers-reduced-motion: no-preference) {
  .wall[data-room="kids"]:has(.air--warn) .lit--air {
    animation: air-breathe 7s ease-in-out infinite;
  }

  .wall[data-room="kids"]:has(.air--bad) .lit--air {
    animation: air-breathe-bad 7s ease-in-out infinite;
  }
}

@keyframes air-breathe {
  0%,
  100% {
    opacity: 0.42;
  }
  50% {
    opacity: 0.62;
  }
}

@keyframes air-breathe-bad {
  0%,
  100% {
    opacity: 0.55;
  }
  50% {
    opacity: 0.78;
  }
}

/* ---------- предметы детской ---------- */

.desk {
  --i: 1;
  position: absolute;
  left: 16%;
  bottom: 0;
  width: 27%;
  height: auto;
  display: block;
  filter: drop-shadow(0 -6px 18px rgba(40, 28, 16, 0.16));
}

.desk__glow {
  opacity: var(--seen-lamp);
}

.desk__shade {
  filter: brightness(calc(1 + 0.5 * var(--seen-lamp)));
}

.desk__bulb {
  opacity: calc(0.3 + 0.7 * var(--seen-lamp));
}

/* Качалка наполовину за кадром: отрицательный left уводит её за левую кромку,
   а обрезает .room со своим overflow. Конь развёрнут вправо, поэтому в кадре
   остаётся голова с гривой — по ней игрушка и читается. Целиком в кадре она
   становилась третьим предметом обстановки и спорила со столом. */
.horse {
  --i: 0;
  position: absolute;
  left: -6%;
  bottom: 0;
  width: 15%;
  height: auto;
  display: block;
  filter: drop-shadow(0 -5px 14px rgba(40, 28, 16, 0.16));
}

.crib {
  --i: 2;
  position: absolute;
  left: 55%;
  bottom: 0;
  width: 40%;
  height: auto;
  display: block;
  filter: drop-shadow(0 -8px 22px rgba(40, 28, 16, 0.18));
}

.crib__night {
  opacity: var(--seen-amb);
}

/* Шар мобиля светится сам: он остаётся источником, даже когда общий слой
   уже погашен, — как абажур торшера в гостиной. */
.crib__bulb {
  filter: brightness(calc(1 + 0.55 * var(--seen-amb)));
}

@media (max-width: 900px) {
  /* Стол и колыбель занимают всю узкую сцену вплотную, и обрезанной качалке
     остаётся налезать на стол. Намёк, который не читается, — просто мусор. */
  .horse {
    display: none;
  }

  .desk {
    left: 4%;
    width: 34%;
  }

  .crib {
    left: 52%;
    width: 47%;
  }

  .wall[data-room="kids"] .lit--lamp {
    background: radial-gradient(
      36% 44% at 28% 66%,
      rgba(255, 216, 154, 0.92),
      rgba(255, 202, 138, 0.36) 44%,
      transparent 74%
    );
  }

  .wall[data-room="kids"] .lit--cove {
    background: radial-gradient(
      11% 14% at 61% 54%,
      rgba(255, 196, 140, 0.38),
      rgba(255, 168, 110, 0.1) 42%,
      transparent 72%
    );
  }
}

/* ============================================================================
   Гараж

   Бетонная стена, секционные ворота вместо окна, верстак, доска с
   инструментами и красный ящик.
   ============================================================================ */

.wall[data-room="garage"] {
  --l-day: 0.4;
}

.wall[data-room="garage"] .wall__face {
  background-color: #8a8a84;
  background-image:
    repeating-linear-gradient(
      0deg,
      transparent,
      transparent 47px,
      rgba(0, 0, 0, 0.06) 47px,
      rgba(0, 0, 0, 0.06) 48px
    ),
    repeating-linear-gradient(
      90deg,
      transparent,
      transparent 95px,
      rgba(0, 0, 0, 0.04) 95px,
      rgba(0, 0, 0, 0.04) 96px
    ),
    radial-gradient(120% 70% at 30% 0%, rgba(255, 255, 255, 0.08), transparent 55%),
    radial-gradient(80% 60% at 90% 90%, rgba(0, 0, 0, 0.1), transparent 60%);
}

/* Пол и плинтус тоже бетонные. Пока дверь висела над плинтусом, тёплые
   половицы под серой стеной ещё сходили за край кадра; теперь дверь садится
   ровно на эту линию, и стык бетона с паркетом стоит в упор. */
.wall[data-room="garage"] .wall__skirting {
  background: linear-gradient(#6f6f69, #5b5b55);
  border-top-color: rgba(255, 255, 255, 0.14);
  box-shadow: 0 -1px 0 rgba(0, 0, 0, 0.22);
}

.wall[data-room="garage"] .wall__skirting::after {
  background: #494943;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
}

.wall[data-room="garage"] .wall__floor {
  background: linear-gradient(180deg, #605f59 0%, #4e4e48 55%, #43433d 100%);
  box-shadow: inset 0 8px 12px rgba(0, 0, 0, 0.3);
}

.wall[data-room="garage"] .wall__devices {
  align-items: flex-start;
}

.wall[data-room="garage"] .gang__plate,
.wall[data-room="garage"] .lcd__bezel {
  filter: brightness(calc(0.6 + 0.4 * var(--room-lit)));
}

/* Флуоресцентный верхний свет — холоднее жилых комнат */
.wall[data-room="garage"] .lit--main {
  background: radial-gradient(
    110% 80% at 50% -12%,
    rgba(220, 235, 255, 0.68),
    rgba(200, 220, 255, 0.22) 42%,
    transparent 72%
  );
}

/* Дневной свет — снизу ворот, не сверху окна */
.wall[data-room="garage"] .lit--day {
  opacity: var(--l-day);
  background:
    linear-gradient(
      0deg,
      rgba(180, 210, 255, 0.45) 0%,
      rgba(160, 195, 240, 0.18) 28%,
      transparent 55%
    ),
    radial-gradient(90% 50% at 14% 85%, rgba(160, 195, 240, 0.2), transparent 60%);
}

/* Прячем стандартное окно и шторы */
.wall[data-room="garage"] .win,
.wall[data-room="garage"] .win__rod,
.wall[data-room="garage"] .curtain {
  display: none;
}

/* ---------- секционные ворота ---------- */

/* Дверь занимает ровно тот прямоугольник, в котором в жилых комнатах стоит
   окно: те же --win-*, тот же уход за левую кромку, тот же низ на линии пола.
   Проём один и тот же — меняется только то, чем он закрыт. */
.gar-door {
  position: absolute;
  left: var(--win-x);
  width: var(--win-w);
  top: var(--win-t);
  bottom: var(--win-b);
  border: 5px solid #b0aca2;
  border-radius: 2px;
  overflow: hidden;
  background: #ddd9d1;
  box-shadow:
    0 12px 28px rgba(30, 26, 20, 0.3),
    inset 0 0 0 1px rgba(255, 255, 255, 0.5);
}

/* Полотно едет одним куском, поэтому секции лежат в нём, а не в самой двери:
   увозить пять секций порознь пришлось бы пятью согласованными переходами.
   Секции делят высоту пополам-по-пятым — колонка на flex избавляет от таблицы
   top/height, и число секций остаётся вопросом разметки. Обрезает уехавшее
   полотно .gar-door со своим overflow. */
.gar-door__leaf {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  z-index: 1;
  /* Закрытое положение задано явно: Safari не анимирует transform, если
     стартовое состояние — «свойства нет». JS пишет конкретный translate3d;
     CSS transition ведёт ход за 2.4 с — как створка на планшете. */
  transform: translate3d(0, 0, 0);
  transition: transform 2.4s cubic-bezier(0.45, 0.05, 0.35, 1);
  will-change: transform;
}

/* Без JS полотно стоит на месте; с JS ходом управляет moveDoor() из main.js.
   Правило оставлено для гипотетического no-JS, но под html:not(.js), чтобы
   не конфликтовать с inline transform, который пишет rAF. */
html:not(.js) .wall[data-gardoor="open"] .gar-door__leaf {
  transform: translate3d(0, -100%, 0);
}

/* Полотно светлое: обычная секционная дверь в тёплый белый. На серой бетонной
   стене она за счёт этого читается прибором, а не проёмом — тёмное полотно
   сливалось со стеной в одно пятно. */
.gar-door__panel {
  position: relative;
  flex: 1;
  background: linear-gradient(180deg, #f2f0ea 0%, #ddd9d1 46%, #e9e6df 100%);
}

/* Стык рисует не каждая секция снизу, а каждая следующая сверху: тогда швов
   ровно на один меньше, чем секций, и крайние края остаются чистыми. Через
   border-bottom + :last-of-type так не выйдет — последний div в двери это
   полоска света, а не секция, и правило село бы мимо. */
.gar-door__panel + .gar-door__panel {
  border-top: 2px solid #b6b1a6;
}

/* Филёнка в каждой секции. На тёмном полотне поверхность держали вертикальные
   рёбра через 46 px, но в светлом тоне они вместе с пятью швами сложились в
   сетку и полотно снова стало читаться переплётом окна — того самого, которое
   из этого проёма и убирали. Утопленный прямоугольник в секции оставляет
   главным горизонтальный ритм, по нему дверь и узнаётся секционной. */
.gar-door__panel::after {
  content: "";
  position: absolute;
  inset: 15% 7%;
  border-radius: 2px;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.9),
    inset 0 -1px 0 rgba(120, 112, 98, 0.2),
    0 0 0 1px rgba(120, 112, 98, 0.13);
}

.gar-door__panel::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 3px;
  background: linear-gradient(
    90deg,
    transparent,
    rgba(255, 255, 255, 0.7) 30%,
    rgba(255, 255, 255, 0.85) 50%,
    rgba(255, 255, 255, 0.7) 70%,
    transparent
  );
}

/* ---------- двор за воротами ---------- */

/* Лежит под полотном и виден только когда оно уехало. Само содержимое —
   рисунком в разметке (.gar-door__view): светлая абстракция из мягких пятен.
   До неё тут перебрали два подхода: полосы градиентов, где «брусчатка» из
   repeating-linear-gradient читалась штриховкой поперёк проёма, и двор с
   деревом — узнаваемые предметы на этом размере выходили грубыми. */
.gar-door__out {
  position: absolute;
  inset: 0;
  overflow: hidden;
  background: #c9dcee;
}

.gar-door__view {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

/* Полоска света под закрытым полотном. Когда ворота открыты, светится весь
   проём, и подсказывать щелью больше нечего. */
.gar-door__shadow {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 10px;
  z-index: 2;
  /* Обычная заливка, без mix-blend-mode: screen. На тёмном полотне «экран»
     высветлял щель и был единственным способом её показать, на светлом же он
     почти ничего не меняет — светлее белого не бывает. */
  background: linear-gradient(
    0deg,
    rgba(198, 224, 255, 0.95),
    rgba(198, 224, 255, 0)
  );
  opacity: var(--l-day, 0.4);
  transition: opacity 0.4s ease;
}

.wall[data-gardoor="open"] .gar-door__shadow {
  opacity: 0;
}

/* Свет из открытого проёма: пятно у самих ворот и разлив по полу вправо.
   Ползёт вместе с полотном, поэтому переход длится столько же — иначе свет
   приходил бы в комнату раньше, чем поднялась створка. */
/* Держится у самого проёма. Широкая полоса по низу кадра сюда не годится:
   на ней вся комната уходила в голубую дымку, и верстак с ящиком выцветали —
   свет читался не как пятно из ворот, а как выключенная насыщенность. */
.lit--gar-open {
  opacity: 0;
  background:
    radial-gradient(30% 52% at 6% 56%, rgba(206, 228, 255, 0.4), transparent 72%),
    radial-gradient(44% 22% at 15% 95%, rgba(190, 214, 246, 0.26), transparent 74%);
}

.wall[data-gardoor="open"] .lit--gar-open {
  opacity: 1;
}

@media (prefers-reduced-motion: no-preference) {
  .lit--gar-open {
    transition: opacity 2.4s ease;
  }
}

/* ---------- предметы гаража ---------- */

.room__stage--garage .furn {
  --n: 3;
}

/* Верстак — правая часть стены */
.workbench {
  --i: 2;
  position: absolute;
  right: 0;
  bottom: 0;
  width: 55%;
  height: auto;
  display: block;
  filter: drop-shadow(0 -8px 22px rgba(0, 0, 0, 0.32));
}

/* Доска висит на стене над верстаком. На 30% её низ уходил на 79 px ниже
   столешницы (она на 40.6% от низа сцены), и доска читалась не повешенной, а
   поставленной на стол. Держим низ заметно выше столешницы; по горизонтали
   доска правее планшета, так что поднимать её ничего не мешает. */
.toolboard {
  --i: 1;
  position: absolute;
  right: 7%;
  bottom: 50%;
  width: 22%;
  height: auto;
  display: block;
  filter: drop-shadow(0 6px 16px rgba(0, 0, 0, 0.4));
}

/* Красный ящик с инструментами левее верстака */
.toolbox {
  --i: 0;
  position: absolute;
  left: 29%;
  bottom: 0;
  width: 11%;
  height: auto;
  display: block;
  filter: drop-shadow(0 -6px 18px rgba(0, 0, 0, 0.28));
}

@media (max-width: 900px) {
  .toolboard {
    display: none;
  }

  .workbench {
    width: 68%;
  }

  .toolbox {
    left: 24%;
    width: 14%;
  }
}


/* ============================================================================
   Сауна

   Предбанник: тёплое дерево на стенах, лавка с полотенцами, шайка и веник.
   Вместо окна — стеклянная дверь в парную, за ней полок и печь с камнями.
   ============================================================================ */

/* Дневного света в предбаннике нет — за дверью парная, а не улица. Но и чёрным
   кадр быть не должен: держим ровный тёплый фон, поверх которого работают
   «Свет» и «Подсветка». */
.wall[data-room="sauna"] {
  --l-day: 0.34;
}

/* Стены обшиты доской вертикально, как в настоящей парилке. Полосы редкие и
   слабые: на частой штриховке стена начинает рябить. */
.wall[data-room="sauna"] .wall__face {
  background-color: #c8a274;
  background-image:
    repeating-linear-gradient(
      90deg,
      transparent,
      transparent 62px,
      rgba(92, 58, 26, 0.13) 62px,
      rgba(92, 58, 26, 0.13) 64px
    ),
    radial-gradient(120% 70% at 26% 0%, rgba(255, 240, 214, 0.5), transparent 58%),
    radial-gradient(80% 60% at 92% 92%, rgba(80, 46, 16, 0.16), transparent 60%);
}

.wall[data-room="sauna"] .wall__skirting {
  background: linear-gradient(#b98f61, #a67c50);
  border-top-color: rgba(255, 236, 208, 0.4);
}

.wall[data-room="sauna"] .wall__skirting::after {
  background: #8e6740;
}

.wall[data-room="sauna"] .wall__devices {
  align-items: flex-start;
}

.wall[data-room="sauna"] .gang__plate,
.wall[data-room="sauna"] .lcd__bezel {
  filter: brightness(calc(0.72 + 0.28 * var(--room-lit)));
}

/* Верхний свет предбанника — тёплый, без холодной ноты жилых комнат. */
.wall[data-room="sauna"] .lit--main {
  background: radial-gradient(
    100% 78% at 50% -10%,
    rgba(255, 232, 190, 0.72),
    rgba(255, 214, 158, 0.3) 40%,
    transparent 70%
  );
}

/* «Подсветка»: свет от LED-ленты под потолком (см. .sau-led). Скат идёт сверху
   вниз — источник виден в кадре, и свет должен приходить именно от него, иначе
   лента горит в одном месте, а светит в другом. */
.lit--sau-amb {
  opacity: var(--seen-amb);
  background: linear-gradient(
    180deg,
    rgba(255, 198, 130, 0.5) 0%,
    rgba(255, 184, 116, 0.26) 18%,
    rgba(255, 176, 104, 0.1) 40%,
    transparent 64%
  );
}

/* ---------- LED-лента ---------- */

/* Неоновая лента под потолком: непрерывная светящаяся трубка, а не строчка
   диодов. Погашенная читается матовым силиконом на стене — подсветку должно
   быть видно и выключенной, иначе клавиша включает свет из ниоткуда. */
.sau-led {
  position: absolute;
  left: 4%;
  right: 4%;
  top: 2.4%;
  height: 0.8%;
  min-height: 5px;
  border-radius: 999px;
  background: linear-gradient(180deg, #928a7d 0%, #6f6759 58%, #575044 100%);
  box-shadow: 0 2px 6px rgba(60, 34, 10, 0.3);
}

/* Зажжённая трубка: белое ядро, тёплая оболочка и ореол в четыре слоя — от
   узкого и плотного до широкого и еле заметного. Именно набор слоёв и даёт
   неон: одиночная тень читается просто размытой линией.

   Накладываем прозрачностью, а не сменой цветов: между «выкл» и «вкл» так
   получается плавный переход, а цвета бы щёлкнули. */
.sau-led::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  opacity: var(--seen-amb);
  background: linear-gradient(
    180deg,
    rgba(255, 238, 210, 0.95) 0%,
    #fff 44%,
    rgba(255, 200, 132, 0.95) 100%
  );
  box-shadow:
    0 0 4px 1px rgba(255, 232, 190, 0.95),
    0 0 14px 3px rgba(255, 188, 108, 0.85),
    0 0 38px 10px rgba(255, 158, 72, 0.55),
    0 0 84px 28px rgba(255, 142, 58, 0.3);
}

@media (prefers-reduced-motion: no-preference) {
  /* Коротко: при диммировании уровень меняется каждые 40 мс, и на прежних
     0.45s лента тянулась за ручкой с заметным запозданием. Диффузные слои
     света это переживают, а трубка — крайний предмет, глаз следит за ней. */
  .sau-led::after {
    transition: opacity 0.14s linear;
  }
}

/* ---------- стеклянная дверь в парную ---------- */

/* Тот же проём, что окно в жилых комнатах и подъёмная дверь в гараже. */
.sau-door {
  position: absolute;
  left: var(--win-x);
  width: var(--win-w);
  top: var(--win-t);
  bottom: var(--win-b);
  overflow: hidden;
  border: 7px solid #8a6237;
  border-radius: 3px;
  background: #4a3220;
  box-shadow:
    0 14px 30px rgba(60, 34, 10, 0.34),
    inset 0 0 0 1px rgba(255, 226, 190, 0.22);
}

/* Рисунок за стеклом размыт и притушен — отсюда «угадывается». Чёткая
   картинка читалась бы дырой в стене, а не дверью. */
.sau-door__in {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  /* Яркость идёт от --l-amb: клавиша «Подсветка» зажигает свет в самой парной,
     и за стеклом это видно. Иначе клавиша меняла бы только пятно на стене
     предбанника, а обещает она свет там, внутри. */
  filter: blur(2.2px) saturate(0.86)
    brightness(calc(0.78 + 0.55 * var(--seen-amb)));
  opacity: 0.9;
}

/* Само стекло: холодный отблеск наискось и лёгкая матовость. Оно поверх
   картинки, поэтому и делает её стеклом. */
.sau-door__glass {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(114deg, rgba(255, 255, 255, 0.16) 0 18%, transparent 18% 34%,
      rgba(255, 255, 255, 0.1) 34% 44%, transparent 44%),
    linear-gradient(180deg, rgba(214, 236, 244, 0.2), rgba(140, 170, 186, 0.12) 52%,
      rgba(70, 96, 110, 0.16));
}

.sau-door__handle {
  position: absolute;
  right: 7%;
  top: 38%;
  width: 4%;
  min-width: 5px;
  height: 22%;
  border-radius: 999px;
  background: linear-gradient(90deg, #d8c6a8, #9d8460);
  box-shadow: 0 2px 6px rgba(40, 24, 8, 0.45);
}

/* ---------- пар ---------- */

/* Клубы живут всегда, но их не видно, пока не нажали «Пар»: держать их за
   display: none нельзя — анимация должна начинаться с уже разложенного слоя,
   иначе первый клуб выпрыгивает из угла. */
.sau-door__steam {
  position: absolute;
  inset: 0;
  opacity: 0;
  overflow: hidden;
  mix-blend-mode: screen;
}

/* Клуб висит у потолка: пар подаёт форсунка сверху, и вниз он валит, а не
   поднимается с пола. */
.sau-door__steam i {
  position: absolute;
  /* Отступ считается от высоты проёма, а не облачка. На прежних -18% мелкое
     облачко (7% высоты проёма) целиком оставалось над кромкой и в кадр не
     попадало вовсе. */
  top: -5%;
  aspect-ratio: 1;
  border-radius: 50%;
  /* Ядро в полную белизну и более собранный край: на прежней заливке клуб
     расплывался ровной дымкой, а пар должен читаться комками. */
  background: radial-gradient(
    closest-side,
    rgba(255, 255, 255, 1),
    rgba(255, 253, 250, 0.78) 34%,
    rgba(255, 250, 244, 0.32) 62%,
    transparent 82%
  );
  filter: blur(4px);
}

/* Клубы держатся правее 28%: левая четверть двери уходит за кромку кадра, и
   всё, что стоит левее, зритель просто не увидит. */
/* Размеры и места врассыпную: от мелких облачков в пятую часть проёма до пары
   крупных. Одинаковые клубы сливаются в одно светлое пятно, а разнокалиберные
   читаются паром — и вместе всё равно накрывают парную целиком. Все держатся
   правее 26%: левая четверть двери уходит за кромку кадра.

   --fall — ход вниз, свой у каждого облачка. Проценты в translate считаются от
   размера самого элемента, поэтому одно и то же число даёт крупному облачку
   долгий путь, а мелкому — топтание на месте. Число подобрано так, чтобы все
   проходили примерно одинаковую долю проёма: 9310 / ширину в процентах. */
.sau-door__steam i:nth-child(1) { left: 30%; width: 58%; --fall: 161%; }
.sau-door__steam i:nth-child(2) { left: 52%; width: 30%; --fall: 310%; }
.sau-door__steam i:nth-child(3) { left: 66%; width: 24%; --fall: 388%; }
.sau-door__steam i:nth-child(4) { left: 34%; width: 40%; --fall: 233%; }
.sau-door__steam i:nth-child(5) { left: 58%; width: 34%; --fall: 274%; }
.sau-door__steam i:nth-child(6) { left: 44%; width: 22%; --fall: 423%; }
.sau-door__steam i:nth-child(7) { left: 70%; width: 30%; --fall: 310%; }
.sau-door__steam i:nth-child(8) { left: 28%; width: 34%; --fall: 274%; }
.sau-door__steam i:nth-child(9) { left: 48%; width: 44%; --fall: 212%; }
.sau-door__steam i:nth-child(10) { left: 62%; width: 20%; --fall: 466%; }
.sau-door__steam i:nth-child(11) { left: 38%; width: 26%; --fall: 358%; }
.sau-door__steam i:nth-child(12) { left: 54%; width: 62%; --fall: 150%; }

.wall[data-steam="on"] .sau-door__steam {
  opacity: 1;
}

/* Пар подмешивает белёсую дымку и самому стеклу: запотевает вся дверь, а не
   только те места, где прошёл клуб. */
.wall[data-steam="on"] .sau-door__glass {
  background:
    linear-gradient(114deg, rgba(255, 255, 255, 0.2) 0 18%, transparent 18% 34%,
      rgba(255, 255, 255, 0.13) 34% 44%, transparent 44%),
    linear-gradient(180deg, rgba(238, 246, 248, 0.2), rgba(206, 224, 230, 0.15) 52%,
      rgba(150, 176, 186, 0.18));
}

/* Пар — не декор, а содержание: это и есть подача пара, показанная
   картинкой. Поэтому клубы идут и при prefers-reduced-motion, в отличие
   от зума и параллакса. Проход всё равно один, петли нет. */
.sau-door__steam,
.sau-door__glass {
  transition: opacity 0.7s ease, background 0.7s ease;
}

/* Один проход, а не петля: пар подают разово. Каждое облачко вспыхивает
   почти мгновенно и потом долго расходится — отсюда несимметричная кривая.
   Задержки не выстроены в ряд: первая горсть появляется почти разом, а
   несколько облачков подходят позже, до полутора секунд, — от этого подача
   и выглядит живой, а не залпом из двенадцати одинаковых шаров.

   Шорткат пишем целиком на каждом облачке и с приставкой .wall[data-steam]:
   он сбрасывает animation-delay в ноль и перебивает по специфичности более
   короткий селектор — на этом разбег фаз уже один раз терялся. */
.wall[data-steam="on"] .sau-door__steam i:nth-child(1) {
  animation: sau-puff-1 5.6s cubic-bezier(0.08, 0.7, 0.25, 1) 0s 1 both;
}

.wall[data-steam="on"] .sau-door__steam i:nth-child(2) {
  animation: sau-puff-3 4.2s cubic-bezier(0.08, 0.7, 0.25, 1) 0.1s 1 both;
}

.wall[data-steam="on"] .sau-door__steam i:nth-child(3) {
  animation: sau-puff-5 3.8s cubic-bezier(0.08, 0.7, 0.25, 1) 0.06s 1 both;
}

.wall[data-steam="on"] .sau-door__steam i:nth-child(4) {
  animation: sau-puff-2 5.1s cubic-bezier(0.08, 0.7, 0.25, 1) 0.22s 1 both;
}

.wall[data-steam="on"] .sau-door__steam i:nth-child(5) {
  animation: sau-puff-6 4.6s cubic-bezier(0.08, 0.7, 0.25, 1) 0.14s 1 both;
}

.wall[data-steam="on"] .sau-door__steam i:nth-child(6) {
  animation: sau-puff-4 3.5s cubic-bezier(0.08, 0.7, 0.25, 1) 0.34s 1 both;
}

.wall[data-steam="on"] .sau-door__steam i:nth-child(7) {
  animation: sau-puff-1 4.9s cubic-bezier(0.08, 0.7, 0.25, 1) 0.46s 1 both;
}

.wall[data-steam="on"] .sau-door__steam i:nth-child(8) {
  animation: sau-puff-5 4.1s cubic-bezier(0.08, 0.7, 0.25, 1) 0.62s 1 both;
}

.wall[data-steam="on"] .sau-door__steam i:nth-child(9) {
  animation: sau-puff-2 5.4s cubic-bezier(0.08, 0.7, 0.25, 1) 0.28s 1 both;
}

.wall[data-steam="on"] .sau-door__steam i:nth-child(10) {
  animation: sau-puff-3 3.4s cubic-bezier(0.08, 0.7, 0.25, 1) 0.86s 1 both;
}

.wall[data-steam="on"] .sau-door__steam i:nth-child(11) {
  animation: sau-puff-6 4.4s cubic-bezier(0.08, 0.7, 0.25, 1) 1.05s 1 both;
}

.wall[data-steam="on"] .sau-door__steam i:nth-child(12) {
  animation: sau-puff-4 5.8s cubic-bezier(0.08, 0.7, 0.25, 1) 0.18s 1 both;
}

/* Шесть траекторий: облачко сносит то вправо, то влево, и по дороге качает —
   промежуточные кадры нарочно уводят его в другую сторону, чтобы путь не был
   прямой линией. Вертикаль берётся из --fall самого облачка, горизонталь и
   масштаб общие: сносит всех примерно одинаково, а спускаются они на свою
   долю проёма. */
@keyframes sau-puff-1 {
  0% {
    transform: translate(-6%, 0) scale(0.50);
    opacity: 0;
  }
  5% {
    opacity: 1;
  }
  30% {
    transform: translate(10%, calc(var(--fall) * 0.26)) scale(1.25);
    opacity: 0.95;
  }
  62% {
    transform: translate(-4%, calc(var(--fall) * 0.6)) scale(1.70);
    opacity: 0.6;
  }
  100% {
    transform: translate(14%, var(--fall)) scale(2.30);
    opacity: 0;
  }
}

@keyframes sau-puff-2 {
  0% {
    transform: translate(5%, 0) scale(0.58);
    opacity: 0;
  }
  5% {
    opacity: 1;
  }
  30% {
    transform: translate(-8%, calc(var(--fall) * 0.26)) scale(1.30);
    opacity: 0.95;
  }
  62% {
    transform: translate(6%, calc(var(--fall) * 0.6)) scale(1.75);
    opacity: 0.6;
  }
  100% {
    transform: translate(-13%, var(--fall)) scale(2.15);
    opacity: 0;
  }
}

@keyframes sau-puff-3 {
  0% {
    transform: translate(-3%, 0) scale(0.44);
    opacity: 0;
  }
  5% {
    opacity: 1;
  }
  30% {
    transform: translate(7%, calc(var(--fall) * 0.26)) scale(1.20);
    opacity: 0.95;
  }
  62% {
    transform: translate(-9%, calc(var(--fall) * 0.6)) scale(1.65);
    opacity: 0.6;
  }
  100% {
    transform: translate(4%, var(--fall)) scale(2.45);
    opacity: 0;
  }
}

@keyframes sau-puff-4 {
  0% {
    transform: translate(8%, 0) scale(0.62);
    opacity: 0;
  }
  5% {
    opacity: 1;
  }
  30% {
    transform: translate(-5%, calc(var(--fall) * 0.26)) scale(1.35);
    opacity: 0.95;
  }
  62% {
    transform: translate(11%, calc(var(--fall) * 0.6)) scale(1.80);
    opacity: 0.6;
  }
  100% {
    transform: translate(-7%, var(--fall)) scale(2.05);
    opacity: 0;
  }
}

@keyframes sau-puff-5 {
  0% {
    transform: translate(-8%, 0) scale(0.40);
    opacity: 0;
  }
  5% {
    opacity: 1;
  }
  30% {
    transform: translate(4%, calc(var(--fall) * 0.26)) scale(1.15);
    opacity: 0.95;
  }
  62% {
    transform: translate(-2%, calc(var(--fall) * 0.6)) scale(1.60);
    opacity: 0.6;
  }
  100% {
    transform: translate(10%, var(--fall)) scale(2.55);
    opacity: 0;
  }
}

@keyframes sau-puff-6 {
  0% {
    transform: translate(2%, 0) scale(0.54);
    opacity: 0;
  }
  5% {
    opacity: 1;
  }
  30% {
    transform: translate(-10%, calc(var(--fall) * 0.26)) scale(1.28);
    opacity: 0.95;
  }
  62% {
    transform: translate(8%, calc(var(--fall) * 0.6)) scale(1.72);
    opacity: 0.6;
  }
  100% {
    transform: translate(-4%, var(--fall)) scale(2.25);
    opacity: 0;
  }
}
/* ---------- предметы предбанника ---------- */

.sau-bench {
  --i: 2;
  position: absolute;
  left: 31%;
  bottom: 0;
  width: 30%;
  height: auto;
  display: block;
  filter: drop-shadow(0 -6px 18px rgba(60, 34, 10, 0.2));
}

.sau-pail {
  --i: 1;
  position: absolute;
  left: 65%;
  bottom: 0;
  width: 9%;
  height: auto;
  display: block;
  filter: drop-shadow(0 -5px 14px rgba(60, 34, 10, 0.2));
}

/* Веник висит на стене, а не стоит на полу: у него bottom считается от низа
   сцены, как у доски инструментов в гараже. */
.sau-broom {
  --i: 0;
  position: absolute;
  left: 78%;
  bottom: 34%;
  width: 7%;
  height: auto;
  display: block;
  filter: drop-shadow(0 6px 14px rgba(60, 34, 10, 0.24));
}

@media (max-width: 900px) {
  /* Узкая сцена: лавка и дверь держат комнату, мелочь только рябит. */
  .sau-pail,
  .sau-broom {
    display: none;
  }

  .sau-bench {
    left: 36%;
    width: 40%;
  }
}


/* ========== chat.css (merged) ========== */

/* ---------------------------------------------------------------------------
   Диалог с домом: тёмная секция, внутри — телефон с перепиской.
   Экран намеренно повторяет светлую тему ассистента: на тёмном фоне он
   читается как живой скриншот, а не как ещё один блок сайта.
   Всё состояние «до анимации» спрятано под html.js — без скриптов переписка
   просто видна целиком.
--------------------------------------------------------------------------- */

.talk {
  display: grid;
  /* Свечение за телефоном выходит далеко за края корпуса и на узких экранах
     распирало страницу по горизонтали. Светиться внутри блока можно, наружу —
     нет. clip, а не hidden: скролл-контейнер здесь не нужен. */
  overflow-x: clip;
  /* Правая колонка ограничена по мере чтения, а не растянута на всю ширину:
     иначе строка уезжает под сотню знаков и текст перестают читать. */
  grid-template-columns: minmax(0, auto) minmax(0, 33rem);
  justify-content: start;
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: center;
}

.talk__stage {
  position: relative;
  justify-self: center;
  display: grid;
  justify-items: center;
  gap: 1.1rem;
}

/* Тёплое пятно за телефоном: без него аппарат висит в пустоте. */
.talk__stage::before {
  content: "";
  position: absolute;
  inset: -12% -18% -6%;
  border-radius: 50%;
  background: radial-gradient(
    closest-side,
    rgba(217, 119, 87, 0.22),
    rgba(217, 119, 87, 0)
  );
  pointer-events: none;
}

/* ---------- корпус ---------- */

.phone {
  position: relative;
  /* Ширина постоянная. Раньше она ехала от вьюпорта, а высота — нет, и на
     узких экранах аппарат вытягивался в неестественно узкий: пропорция важнее
     того, чтобы вписаться любой ценой. */
  width: 20rem;
  padding: 0.62rem;
  border-radius: 2.85rem;
  background: linear-gradient(160deg, #3a3733, #1a1917 55%, #2b2825);
  box-shadow:
    0 0 0 1px rgba(255, 255, 255, 0.09),
    0 2.2rem 3.6rem rgba(0, 0, 0, 0.55);
}

.phone__screen {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  /* Высота считается от ширины, а не от вьюпорта: пропорция настоящего
     аппарата держится на любом экране сама. */
  aspect-ratio: 9 / 19;
  border-radius: 2.3rem;
  overflow: hidden;
  background: #f7f3ec;
  color: #1e1c1a;
  font-size: 0.93rem;
  line-height: 1.5;
}

.phone__bar {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.95rem 1rem 0.7rem;
  border-bottom: 1px solid rgba(30, 28, 26, 0.09);
  background: #f2ede4;
}

.phone__bar b {
  display: block;
  font-size: 0.9rem;
  font-weight: 700;
  line-height: 1.2;
}

.phone__bar span {
  display: block;
  font-family: var(--mono);
  font-size: 0.63rem;
  letter-spacing: 0.05em;
  color: #7d766d;
}

.phone__live {
  margin-left: auto;
  width: 0.44rem;
  height: 0.44rem;
  border-radius: 50%;
  background: #4a9d6a;
  box-shadow: 0 0 0 3px rgba(74, 157, 106, 0.18);
}

.mark {
  width: 1.65rem;
  height: 1.65rem;
  flex: none;
  fill: #d97757;
}

.phone__input {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin: 0.55rem 0.7rem 0.8rem;
  padding: 0.6rem 0.6rem 0.6rem 0.9rem;
  border: 1px solid rgba(30, 28, 26, 0.14);
  border-radius: 1.4rem;
  background: #fff;
  color: #a09890;
}

.phone__input > span {
  flex: 1;
  min-width: 0;
  color: #a09890;
}

.phone__send {
  display: grid;
  place-items: center;
  width: 1.7rem;
  height: 1.7rem;
  flex: none;
  border-radius: 50%;
  background: #d97757;
  color: #fff;
  font-size: 0.9rem;
  line-height: 1;
}

/* ---------- лента ---------- */

.feed {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  padding: 1rem 0.9rem 0.6rem;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: none;
}

/* Гашение перед новым кругом переписки. */
.feed.is-clearing {
  opacity: 0;
  transition: opacity 0.35s ease;
}

.feed::-webkit-scrollbar {
  width: 0;
}

.msg {
  max-width: 100%;
}

.msg p {
  margin: 0 0 0.45rem;
}

.msg p:last-child {
  margin-bottom: 0;
}

.msg--me {
  align-self: flex-end;
  max-width: 86%;
  padding: 0.55rem 0.85rem;
  border-radius: 1.05rem 1.05rem 0.3rem 1.05rem;
  background: #22201d;
  color: #f3ede2;
}

/* display у .msg--ai сильнее браузерного [hidden]{display:none}, поэтому
   неиспользуемые варианты просьбы надо гасить явно — иначе они держат в ленте
   пустое место в свою высоту. */
.feed .msg[hidden] {
  display: none;
}

.msg--ai {
  display: grid;
  grid-template-columns: 1.35rem minmax(0, 1fr);
  gap: 0.55rem;
}

.msg--ai .mark {
  width: 1.35rem;
  height: 1.35rem;
  margin-top: 0.12rem;
}

.msg__body > *:first-child {
  margin-top: 0;
}

/* ---------- виджеты в ответах ---------- */

.w {
  margin: 0.6rem 0 0;
  padding: 0.7rem 0.8rem;
  border: 1px solid rgba(30, 28, 26, 0.12);
  border-radius: 0.85rem;
  background: #fff;
}

.w__cap {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-family: var(--mono);
  font-size: 0.6rem;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: #8b8379;
}

/* Звёздочки ИИ. Стоят там, где решение принял не человек: в подписи секции и
   на карточке сценария, который дом собрал сам. */
.spark {
  width: 1em;
  height: 1em;
  flex: none;
  fill: currentColor;
  vertical-align: -0.13em;
}

/* В шапке секции .head .label задан блоком, а спрайты на сайте — display:block.
   Без этой строки звёздочка встаёт отдельной строкой над подписью. */
.head .label--ico {
  display: inline-flex;
}

.label--ico .spark {
  width: 1.05rem;
  height: 1.05rem;
}

.w__cap .spark {
  width: 0.8rem;
  height: 0.8rem;
  color: #d97757;
}

/* Столбики недели: высота приходит из --v, число подписано сверху. */
.chart {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  align-items: end;
  gap: 0.3rem;
  margin-top: 0.6rem;
}

.chart div {
  display: grid;
  justify-items: center;
  gap: 0.25rem;
  font-family: var(--mono);
  font-size: 0.58rem;
  color: #8b8379;
}

.chart i {
  width: 100%;
  height: calc(2.9rem * var(--v));
  border-radius: 0.22rem 0.22rem 0.1rem 0.1rem;
  background: #e3d7c6;
}

.chart div.is-top i {
  background: #d97757;
}

.chart b {
  font-weight: 500;
  color: #4a443d;
}

/* Список окон: одна строка — одна комната, тревожная подсвечена. */
.w--rooms {
  padding: 0.25rem 0.2rem;
}

.w--rooms li {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  padding: 0.42rem 0.6rem;
  border-radius: 0.5rem;
  font-size: 0.85rem;
}

.w--rooms li + li {
  box-shadow: 0 -1px 0 rgba(30, 28, 26, 0.07);
}

.w--rooms b {
  font-weight: 600;
}

.w--rooms span {
  margin-left: auto;
  font-family: var(--mono);
  font-size: 0.66rem;
  color: #8b8379;
  white-space: nowrap;
}

.w--rooms .is-open {
  background: rgba(217, 119, 87, 0.1);
}

.w--rooms .is-open span {
  color: #b8542f;
}

.w--done {
  display: flex;
  align-items: center;
  gap: 0.7rem;
}

.w--done .tick {
  display: grid;
  place-items: center;
  width: 1.6rem;
  height: 1.6rem;
  flex: none;
  border-radius: 50%;
  background: #4a9d6a;
  color: #fff;
  font-size: 0.85rem;
}

.w--done b {
  display: block;
  font-size: 0.86rem;
}

.w--done em {
  font-style: normal;
  font-family: var(--mono);
  font-size: 0.65rem;
  color: #8b8379;
}

/* ---------- карточка автоматизации ---------- */

.w--auto b.w__name {
  display: block;
  margin: 0.3rem 0 0.55rem;
  font-size: 0.95rem;
}

.w--auto dl {
  display: grid;
  gap: 0.45rem;
  margin: 0;
  font-size: 0.83rem;
}

.w--auto dt {
  font-family: var(--mono);
  font-size: 0.6rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #8b8379;
}

.w--auto dd {
  margin: 0.1rem 0 0;
}

.w__ft {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  margin-top: 0.75rem;
  padding-top: 0.6rem;
  border-top: 1px solid rgba(30, 28, 26, 0.1);
  font-family: var(--mono);
  font-size: 0.66rem;
  letter-spacing: 0.04em;
  color: #8b8379;
}

.sw {
  position: relative;
  width: 1.85rem;
  height: 1.05rem;
  flex: none;
  border-radius: 999px;
  background: #ddd5c8;
  transition: background 0.28s;
}

.sw i {
  position: absolute;
  top: 0.13rem;
  left: 0.13rem;
  width: 0.79rem;
  height: 0.79rem;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
  transition: transform 0.28s;
}

.w__ft.is-on {
  color: #3c7a55;
}

.w__ft.is-on .sw {
  background: #4a9d6a;
}

.w__ft.is-on .sw i {
  transform: translateX(0.8rem);
}

/* ---------- «печатает» ---------- */

.typing {
  display: grid;
  grid-template-columns: 1.35rem auto;
  /* Без start колонка auto забирает всю свободную ширину и три точки
     растягиваются в полосу во весь экран. */
  justify-content: start;
  gap: 0.55rem;
  align-items: center;
}

.typing s {
  display: flex;
  gap: 0.22rem;
  padding: 0.5rem 0.65rem;
  border-radius: 0.9rem;
  background: rgba(30, 28, 26, 0.06);
  text-decoration: none;
}

.typing s span {
  width: 0.3rem;
  height: 0.3rem;
  border-radius: 50%;
  background: #9b938a;
  animation: talk-dot 1.05s infinite ease-in-out;
}

/* Свои точки живут справа и в тёмном пузырьке — как и сама реплика, которая
   из них вырастет. */
.typing--me {
  grid-template-columns: auto;
  justify-content: end;
}

.typing--me s {
  background: #22201d;
}

.typing--me s span {
  background: #b7ac9c;
}

.typing s span:nth-child(2) {
  animation-delay: 0.16s;
}

.typing s span:nth-child(3) {
  animation-delay: 0.32s;
}

@keyframes talk-dot {
  0%,
  70%,
  100% {
    opacity: 0.32;
    transform: translateY(0);
  }
  35% {
    opacity: 1;
    transform: translateY(-0.16rem);
  }
}

/* ---------- появление ---------- */

/* Непоказанное сообщение выключено из потока: иначе лента считает свою высоту
   по всей переписке сразу и уезжает вниз, в пустоту. */
html.js .feed .msg.is-off {
  display: none;
}

html.js .feed .msg {
  opacity: 0;
  transform: translateY(0.5rem);
}

html.js .feed .msg.is-in {
  opacity: 1;
  transform: none;
  transition:
    opacity 0.4s ease,
    transform 0.4s cubic-bezier(0.22, 0.8, 0.3, 1);
}

.talk__replay {
  appearance: none;
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.5rem 1rem;
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: 999px;
  background: transparent;
  color: var(--night-muted);
  font: 500 0.78rem/1 var(--sans);
  cursor: pointer;
  transition:
    color 0.2s,
    border-color 0.2s;
}

.talk__replay:hover {
  color: #fff;
  border-color: rgba(255, 255, 255, 0.45);
}

/* ---------- правая колонка ---------- */

.talk__side h3 {
  margin: 0 0 0.4rem;
  font-size: 1.06rem;
}

.talk__side ul {
  display: grid;
  gap: 1.5rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.talk__side p {
  margin: 0;
  color: var(--night-muted);
}

.talk__note {
  margin-top: 2rem;
  padding-top: 1.1rem;
  border-top: 1px solid var(--night-line);
  font-size: 0.85rem;
  color: var(--night-muted);
}

/* Порог считается так: аппарат 20rem плюс поля секции. Всё, что шире, влезает
   без помощи; ниже — сужаем поля, а не аппарат, иначе он опять начнёт худеть. */
@media (max-width: 22rem) {
  .talk__stage {
    margin-inline: calc(0.5rem - var(--pad));
  }
}

@media (max-width: 60rem) {
  .talk {
    grid-template-columns: minmax(0, 1fr);
    gap: 2.6rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  html.js .feed .msg {
    opacity: 1;
    transform: none;
  }

  html.js .feed .msg.is-off {
    display: none;
  }

  .typing s span {
    animation: none;
  }
}
