:root{--cream:#d5cadb;--panel:#101119;--cell:#0a0710;--cell-dark:#1a1226}*{box-sizing:border-box}/* Протяжка по полю — это ход, а не жест обновления страницы. */
html,body{height:100%;margin:0;overflow:hidden;overscroll-behavior:none}body{min-width:320px;background:radial-gradient(circle at 50% 0,#10121c,#05050a 42%,#010104 78%);color:var(--cream);font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.game{height:100dvh;padding:18px 24px;position:relative}.brand{position:absolute;left:28px;top:25px;z-index:2;font:900 clamp(34px,5vw,70px)/.7 system-ui;letter-spacing:-.09em;transform:skewY(-4deg);text-shadow:4px 4px #09050d}.brand span{display:block;color:#70f27d}.brand b{color:#f15b70}.brand small{display:block;margin-top:18px;color:#767c8b;font:9px ui-monospace,monospace;letter-spacing:.14em}.stage{height:100%;display:grid;grid-template-columns:minmax(0,auto) 165px;align-items:center;justify-content:center;gap:clamp(18px,3vw,42px)}.play-area{min-width:0}.board{position:relative;width:min(88vh,calc(100vw - 240px));aspect-ratio:1;display:grid;grid-template:repeat(var(--size,9),1fr)/repeat(var(--size,9),1fr);border:6px solid #585d6b;background:repeating-linear-gradient(0deg,#020205 0 3px,#05060a 3px 4px);box-shadow:inset 0 0 0 2px #151821,6px 6px 0 #000}.cell{position:relative;min-width:0;padding:0;border:1px solid #14101c;background:var(--cell);box-shadow:inset 0 0 0 1px #0d0a14;cursor:pointer}.cell:hover{background:#160f22}.cell.selected{outline:2px solid #00d9ff;outline-offset:-2px;z-index:2;box-shadow:inset 0 0 12px #00d9ff66}.ball{--ball:#fff;width:92%;aspect-ratio:1;display:block;margin:auto;background-position:center;background-size:contain;background-repeat:no-repeat}.selected .ball{animation:selected .52s ease-in-out infinite alternate}.cell.born{z-index:3;overflow:visible}.cell.born .ball{animation:born .52s cubic-bezier(.15,.85,.25,1) both}.cell.clearing .ball{animation:clear .42s ease-in both}.route-ball{position:absolute;z-index:5;width:calc(100%/var(--size,9)*.86);margin:0;left:calc(var(--x)*100%/var(--size,9) + 100%/var(--size,9)*.07);top:calc(var(--y)*100%/var(--size,9) + 100%/var(--size,9)*.07);transition:left 90ms linear,top 90ms linear;animation:step-hop 90ms ease-in-out infinite alternate;pointer-events:none}.cell.ghost .ball{visibility:hidden}@keyframes selected{to{transform:translateY(-17%) scale(1.07);filter:brightness(1.18)}}@keyframes step-hop{to{transform:translateY(-22%) scale(1.05)}}@keyframes born{from{transform:scale(.04);filter:brightness(3)}65%{transform:scale(1.2)}}@keyframes clear{to{transform:scale(.02) rotate(120deg);opacity:0;filter:brightness(3)}}.message{height:20px;margin:10px 0 0;color:#d5cadb;font-size:11px}.sidebar{display:grid;gap:11px}.meter,.preview,.records{padding:10px;border:1px solid #313442;background:linear-gradient(#101119,#050508)}.sidebar small{display:block;margin-bottom:6px;color:#9198a8;font-size:9px;letter-spacing:.1em}.meter strong{color:#efff00;font-size:20px}.preview>div{display:flex;gap:6px}.preview .ball{width:19px;margin:0}.action,.curtain button{padding:10px 7px;border:2px solid #7dff00;background:#0d1a06;color:#b6ff6a;font:bold 10px ui-monospace,monospace;letter-spacing:.08em;box-shadow:3px 3px #000;cursor:pointer}.records p{display:flex;justify-content:space-between;margin:5px 0;font-size:9px}.records b{color:#efff00}.records em{color:#767c8b;font-size:9px;font-style:normal}@media(max-width:680px){.game{padding:10px}.brand{top:14px;left:14px;font-size:34px}.brand small{display:none}.stage{grid-template-columns:minmax(0,1fr) 96px;gap:9px}.board{width:calc(100vw - 125px)}.sidebar{gap:7px}.meter,.preview,.records{padding:6px}.meter strong{font-size:13px}.records p,.records em{font-size:7px}.message{font-size:8px}}

.board.quake{animation:quake 420ms cubic-bezier(.36,.07,.19,.97) both}
.board.quake::after{content:'';position:absolute;inset:0;z-index:6;pointer-events:none;mix-blend-mode:screen;background:radial-gradient(circle at 50% 50%,#fff,#70f27d 40%,transparent 72%);animation:board-flash 420ms ease-out forwards}
@keyframes quake{0%{transform:translate3d(0,0,0)}15%{transform:translate3d(calc(var(--quake,1)*-3px),calc(var(--quake,1)*2px),0) rotate(calc(var(--quake,1)*-.2deg))}33%{transform:translate3d(calc(var(--quake,1)*3px),calc(var(--quake,1)*-2px),0) rotate(calc(var(--quake,1)*.24deg))}52%{transform:translate3d(calc(var(--quake,1)*-2px),calc(var(--quake,1)*1px),0) rotate(calc(var(--quake,1)*-.13deg))}74%{transform:translate3d(calc(var(--quake,1)*1px),calc(var(--quake,1)*-1px),0)}100%{transform:translate3d(0,0,0)}}
@keyframes board-flash{0%{opacity:0}14%{opacity:calc(var(--quake,1)*.17)}100%{opacity:0}}
@media(prefers-reduced-motion:reduce){.board.quake{animation:none}.board.quake::after{display:none}}

/* 2026 cyber-noir visual unification with Tetcolor. */
:root{--cream:#d5cadb;--panel:#101119;--cell:#0a0710;--cell-dark:#1a1226}
body{background:repeating-linear-gradient(0deg,#87ffd514 0 1px,transparent 1px 4px),radial-gradient(circle at 50% 0,#10121c,#05050a 42%,#010104 78%)}
.board{border:6px solid #74829b;background:repeating-linear-gradient(0deg,#050914 0 3px,#0a101d 3px 4px);box-shadow:inset 0 0 0 2px #27354e,0 0 22px #3c71a52e,6px 6px 0 #000}
.cell{border:1px solid #334661;background:linear-gradient(145deg,#111a29,#09101d);box-shadow:inset 0 0 0 1px #18243a}.cell:hover{background:#16243a}.cell.selected{outline-color:#00d9ff;filter:drop-shadow(0 0 7px #00d9ff)}
.meter,.preview,.records{border-color:#313442;background:linear-gradient(#101119,#050508)}
.meter strong,.records b{color:#efff00}
/* The same three buttons Tetcolor uses: gold for the action, cyan for the
   toggle beside it, acid green only for the one on the curtain. Three
   different button colours across two games read as two different games. */
.action{border-color:#f4c542;background:#26210e;color:#ffe78d;box-shadow:3px 3px 0 #000}
#sound-toggle,#music-toggle{border-color:#00d9ff;background:#07191d;color:#83f1ff}
#undo{order:-1;grid-column:1/-1;min-height:54px;border-color:#bd67e8;background:#150a1c;color:#d9a8f0;font-size:11px}
#restart,#sound-toggle,#music-toggle,#how{min-height:44px;padding:6px 5px;border-width:1px;font-size:8px;box-shadow:2px 2px 0 #000}
#restart,#how{border-color:#46536b;background:#0d1320;color:#b9c4d8}
.overlay button{border-color:#7dff00;background:#101606;color:#efff00;box-shadow:0 0 14px #7dff0055,3px 3px #000}
.overlay{position:fixed;inset:0;z-index:80;justify-items:center;gap:20px;padding:clamp(24px,6vw,72px);border:0;background:radial-gradient(circle at 50% 18%,#181025,#05050a 55%,#010104);animation:curtain .48s cubic-bezier(.1,.8,.2,1)}
.overlay > button{min-width:min(88vw,620px);min-height:clamp(88px,21vh,132px);font-size:clamp(30px,8vw,52px)}
.game-home-menu{opacity:.58!important;border-color:#313442!important;color:#d5cadb!important;font-size:8px!important}
/* Свой минимум на случай, если общий /game-menu.css не доедет: он живёт в
   корне сайта и в другом репозитории, а правило «выход есть всегда» —
   наше. Без этих строк кнопка становится строчкой 48×10 в левом углу. */
.game-home-menu{position:fixed;top:max(10px,env(safe-area-inset-top));right:max(10px,env(safe-area-inset-right));z-index:1000;
  display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;
  min-height:44px;padding:9px 13px;background:#0b1010f2;text-decoration:none;white-space:nowrap;border:1px solid #313442;border-radius:999px}
body[data-ball-style] .ball{border-radius:50%;background:var(--ball)}
@media(max-width:680px){.stage{padding-top:40px;gap:3px}.board{width:min(calc(100vw - 14px),calc(100dvh - 188px))}.sidebar-bottom{margin-top:0;padding:3px}.message{min-height:22px;margin-top:2px;padding:3px 6px}.overlay > button{min-height:clamp(88px,21vh,132px)}}

/* The curtain went full screen in the pass above, which left it with no
   layout of its own and no keyframes — both were tied to the little dialog it
   used to be. It gets them back, and it gets pictures: the start screen is a
   portrait 856x1838 split into a band pinned to the top and a band pinned to
   the bottom, the way Tetcolor holds its poster together on a phone and on a
   laptop alike. Game over is a corridor of wreckage, so it goes full bleed. */
@keyframes curtain{from{opacity:0}}
.overlay{display:grid;align-content:center;justify-items:center;text-align:center;overflow:hidden}
.overlay > b,.overlay > span,.overlay > button{position:relative;z-index:1}
.overlay > button{width:min(88vw,620px);min-height:clamp(88px,21vh,132px);border:4px solid #7dff00;border-radius:14px;background:linear-gradient(135deg,#efff00,#4dff00 56%,#00ffe1);color:#040006;font-size:clamp(30px,8vw,52px);font-weight:900;letter-spacing:.08em;box-shadow:0 0 28px #7dff00bf,inset 0 0 30px #ffffff59}
.overlay > button:active{transform:scale(.98);filter:brightness(1.15)}
.overlay > b{color:#78f387;font-size:clamp(34px,9vw,64px);line-height:1;letter-spacing:-.02em;text-shadow:0 0 18px #78f38766}
.overlay > span{color:#d5cadb;font-size:clamp(13px,3.6vw,18px)}
.curtain-sky,.curtain-floor,.curtain-full{position:absolute;left:0;z-index:0;width:100%;object-fit:cover;pointer-events:none;user-select:none;filter:brightness(.72) saturate(1.08)}
.curtain-sky,.curtain-floor{height:52%}
.curtain-sky{top:0;object-position:50% 0;-webkit-mask-image:linear-gradient(#000 0 44%,transparent 97%);mask-image:linear-gradient(#000 0 44%,transparent 97%)}
.curtain-floor{bottom:0;object-position:50% 100%;-webkit-mask-image:linear-gradient(transparent 3%,#000 56%);mask-image:linear-gradient(transparent 3%,#000 56%)}
.curtain-full{top:0;height:100%;object-position:50% 50%}
.curtain-veil{position:absolute;z-index:0;inset:0;pointer-events:none;background:radial-gradient(58% 40% at 50% 50%,#03000ae6 0 34%,#03000ab3 66%,transparent 88%),linear-gradient(180deg,#03000a66,transparent 26%,transparent 74%,#03000a66)}
@media(prefers-reduced-motion:reduce){.overlay{animation:none}}

/* Волшебный шар: подходит к любому цвету, поэтому носит их все сразу. Форму и
   скругление задаёт заново — иначе огранка обрезала бы его своим clip-path, а
   трубка оставила бы полым. Мигает за два хода до исчезновения: пропасть без
   предупреждения читалось бы как сбой, а не как правило. */
body[data-ball-style] .ball.wild{
  position:relative;border:0;border-radius:50%;clip-path:none;
  background:conic-gradient(from 0deg,#ef4356,#ffd13a,#42d574,#42aef5,#bd67e8,#ff70ad,#ff9138,#ef4356);
  box-shadow:0 0 10px #ffffffaa,0 0 20px #bd67e8cc;
  animation:wild-spin 3.6s linear infinite;
}
body[data-ball-style] .ball.wild::after{
  content:'';position:absolute;inset:30%;border-radius:50%;
  background:radial-gradient(circle at 38% 33%,#fff,#ffffffd9 45%,#ffffff40 72%,transparent);
}
body[data-ball-style] .ball.wild.fading{animation:wild-spin 3.6s linear infinite,wild-blink .9s ease-in-out infinite}
@keyframes wild-spin{to{transform:rotate(360deg)}}
@keyframes wild-blink{50%{opacity:.3;box-shadow:0 0 4px #ffffff66}}
@media(prefers-reduced-motion:reduce){
  body[data-ball-style] .ball.wild,body[data-ball-style] .ball.wild.fading{animation:none}
  body[data-ball-style] .ball.wild.fading{opacity:.55}
}

/* Номер этапа под счётом: подниматься молча он не должен, иначе игрок
   заметит только, что стало теснее, и решит, что ему не везёт. */
.meter em{display:block;margin-top:4px;color:#767c8b;font-size:8px;font-style:normal;letter-spacing:.1em}

/* Бомба и камень: обе не цвета, поэтому обе сбрасывают чужую форму. Камень
   единственный на поле ничего не излучает — он и не должен, он помеха. */
body[data-ball-style] .ball.bomb{
  position:relative;border:0;border-radius:50%;clip-path:none;
  background:
    radial-gradient(circle at 50% 50%,#ff9138 0 6%,transparent 7%),
    radial-gradient(circle at 30% 26%,#6a5040,#2b1c18 44%,#0b0608 76%);
  box-shadow:0 0 12px #ff913899,0 0 26px #ef435666,inset -5px -6px 11px #000;
  animation:bomb-pulse 1.1s ease-in-out infinite;
}
/* Трещины: две скрещённые полосы раскалённого света поверх тёмного шара —
   чтобы он читался как «сейчас рванёт», а не как серый камень. */
body[data-ball-style] .ball.bomb::before{
  content:'';position:absolute;inset:14%;border-radius:50%;pointer-events:none;
  background:
    linear-gradient(58deg,transparent 44%,#ff9138 47%,#ffd13a 50%,#ff9138 53%,transparent 56%),
    linear-gradient(-34deg,transparent 46%,#ef4356 49%,#ff9138 51%,transparent 54%);
  opacity:.85;mix-blend-mode:screen;
}
body[data-ball-style] .ball.bomb::after{
  content:'';position:absolute;left:56%;top:-20%;width:16%;height:30%;
  border-radius:50% 50% 40% 40%;background:linear-gradient(#fff,#ffd13a 40%,#ff9138);
  box-shadow:0 0 9px #ffd13a,0 0 18px #ff9138;
  animation:fuse .42s ease-in-out infinite alternate;
}
@keyframes fuse{to{opacity:.4;transform:scale(.72) translateY(8%)}}
@keyframes bomb-pulse{50%{box-shadow:0 0 18px #ff9138cc,0 0 34px #ef435699,inset -5px -6px 11px #000}}

body[data-ball-style] .ball.stone{
  border:0;border-radius:22%;clip-path:none;
  background:linear-gradient(150deg,#4a4d58,#2a2c34 52%,#15161c);
  box-shadow:inset 2px 2px 0 #5d616e,inset -3px -3px 0 #0d0e12;
}

/* Указатель пути — и только он. Сергей, 3.10.2026: «когда появляется вариант
   передвинуть шарик, только стрелочка до нового поля появляется, и всё».
   До этого на выборе шара вся доска получала разметку «долетит / не долетит»
   (мятная рамка на каждой доступной клетке, штриховка с красным крестом на
   каждой недоступной) и теряла её на приземлении — это и мигало на каждом
   ходу. Маршрут показывается под курсором, пока выбираешь клетку, и на любом
   устройстве — пока шар летит по нему. Точка в центре клетки — цвет выбора,
   чтобы читалось «куда идёт выбранный». */
.cell.route{background:#141026;box-shadow:inset 0 0 0 1px #3a2f5c}
.cell.route::before{content:'';position:absolute;inset:40%;border-radius:50%;background:#00d9ff66;box-shadow:0 0 6px #00d9ff55;pointer-events:none}


/* Кнопка занавеса тоже должна отвечать курсору. */
.overlay > button:hover{filter:brightness(1.08);box-shadow:0 0 34px #7dff00e6,inset 0 0 30px #ffffff73}

/* Постер вертикальный. На широком экране полоса шириной 1440 растягивает его
   в 1,7 раза, и мелкие шары сверху превращаются в планеты во весь экран —
   ровно то, чего на телефоне не видно. Поэтому в ландшафте берём одну спокойную
   полосу из середины постера, а двухполосную подачу оставляем портрету. */
@media(min-aspect-ratio:1/1){
  .curtain-sky{top:0;height:100%;object-position:50% 44%;-webkit-mask-image:none;mask-image:none;filter:brightness(.5) saturate(1.05)}
  .curtain-floor{display:none}
}

/* Выбранный шар подпрыгивает, а не пульсирует: приседание, взлёт, посадка.
   Пульсация читается как «он загружается», прыжок — как «он выбран». */
@keyframes hop{
  0%,58%,100%{transform:translateY(0) scale(1,1)}
  9%{transform:translateY(0) scale(1.1,.86)}
  32%{transform:translateY(-36%) scale(.95,1.07)}
  50%{transform:translateY(0) scale(1.07,.93)}
}
.selected .ball{animation:hop .62s ease-in-out infinite!important}
@keyframes step-hop-real{
  0%,100%{transform:translateY(0) scale(1,1)}
  40%{transform:translateY(-30%) scale(.96,1.05)}
}
.route-ball{animation:step-hop-real .18s ease-in-out infinite!important}
@media(prefers-reduced-motion:reduce){.selected .ball,.route-ball{animation:none!important}}

/* Особые фишки просят position:relative под свои свечения и фитиль, и это
   правило весит больше, чем `.route-ball` — летящая фишка теряла абсолютное
   положение, вываливалась в поток сетки и съёживалась до восьми пикселей.
   Отсюда и «бомба взорвалась, а как летела — не видно». */
body[data-ball-style] .ball.route-ball{position:absolute}

/* Объяснение особой фишки: выезжает от самой клетки, где она легла. */
.tip{
  position:absolute;z-index:9;width:min(220px,62%);
  left:clamp(0px,calc(var(--x)*100%/var(--size,9) - 22%),calc(100% - min(220px,62%)));
  padding:9px 11px;display:grid;grid-template-columns:auto 1fr;gap:3px 9px;
  border:1px solid #7dff00;background:#080a10f2;box-shadow:0 8px 26px #000c,0 0 18px #7dff0033;
  cursor:pointer;animation:tip-in .28s cubic-bezier(.2,.8,.3,1) both;
}
.tip[data-side='below']{top:calc((var(--y) + 1)*100%/var(--size,9) + 6px)}
.tip[data-side='above']{top:auto;bottom:calc((var(--size,9) - var(--y))*100%/var(--size,9) + 6px)}
.tip .ball{grid-row:span 2;width:26px;height:26px;margin:0;align-self:center}
.tip b{align-self:end;color:#b6ff6a;font-size:10px;letter-spacing:.1em}
.tip span{color:#d5cadb;font-size:9px;line-height:1.45}
@keyframes tip-in{from{opacity:0;transform:translateY(6px) scale(.96)}}
@media(prefers-reduced-motion:reduce){.tip{animation:none}}

/* Виды поля. Сетка — как было; пустота убирает её вовсе; развёртка кладёт
   строчки ЭЛТ; плитка делает из клеток чуть приподнятые квадраты. */
body[data-board-style='void'] .cell{border-color:#334661;box-shadow:inset 0 0 0 1px #18243a}
body[data-board-style='scan'] .board{background:repeating-linear-gradient(0deg,#03030a 0 2px,#08060f 2px 4px)}
body[data-board-style='scan'] .cell{border-color:#100c1a}
body[data-board-style='tile'] .cell{background:linear-gradient(158deg,#12101c,#08070e);box-shadow:inset 1px 1px 0 #201c30,inset -1px -1px 0 #05040a;border-color:#0b0912}

/* Фоны страницы. Развёртка поверх остаётся во всех. */
body[data-back-style='violet']{background:repeating-linear-gradient(0deg,#87ffd514 0 1px,transparent 1px 4px),radial-gradient(circle at 50% 0,#1c1236,#0a0618 48%,#040209 82%)}
body[data-back-style='ember']{background:repeating-linear-gradient(0deg,#87ffd514 0 1px,transparent 1px 4px),radial-gradient(circle at 50% 108%,#2a1208,#100710 52%,#040208 84%)}
body[data-back-style='ice']{background:repeating-linear-gradient(0deg,#87ffd514 0 1px,transparent 1px 4px),radial-gradient(circle at 50% -6%,#0d2233,#060d1a 50%,#02040a 84%)}

/* Панель видов. */
.looks{position:fixed;inset:0;z-index:120;display:grid;place-items:center;padding:18px;background:#03000699;backdrop-filter:blur(4px)}
.looks-card{width:min(94vw,520px);max-height:88dvh;overflow:auto;padding:16px;border:2px solid #4a5160;background:linear-gradient(140deg,#12131b,#06070b);box-shadow:8px 8px 0 #000}
.looks-card header{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:14px}
.looks-card header b{color:#efff00;font-size:13px;letter-spacing:.12em}
.looks-card section{margin-bottom:13px}
.looks-card small{display:block;margin-bottom:6px;color:#767c8b;font-size:8px;letter-spacing:.14em}
.looks-card section div{display:flex;flex-wrap:wrap;gap:6px}
.looks-card button{width:auto;min-width:0;min-height:0;padding:7px 12px;border:1px solid #3d4251;background:#0d0f16;color:#9aa0ae;font:bold 9px ui-monospace,monospace;letter-spacing:.08em;box-shadow:none}
.looks-card section button.on{border-color:#7dff00;background:#10210a;color:#b6ff6a}
.looks-card footer{display:flex;align-items:center;gap:12px;margin-top:6px;padding-top:11px;border-top:1px solid #23262f}
.looks-card footer span{flex:1;color:#767c8b;font-size:8px;line-height:1.5}
#looks-open{grid-column:1/-1;border-color:#efff00;background:#191505;color:#efff00}

/* Картинки шаров: пара «вид + цвет» — своя. Подложки под ними нет: пробовала
   класть вторым слоем пятно цвета на случай, если файл опоздает, — и оно
   заливало дырку, которую набор «ядро» оставляет нарочно. Полый шар с
   закрашенной серединой перестаёт быть полым. Картинки лежат на своём
   домене и в офлайн-копии; не приехавшая честнее выглядит пропуском. */
body[data-ball-style] .ball{border-radius:50%;background:center/contain no-repeat}
body[data-ball-style='glass'] .ball[data-colour='red']{background-image:url(art/ball-glass-red.png)}
body[data-ball-style='glass'] .ball[data-colour='yellow']{background-image:url(art/ball-glass-yellow.png)}
body[data-ball-style='glass'] .ball[data-colour='green']{background-image:url(art/ball-glass-green.png)}
body[data-ball-style='glass'] .ball[data-colour='blue']{background-image:url(art/ball-glass-blue.png)}
body[data-ball-style='glass'] .ball[data-colour='violet']{background-image:url(art/ball-glass-violet.png)}
body[data-ball-style='glass'] .ball[data-colour='pink']{background-image:url(art/ball-glass-pink.png)}
body[data-ball-style='glass'] .ball[data-colour='orange']{background-image:url(art/ball-glass-orange.png)}
body[data-ball-style='core'] .ball[data-colour='red']{background-image:url(art/ball-core-red.png)}
body[data-ball-style='core'] .ball[data-colour='yellow']{background-image:url(art/ball-core-yellow.png)}
body[data-ball-style='core'] .ball[data-colour='green']{background-image:url(art/ball-core-green.png)}
body[data-ball-style='core'] .ball[data-colour='blue']{background-image:url(art/ball-core-blue.png)}
body[data-ball-style='core'] .ball[data-colour='violet']{background-image:url(art/ball-core-violet.png)}
body[data-ball-style='core'] .ball[data-colour='pink']{background-image:url(art/ball-core-pink.png)}
body[data-ball-style='core'] .ball[data-colour='orange']{background-image:url(art/ball-core-orange.png)}
body[data-ball-style='crystal'] .ball[data-colour='red']{background-image:url(art/ball-crystal-red.png)}
body[data-ball-style='crystal'] .ball[data-colour='yellow']{background-image:url(art/ball-crystal-yellow.png)}
body[data-ball-style='crystal'] .ball[data-colour='green']{background-image:url(art/ball-crystal-green.png)}
body[data-ball-style='crystal'] .ball[data-colour='blue']{background-image:url(art/ball-crystal-blue.png)}
body[data-ball-style='crystal'] .ball[data-colour='violet']{background-image:url(art/ball-crystal-violet.png)}
body[data-ball-style='crystal'] .ball[data-colour='pink']{background-image:url(art/ball-crystal-pink.png)}
body[data-ball-style='crystal'] .ball[data-colour='orange']{background-image:url(art/ball-crystal-orange.png)}
body[data-ball-style='plasma'] .ball[data-colour='red']{background-image:url(art/ball-plasma-red.png)}
body[data-ball-style='plasma'] .ball[data-colour='yellow']{background-image:url(art/ball-plasma-yellow.png)}
body[data-ball-style='plasma'] .ball[data-colour='green']{background-image:url(art/ball-plasma-green.png)}
body[data-ball-style='plasma'] .ball[data-colour='blue']{background-image:url(art/ball-plasma-blue.png)}
body[data-ball-style='plasma'] .ball[data-colour='violet']{background-image:url(art/ball-plasma-violet.png)}
body[data-ball-style='plasma'] .ball[data-colour='pink']{background-image:url(art/ball-plasma-pink.png)}
body[data-ball-style='plasma'] .ball[data-colour='orange']{background-image:url(art/ball-plasma-orange.png)}
body[data-ball-style] .ball.wild{background:url(art/ball-wild.png) center/contain no-repeat;border:0;border-radius:50%;clip-path:none;box-shadow:none;position:relative;animation:none}
body[data-ball-style] .ball.wild::before,body[data-ball-style] .ball.wild::after{content:none}
body[data-ball-style] .ball.bomb{background:url(art/ball-bomb.png) center/contain no-repeat;border:0;border-radius:50%;clip-path:none;box-shadow:none;position:relative;animation:none}
body[data-ball-style] .ball.bomb::before,body[data-ball-style] .ball.bomb::after{content:none}
body[data-ball-style] .ball.stone{background:url(art/ball-stone.png) center/contain no-repeat;border:0;border-radius:50%;clip-path:none;box-shadow:none;position:relative;animation:none}
body[data-ball-style] .ball.stone::before,body[data-ball-style] .ball.stone::after{content:none}
/* Блик.

   Сергей просил «чтоб шарики как-то блестели иногда» — ключевое слово
   «иногда». Постоянный блик на всех сразу даёт обратное: плоскую наклейку с
   белым пятном. Поэтому пробег занимает десятую часть длинного цикла, а
   соседям заданы разные сдвиги и разные периоды — семь на пять даёт
   тридцать пять сочетаний, и они со временем расходятся, так что блеск
   никогда не выстраивается в такт.

   Объём даёт не столько блик, сколько тень: без неё шар лежит не на доске,
   а в ней. Тень статическая, не анимируется.

   Только обычные шары: у волшебного, бомбы и камня свои движения.
   `:not(.route-ball)` держит летящий в стороне — правило ниже весит
   столько же, и однажды такая пара уже сделала летящую бомбу невидимой. */
body[data-ball-style] .ball[data-colour]:not(.route-ball){
  position:relative;overflow:hidden;
  filter:drop-shadow(0 2px 3px rgba(0,0,0,.62))}
body[data-ball-style] .ball[data-colour]:not(.route-ball)::before{
  content:'';position:absolute;inset:-40%;pointer-events:none;
  background:linear-gradient(72deg,transparent 41%,rgba(255,255,255,.62) 47.5%,rgba(255,255,255,.1) 51.5%,transparent 57%);
  transform:translateX(-130%);
  animation:glint 9s linear infinite}
@keyframes glint{0%,90%{transform:translateX(-130%)}97%,100%{transform:translateX(130%)}}
body[data-ball-style] .cell:nth-child(7n+1) .ball[data-colour]:not(.route-ball)::before{animation-delay:-.4s}
body[data-ball-style] .cell:nth-child(7n+2) .ball[data-colour]:not(.route-ball)::before{animation-delay:-1.7s}
body[data-ball-style] .cell:nth-child(7n+3) .ball[data-colour]:not(.route-ball)::before{animation-delay:-3.1s}
body[data-ball-style] .cell:nth-child(7n+4) .ball[data-colour]:not(.route-ball)::before{animation-delay:-4.6s}
body[data-ball-style] .cell:nth-child(7n+5) .ball[data-colour]:not(.route-ball)::before{animation-delay:-5.9s}
body[data-ball-style] .cell:nth-child(7n+6) .ball[data-colour]:not(.route-ball)::before{animation-delay:-7.2s}
body[data-ball-style] .cell:nth-child(7n+7) .ball[data-colour]:not(.route-ball)::before{animation-delay:-8.5s}
body[data-ball-style] .cell:nth-child(5n+1) .ball[data-colour]:not(.route-ball)::before{animation-duration:9s}
body[data-ball-style] .cell:nth-child(5n+2) .ball[data-colour]:not(.route-ball)::before{animation-duration:10.4s}
body[data-ball-style] .cell:nth-child(5n+3) .ball[data-colour]:not(.route-ball)::before{animation-duration:11.9s}
body[data-ball-style] .cell:nth-child(5n+4) .ball[data-colour]:not(.route-ball)::before{animation-duration:13.1s}
body[data-ball-style] .cell:nth-child(5n+5) .ball[data-colour]:not(.route-ball)::before{animation-duration:14.6s}
@media(prefers-reduced-motion:reduce){
  body[data-ball-style] .ball[data-colour]:not(.route-ball)::before{animation:none;opacity:0}
}
body[data-ball-style] .ball.route-ball{position:absolute}

/* След особой фишки: клетки, через которые она прошла, горят, пока она летит.
   Первый вариант гасил каждую за полсекунды — к тому моменту, как бомба
   долетала, от следа не оставалось ничего, и смотреть было не на что. Теперь
   след держится весь полёт и исчезает вместе с ним: видно всю дорогу разом,
   откуда пошла и куда пришла. */
.cell.trail::before{
  content:'';position:absolute;inset:9%;border-radius:20%;pointer-events:none;z-index:1;
  background:radial-gradient(circle at 50% 50%,#ff9138aa,#ef435633 55%,transparent 78%);
  box-shadow:inset 0 0 0 1px #ff9138aa,0 0 10px #ff913855;
  animation:trail-in .16s ease-out both;
}
@keyframes trail-in{from{opacity:0;transform:scale(.6)}}
@media(prefers-reduced-motion:reduce){.cell.trail::before{animation:none}}

/* Красный и розовый больше не отличаются одной температурой цвета: красный
   шестигранный и с лучом, розовый — округлый четырёхлепестковый с кольцом.
   Силуэт остаётся различимым и в сером, и на клетке около сорока пикселей. */
body[data-ball-style] .ball[data-colour='red']{border-radius:0;clip-path:polygon(50% 1%,91% 25%,91% 75%,50% 99%,9% 75%,9% 25%)}
body[data-ball-style] .ball[data-colour='red']::after{content:'';position:absolute;inset:17%;background:linear-gradient(90deg,transparent 43%,#fff9 47% 53%,transparent 57%);mix-blend-mode:screen;pointer-events:none}
body[data-ball-style] .ball[data-colour='pink']{border-radius:34% 66% 34% 66%/66% 34% 66% 34%}
body[data-ball-style] .ball[data-colour='pink']::after{content:'';position:absolute;inset:21%;border:2px solid #fff9;border-radius:50%;box-shadow:0 0 5px #ffb5d8;pointer-events:none}

/* Обычная линия рассыпается на куски её же шаров. Бомба получает отдельную
   оранжевую ударную волну в точке посадки — её нельзя принять за сбор линии. */
.burst-particle{position:absolute;z-index:8;width:clamp(4px,3.4%,14px);aspect-ratio:1;pointer-events:none;border-radius:32% 68% 42% 58%;background:radial-gradient(circle at 32% 26%,#fff9 0 13%,var(--piece) 32%,color-mix(in srgb,var(--piece),#000 38%) 100%);box-shadow:0 0 7px var(--piece);transform:translate(-50%,-50%);animation:burst-shard .48s cubic-bezier(.14,.76,.28,1) forwards}
@keyframes burst-shard{0%{opacity:0;transform:translate(-50%,-50%) scale(.3)}15%{opacity:1}100%{opacity:0;transform:translate(calc(-50% + var(--dx)),calc(-50% + var(--dy))) rotate(var(--spin)) scale(.18)}}
.board.bomb-blast::before{content:'';position:absolute;z-index:7;left:var(--blast-x);top:var(--blast-y);width:18%;aspect-ratio:1;pointer-events:none;border:2px solid #ffd13a;border-radius:50%;box-shadow:0 0 18px #ff9138,0 0 40px #ef4356;background:radial-gradient(circle,#fff9 0 6%,#ff913899 14%,transparent 58%);animation:bomb-wave .46s ease-out forwards}
@keyframes bomb-wave{from{opacity:1;transform:translate(-50%,-50%) scale(.25)}to{opacity:0;transform:translate(-50%,-50%) scale(3.8)}}
@media(prefers-reduced-motion:reduce){.burst-particle{display:none}.board.bomb-blast::before{display:none}}

/* Вариант поля не имеет права съесть указатель пути: маршрут сильнее, чем
   сетка, пустота, развёртка или плитка. */
body[data-board-style] .cell.route{background:#141026;box-shadow:inset 0 0 0 1px #3a2f5c}

/* В горизонтали узкое место — высота, не ширина. После обязательных 44px
   вторичных кнопок и 54px отката правая колонка не имеет права расти вниз:
   статистика собирается в два ряда, рекорды остаются для обычного экрана, а
   доска получает ровно высоту после строки сообщения. */
@media(max-height:600px) and (orientation:landscape){
  body .stage{align-items:center}
  body .play-area{grid-template-rows:minmax(0,1fr) auto}
  body .board{height:min(calc(100dvh - 62px),calc(100vw - 270px));max-width:none}
  body .sidebar{align-content:center}
  body .sidebar-top{grid-template-columns:1fr 1fr}
  body .sidebar-top .preview{grid-column:1/-1}
  body .sidebar-bottom{grid-template-columns:1fr 1fr}
  body .sidebar-bottom .records{display:none}
}

/* PASS 06 · clearer reward feedback */
.score-pop{position:absolute;z-index:30;left:calc(var(--x)*100%/var(--size,9));top:calc(var(--y)*100%/var(--size,9));transform:translate(-50%,-50%);pointer-events:none;color:#efff00;font:900 clamp(12px,2vw,24px)/1 ui-monospace,monospace;letter-spacing:.05em;text-shadow:0 2px #000,0 0 14px #a6ff3a;white-space:nowrap;animation:score-pop .72s cubic-bezier(.16,.82,.25,1) forwards}@keyframes score-pop{0%{opacity:0;transform:translate(-50%,-20%) scale(.72)}16%{opacity:1;transform:translate(-50%,-58%) scale(1.08)}100%{opacity:0;transform:translate(-50%,-150%) scale(.92)}}@media(prefers-reduced-motion:reduce){.score-pop{animation:score-pop-reduced .35s ease-out forwards}@keyframes score-pop-reduced{to{opacity:0}}}
