/* Нодовая схема (js/nodes.js). Холст — колонка сайта 912, фон в точку, как
   у нодовых редакторов. Все размеры — макетные пиксели через --u; позиции
   нод приходят из json (--x, --y, --w). На телефоне холст шире экрана и
   прокручивается вбок: схема не пересобирается, а остаётся схемой. */

.nodes { margin-block-start: calc(40 * var(--u)); }
@media (max-width: 760px) {
  .nodes {
    overflow-x: auto;
    overscroll-behavior-x: contain;
  }
}
.nodes__canvas {
  position: relative;
  width: calc(912 * var(--u));
  height: calc(var(--h, 520) * var(--u));
  background-image: radial-gradient(
    circle,
    color-mix(in srgb, var(--fg) 16%, transparent) 0 calc(1 * var(--u)),
    transparent calc(1.2 * var(--u))
  );
  background-size: calc(19 * var(--u)) calc(19 * var(--u));
  touch-action: pan-y;
}

/* --- Провода --------------------------------------------------------------- */

.nodes__wires {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
}
.wire__line,
.wire__flow {
  fill: none;
  stroke-width: var(--line-px);
  stroke-linecap: round;
  transition: stroke var(--dur-ui) var(--ease), opacity var(--dur-ui) var(--ease);
}
.wire__line { stroke: var(--hair); }
.wire__flow {
  stroke: var(--accent);
  stroke-dasharray: 2 14;
  opacity: 0;
}
.nodes.is-live .wire__flow {
  opacity: 0.9;
  animation: wire-flow 1.4s linear infinite;
}
@keyframes wire-flow { to { stroke-dashoffset: -16; } }
.wire.is-hot .wire__line { stroke: var(--accent); }
.nodes.is-focus .wire:not(.is-hot) { opacity: 0.25; }

.wire__label {
  position: absolute;
  z-index: 1;
  translate: -50% -50%;
  padding: calc(2 * var(--u)) calc(8 * var(--u));
  border: var(--line) solid var(--hair);
  border-radius: 100px;
  background: var(--bg);
  font-family: var(--mono);
  font-size: calc(10 * var(--u));
  line-height: calc(14 * var(--u));
  letter-spacing: 0.1em;
  color: color-mix(in srgb, var(--fg) 60%, var(--bg));
  pointer-events: none;
}

/* --- Ноды ------------------------------------------------------------------- */

.node {
  position: absolute;
  inset-inline-start: calc(var(--x) * var(--u));
  inset-block-start: calc(var(--y) * var(--u));
  z-index: 2;
  width: calc(var(--w) * var(--u));
  margin: 0;
  cursor: grab;
  user-select: none;
  touch-action: none;
  transition: opacity var(--dur-ui) var(--ease);
}
.node.is-dragging { z-index: 3; cursor: grabbing; }
.nodes.is-focus .node:not(.is-hot, .is-near) { opacity: 0.35; }

.node__head {
  display: flex;
  gap: calc(10 * var(--u));
  margin-block-end: calc(6 * var(--u));
  font-family: var(--mono);
  font-size: calc(10 * var(--u));
  line-height: calc(14 * var(--u));
  letter-spacing: 0.14em;
  text-transform: uppercase;
  white-space: nowrap;
}
.node__tool { color: color-mix(in srgb, var(--fg) 50%, var(--bg)); }

.node__body {
  position: relative;
  border-radius: calc(8 * var(--u));
  background: color-mix(in srgb, var(--fg) 5%, var(--bg));
  transition: box-shadow var(--dur-ui) var(--ease);
}
.node.is-hot .node__body { box-shadow: 0 0 0 var(--line) var(--accent); }
.node--text .node__body {
  padding: calc(10 * var(--u)) calc(12 * var(--u));
  border: var(--line) solid var(--hair);
}
.node__media {
  display: block;
  width: 100%;
  height: auto;
  border-radius: calc(8 * var(--u));
  pointer-events: none;
}
.node__text {
  font-family: var(--mono);
  font-size: calc(12 * var(--u));
  line-height: calc(17 * var(--u));
  color: color-mix(in srgb, var(--fg) 70%, var(--bg));
}

/* Порты — кружки на середине краёв тела ноды; к ним подходят провода. */
.node__port {
  position: absolute;
  inset-block-start: 50%;
  width: calc(10 * var(--u));
  height: calc(10 * var(--u));
  border: var(--line) solid var(--hair);
  border-radius: 50%;
  background: var(--bg);
  translate: -50% -50%;
  transition: border-color var(--dur-ui) var(--ease), background-color var(--dur-ui) var(--ease);
}
.node__port--in { inset-inline-start: 0; }
.node__port.is-idle { display: none; } /* к порту ничего не подключено */
.node__port--out { inset-inline-start: 100%; }
.node.is-hot .node__port { border-color: var(--accent); background: var(--accent); }

@media (prefers-reduced-motion: reduce) {
  .nodes.is-live .wire__flow { animation: none; }
}

/* =============================================================================
   Пайплайн на главной (js/pipeline.js) — та же нодовая система, но живая:
   ноды софта включаются кликом, провода пересобираются, выход печатается.
   ============================================================================= */

.pipe-head {
  display: grid;
  grid-template-columns: calc(var(--col) * 2) 1fr;
  gap: calc(40 * var(--u));
  align-items: end;
  margin-block-start: calc(33 * var(--u));
}
.pipe-head__note {
  font-family: var(--mono);
  font-size: var(--fs-caption);
  line-height: calc(22 * var(--u));
  color: color-mix(in srgb, var(--fg) 62%, var(--bg));
}

.pipe__stage {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: calc(var(--x) * var(--u));
  font-family: var(--mono);
  font-size: calc(10 * var(--u));
  line-height: calc(14 * var(--u));
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--fg) 45%, var(--bg));
}

/* Нода софта: значок, имя, роль. Выключенная — пунктиром и без цвета,
   включённая — сплошной рамкой, значок в своих цветах. */
.pipe__tool .node__head { display: none; }
.pipe__tool .node__body {
  display: grid;
  grid-template-columns: auto 1fr;
  column-gap: calc(7 * var(--u));
  align-items: center;
  padding: calc(8 * var(--u)) calc(8 * var(--u));
  border: var(--line) dashed var(--hair);
  background: var(--bg);
  transition:
    border-color var(--dur-ui) var(--ease),
    background-color var(--dur-ui) var(--ease),
    box-shadow var(--dur-ui) var(--ease);
}
.pipe__icon {
  grid-row: span 2;
  width: calc(24 * var(--u));
  height: calc(24 * var(--u));
  filter: grayscale(1);
  opacity: 0.4;
  transition: filter var(--dur-ui) var(--ease), opacity var(--dur-ui) var(--ease);
}
.pipe__name,
.pipe__role { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.pipe__name {
  font-family: var(--mono);
  font-size: calc(10.5 * var(--u));
  line-height: calc(14 * var(--u));
  white-space: nowrap;
  color: color-mix(in srgb, var(--fg) 55%, var(--bg));
  transition: color var(--dur-ui) var(--ease);
}
.pipe__role {
  font-family: var(--mono);
  font-size: calc(9 * var(--u));
  line-height: calc(12 * var(--u));
  white-space: nowrap;
  color: color-mix(in srgb, var(--fg) 35%, var(--bg));
}
.pipe__tool:hover .node__body,
.pipe__tool:focus-visible .node__body { border-color: color-mix(in srgb, var(--fg) 45%, var(--bg)); }
.pipe__tool:focus-visible { outline: none; }
.pipe__tool.is-on .node__body {
  border-style: solid;
  border-color: var(--hair);
  background: color-mix(in srgb, var(--accent) 7%, var(--bg));
}
.pipe__tool.is-on .pipe__icon { filter: none; opacity: 1; }
.pipe__tool.is-on .pipe__name { color: var(--fg); }

/* Вход и выход. */
.pipe__io .node__body { background: var(--bg); }
.pipe__prompt {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  color: var(--fg);
  font-family: var(--mono);
  font-size: calc(12 * var(--u));
  line-height: calc(17 * var(--u));
  resize: none;
  outline: none;
}
.pipe__chips {
  display: flex;
  flex-wrap: wrap;
  gap: calc(4 * var(--u));
  margin-block-start: calc(10 * var(--u));
  padding-block-start: calc(10 * var(--u));
  border-block-start: var(--line) solid var(--hair);
}
.pipe__chip {
  padding: calc(3 * var(--u)) calc(8 * var(--u));
  border: var(--line) solid var(--hair);
  border-radius: 100px;
  background: none;
  color: color-mix(in srgb, var(--fg) 60%, var(--bg));
  font-family: var(--mono);
  font-size: calc(9 * var(--u));
  line-height: calc(12 * var(--u));
  letter-spacing: 0.1em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background-color var(--dur-ui) var(--ease), color var(--dur-ui) var(--ease), border-color var(--dur-ui) var(--ease);
}
.pipe__chip:hover { border-color: var(--accent); color: var(--accent); }
.pipe__chip[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: var(--bg); }

.pipe__result { color: var(--fg); }
.pipe__result.is-typing::after {
  content: "";
  display: inline-block;
  width: 0.5em;
  height: 1em;
  margin-inline-start: 0.1em;
  vertical-align: -0.15em;
  background: var(--accent);
  animation: pipe-caret 0.7s steps(1) infinite;
}
@keyframes pipe-caret { 50% { opacity: 0; } }
.pipe__output.is-pulse .node__body { animation: pipe-pulse 0.9s var(--ease-out); }
@keyframes pipe-pulse {
  from { box-shadow: 0 0 0 calc(6 * var(--u)) color-mix(in srgb, var(--accent) 35%, transparent); }
  to { box-shadow: 0 0 0 0 transparent; }
}

/* Новый провод прорисовывается от порта к порту; сигнал по нему пускается
   после. */
.wire.is-new .wire__line { stroke-dasharray: 1; animation: wire-in 0.8s var(--ease-out) both; }
.wire.is-new .wire__flow { opacity: 0 !important; }
@keyframes wire-in { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }

@media (max-width: 760px) {
  .pipe-head { grid-template-columns: 1fr; gap: calc(16 * var(--u)); }
}
@media (prefers-reduced-motion: reduce) {
  .wire.is-new .wire__line { animation: none; stroke-dasharray: none; }
  .pipe__output.is-pulse .node__body { animation: none; }
}

/* =============================================================================
   Первый экран (js/hero-nodes.js): входы слева от имени, выход справа.
   ============================================================================= */

.hero__graph {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
}
.hero__graph[hidden] { display: none; }
/* Поток сверху вниз: порты у нод — сверху (вход) и снизу (выход). */
.hero__graph .node__port--in { inset-block-start: 0; inset-inline-start: 50%; }
.hero__graph .node__port--out { inset-block-start: 100%; inset-inline-start: 50%; }
.hero__graph .hero-node--side .node__port { inset-block-start: 50%; }
.hero__graph .hero-node--side .node__port--in { inset-inline-start: 0; }
.hero__graph .hero-node--side .node__port--out { inset-inline-start: 100%; }
/* Шоурил на телефоне: вход сбоку, от выхода, а выход снизу — ветка к
   WORK и ABOUT уходит вниз. */
.hero__graph .hero-node--fork .node__port--out { inset-block-start: 100%; inset-inline-start: 50%; }
.hero__graph .node__media {
  aspect-ratio: 16 / 9;
  object-fit: cover;
  transition: opacity 180ms var(--ease);
}
.hero__graph .node.is-swap .node__media { opacity: 0.1; }
.hero-node--action .node__body { cursor: pointer; }
.hero-node--action:hover .node__body,
.hero-node--action:focus-visible .node__body { box-shadow: 0 0 0 var(--line) var(--accent); }
.hero-node--action:focus-visible { outline: none; }
.hero__graph .node {
  position: absolute;
  pointer-events: auto;
  text-align: start;
}
.hero__graph .node__body { background: var(--bg); }
.hero__graph .node--text .node__body { padding: calc(8 * var(--u)) calc(10 * var(--u)); }
/* В DESIGN пять значков: на ширине ноды они чуть ужимаются, а не лезут
   за край. */
.hero-node__icons { display: flex; gap: calc(6 * var(--u)); }
.hero-node__icon { flex: 0 1 calc(22 * var(--u)); min-width: 0; aspect-ratio: 1; }
.hero__port {
  position: absolute;
  width: calc(10 * var(--u));
  height: calc(10 * var(--u));
  border: var(--line) solid var(--accent);
  border-radius: 50%;
  background: var(--bg);
  translate: -50% -50%;
}
/* Подсказка «ноды таскаются» (js/hero-nodes.js): призрак кольца-курсора
   (.cursor в css/home.css) — в том виде, какой оно принимает над нодой:
   крупное, пунктиром, крутится; на «нажатии» сжимается. */
.hero__hint {
  position: absolute;
  inset: 0 auto auto 0;
  z-index: 4;
  width: 28px;
  height: 28px;
  margin: -14px 0 0 -14px;
  mix-blend-mode: difference;
  pointer-events: none;
  opacity: 0;
  transition:
    opacity 200ms var(--ease-out),
    scale 380ms var(--ease);
}
.hero__hint svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
  animation: cursor-spin 7s linear infinite;
}
.hero__hint circle {
  fill: none;
  stroke: #fff;
  stroke-width: 0.75;
  stroke-dasharray: 4.5 3.5;
}
.hero__hint.is-shown { opacity: 1; scale: 2; }
.hero__hint.is-down { scale: 1.6; }
.hero__graph.is-live .wire__flow {
  opacity: 0.9;
  animation: wire-flow 1.4s linear infinite;
}
/* Имя — тоже нода: рамка бегущим пунктиром, шапка сверху, порты графа на её
   рёбрах. Типографика первого экрана и нодовая система — одним языком.
   При наведении рамка сжимается к имени; порты едут за ней (js/hero-nodes.js,
   там же отступы рамки в покое — менять вместе). */
.hero__title { position: relative; }
.hero__frame {
  position: absolute;
  inset: calc(-26 * var(--u)) calc(-32 * var(--u)) calc(-24 * var(--u));
  z-index: -1;
  text-align: start;
  pointer-events: none;
  transition: inset 500ms var(--ease-out);
}
.hero__title:hover .hero__frame {
  inset: calc(-14 * var(--u)) calc(-18 * var(--u)) calc(-12 * var(--u));
}
/* Зона наведения — вся рамка в покое, а не только текст: сжатая рамка не
   уходит из-под курсора и не дёргается. */
.hero__title::after {
  content: "";
  position: absolute;
  inset: calc(-26 * var(--u)) calc(-32 * var(--u)) calc(-24 * var(--u));
  z-index: -1;
}
/* Пунктир — SVG: у border штрихи не двигаются. pathLength="1000" и период
   5 — ровно 200 штрихов по периметру, без шва в углу. */
.hero__dash {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}
.hero__dash rect {
  rx: calc(8 * var(--u));
  fill: none;
  stroke: var(--hair);
  stroke-width: var(--line-px, 1px);
  stroke-dasharray: 2.5 2.5;
  animation: hero-dash 0.9s linear infinite;
}
@keyframes hero-dash { to { stroke-dashoffset: -5; } }
/* Невидимые точки портов на рёбрах рамки (js/hero-nodes.js): едут вместе
   с наклоном и сжатием, по ним встают порты графа. */
.hero__anchor { position: absolute; width: 0; height: 0; }
.hero__frame .node__head {
  position: absolute;
  inset-block-end: 100%;
  inset-inline-start: 0;
}
@media (prefers-reduced-motion: reduce) {
  .hero__dash rect { animation: none; }
  .hero__frame { transition: none; }
}
/* Телефон: граф тот же, ноды плотнее (раскладка — layoutPhone в
   js/hero-nodes.js). Наведения нет — рамка не сжимается. Вертикальный
   свайп по ноде листает страницу, вбок — тащит ноду. */
@media (max-width: 760px) {
  .hero__title::after { display: none; }
  .hero__frame,
  .hero__title:hover .hero__frame { inset: calc(-20 * var(--u)) 0 calc(-18 * var(--u)); }
  .hero__graph .node { touch-action: pan-y; }
  .hero__graph .node__head { gap: calc(6 * var(--u)); font-size: calc(9 * var(--u)); letter-spacing: 0.1em; }
  .hero__graph .node--text .node__body { padding: calc(7 * var(--u)) calc(8 * var(--u)); }
  .hero__graph .node__text { font-size: calc(10.5 * var(--u)); line-height: calc(15 * var(--u)); }
  /* Нода здесь в треть колонки: значки переносятся по три в ряд, а не
     мельчают. */
  .hero-node__icons { flex-wrap: wrap; gap: calc(5 * var(--u)); }
  .hero-node__icon { flex: 0 0 calc(18 * var(--u)); }
}

/* =============================================================================
   «Get in touch», нодовая версия (js/contact.js): вход → каналы → выход.
   Раскладка — сетка в три колонки, а не координаты: окно маленькое, и
   ноды должны вставать сами.
   ============================================================================= */

.contact--nodes { width: min(calc(720 * var(--u)), calc(100vw - 32px)); }
.contact__graph {
  display: grid;
  grid-template-columns: 1.35fr 1fr 1.1fr;
  gap: calc(40 * var(--u));
  align-items: center;
  width: auto;
  height: auto;
  margin-block-start: calc(20 * var(--u));
  padding-block: calc(20 * var(--u));
}
.contact__graph .node {
  position: relative;
  inset: auto;
  width: auto;
  cursor: auto;
  touch-action: auto;
}
.contact__routes { display: grid; gap: calc(10 * var(--u)); position: relative; z-index: 2; }
.contact__route { cursor: pointer; }
.contact__route-icon {
  grid-row: span 2;
  display: grid;
  place-items: center;
  width: calc(26 * var(--u));
  height: calc(26 * var(--u));
  border: var(--line) solid currentColor;
  border-radius: 50%;
  color: color-mix(in srgb, var(--fg) 45%, var(--bg));
  transition: color var(--dur-ui) var(--ease);
}
.contact__route-icon .social__icon { width: 58%; height: 58%; }
.contact__route.is-on .contact__route-icon { color: var(--accent); }
.contact__in .node__port--in,
.contact__out .node__port--out { display: none; }

.contact__graph .wire:not(.is-hot) { opacity: 0.22; }
.contact__graph .wire.is-hot .wire__line { stroke: var(--accent); }
.contact__graph .wire.is-hot .wire__flow {
  opacity: 0.9;
  animation: wire-flow 1.4s linear infinite;
}
.contact__where { font-size: calc(11 * var(--u)); line-height: calc(16 * var(--u)); }
.contact__send {
  margin-block-start: calc(12 * var(--u));
  color: var(--bg);
  background: var(--accent);
  border-color: var(--accent);
}
.contact__out.is-pulse .node__body { animation: pipe-pulse 0.9s var(--ease-out); }
.contact__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: calc(16 * var(--u));
}
.contact__foot .contact__status { margin: 0; }
.contact__more .social { width: calc(32 * var(--u)); height: calc(32 * var(--u)); }

@media (max-width: 760px) {
  .contact__graph { grid-template-columns: 1fr; gap: calc(20 * var(--u)); }
  .contact__graph .wire { display: none; }
  .contact__foot { flex-direction: column; align-items: start; }
}
