/* ============================================================================
   signal.css —— 面板上「會動的訊號」那一層
   ----------------------------------------------------------------------------
   我們的 skills/_視覺語言.md 已經有「透明科技」的靜態質感(切角 / 受光面 / 角括號),
   但那一節管的是玻璃長什麼樣,**沒有管玻璃上面的東西怎麼動**。這份補的是後者。

   來源:reznikov-engineering.com / APEX(2026-08-29 實地扒下來的做法,數值重寫成我們的)。
   完整分析見 wiki/concepts/AI核心元件-用一個data-state換掉整組動畫.md

   ⚠ 顏色一律用 skills/_視覺語言.md 的變數,這裡不定義任何 hex。
     單獨開這份 demo 時才吃下面那組 fallback。
   ============================================================================ */

:root{
  /* fallback —— 正式頁面請引 _視覺語言 那組 oklch token,這裡只是讓單檔也開得起來 */
  --cyan:   oklch(0.800 0.1377 205);
  --amber:  oklch(0.800 0.1377 78);
  --slate:  oklch(0.800 0.0413 245);
  --ink:    oklch(0.935 0.0035 65);
  --mut:    oklch(0.655 0.0035 65);
  --void:   oklch(0.155 0.0070 65);
  --edge:   oklch(0.310 0.0070 65);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
  /* 擴散專用(easeOutSine)—— 幾乎等速,只在尾巴輕輕減速。
     ⚠ 不要拿 --ease-out(easeOutExpo)去推擴散波:它在前 10% 就走完約六成路程,
     讀起來是「彈出去」不是「漫出去」。2026-08-29 使用者退貨的正是那個手感。 */
  --ease-swell: cubic-bezier(0.61, 1, 0.88, 1);

  /* 訊號層的五個節拍 —— 全站只有這五個,不要每個元件自己挑一個數字 */
  --beat-fast:  1.2s;   /* 心跳:閃爍、游標 */
  --beat-mid:   2.4s;   /* 呼吸:環、光暈 */
  --beat-slow:  3.5s;   /* 巡邏:掃描線 */
  --beat-swell: 9s;     /* 漣漪:雷達波擴散(2026-08-29 加,理由見 ② 那節) */
  --beat-drift: 18s;    /* 漂移:自轉、背景 */
  --stagger:    .11s;   /* 一群東西錯開的間隔 */

  /* 🔴 四個狀態變數的保底值(2026-08-29 加)—— 這不是重複,是保險。
     下面的狀態機把它們掛在 [data-state] 上,而**元素沒有 [data-state] 祖先時它們是空的**;
     空值餵進 calc() → 整條 calc 失效 → `animation-duration` 掉回 0s → **那個動畫直接不動**。
     而畫面上看起來只是「這頁比較安靜」,不報錯、不吵、沒有任何檢查抓得到。
     活教材:jarvis/morning 的四圈雷達波就是這樣死的,而它引了這份檔、class 也寫對了。 */
  --sig-rate: 1;
  --sig-tide: 1;
  --sig-amp:  1;
  --sig-hue:  var(--cyan);
}

/* ---------------------------------------------------------------------------
   ① 狀態機 —— 這是整份最重要的一塊
   一個 data-state 換掉整組動畫。JS 只改一個字串,視覺全部交給 CSS。
   --------------------------------------------------------------------------- */
[data-state]{ --sig-rate: 1; --sig-tide: 1; --sig-hue: var(--cyan); --sig-amp: 1; }
[data-state="idle"]    { --sig-rate: 1;   --sig-tide: 1;   --sig-hue: var(--cyan);  --sig-amp: .55; }
[data-state="thinking"]{ --sig-rate: .55; --sig-tide: .92; --sig-hue: var(--amber); --sig-amp: 1;   }
[data-state="speaking"]{ --sig-rate: .45; --sig-tide: .88; --sig-hue: var(--cyan);  --sig-amp: 1.3; }
[data-state="alert"]   { --sig-rate: .35; --sig-tide: .8;  --sig-hue: var(--crimson); --sig-amp: 1.5; }
[data-state="offline"] { --sig-rate: 3;   --sig-tide: 1.6; --sig-hue: var(--slate); --sig-amp: .25; }
/* alert 2026-08-29 加:原本只有四個狀態,而「出事了」沒有位置 ——
   拿 thinking 的金色去講故障會把它講得太輕(金 = 等人決定,紅 = 阻斷/失敗)。 */

/* 🔴 為什麼有兩檔倍率(--sig-rate 與 --sig-tide,2026-08-29 加)
   --sig-rate 管的是**訊號**(閃爍、掃描、指示燈):出事了就該催,alert 直接乘 .35 是對的。
   --sig-tide 管的是**氛圍**(擴散波這種一直在背景漫的東西):同一個 .35 套上去,
   9 秒的漣漪會變成 3 秒的頻閃 —— 那不是緊張,那是壞掉。
   所以氛圍層自己一檔,整個範圍壓在 .8 ~ 1.6 之間:狀態換了看得出來,但永遠還在呼吸。
   活教材:system 面板進 alert 後,雷達波實測只剩 1.225 秒一輪(3.5 × .35),
   使用者的原話是「散發的太快了」。**沒讀 computed style 之前,我以為病在基礎節拍。** */

/* 四個變數往下傳,底下每個動效都乘上去 —— 所以新增狀態不必動任何 keyframes */

/* ---------------------------------------------------------------------------
   ② 雷達波 sig-ping —— 一個圓從球面漫出去,一路淡掉,最後化在空間裡
   用 SVG circle 的 r,不是 transform:scale(那樣線寬會一起變粗)

   🔴 為什麼拆成兩支 keyframes(r 一支、透明度一支)
   CSS 的緩動是**逐段**套用的:某個屬性只寫在 0% 與 100%,而中間有別的 keyframe 時,
   那條曲線會在每個 keyframe 界線上重新開始 —— r 就會走成一節一節的頓挫。
   要「透明度多幾個轉折」又要「半徑一路平滑」,唯一的辦法是把它們分開跑,
   同長度、同 delay,所以永遠同步。
   （改回一支的話畫面上不會報錯,只會變得有點抽搐 —— 這種壞法只有眼睛看得到。）
   --------------------------------------------------------------------------- */
@keyframes sig-ping-r{
  from { r: var(--ping-from, 40); stroke-width: 1.5; }
  to   { r: var(--ping-to, 120);  stroke-width: .3;  }
}
/* 兩端都是 0:從球面「長出來」,不是憑空跳出來;末段拖得很長,讓它化開而不是被切斷 */
@keyframes sig-ping-fade{
  0%   { opacity: 0; }
  16%  { opacity: calc(.34 * var(--sig-amp)); }
  48%  { opacity: calc(.19 * var(--sig-amp)); }
  76%  { opacity: calc(.07 * var(--sig-amp)); }
  100% { opacity: 0; }
}
/* 🔴 backwards 不能省:四個波是靠 animation-delay 錯開的,而**還沒輪到的那幾個**
   在等待期間顯示的是元素本來的樣子 —— 也就是 opacity 1、貼在球面上。
   開頁那幾秒會看到一圈死白的線疊在球邊,然後才「啪」一聲開始跑。
   backwards = 等待期間先套 0% 那格(透明),所以它從一開始就不存在。 */
.sig-ping{
  animation: sig-ping-r    calc(var(--beat-swell) * var(--sig-tide)) var(--ease-swell)  infinite backwards,
             sig-ping-fade calc(var(--beat-swell) * var(--sig-tide)) var(--ease-in-out) infinite backwards;
}

/* ---------------------------------------------------------------------------
   ③ 抽搐 sig-flicker —— 這一條的祕訣是「絕大多數時間不閃」
   93% 的時間 opacity 是 1,只在最後那一小段連抽五下。
   規律閃爍讀起來是裝飾;不規律的抽搐讀起來是「這是一台真的機器」。
   --------------------------------------------------------------------------- */
@keyframes sig-flicker{
  0%,86%,94%,100% { opacity: 1; }
  87% { opacity: .12; } 88% { opacity: .82; }
  90% { opacity: .18; } 91% { opacity: .95; } 92% { opacity: .34; }
}
.sig-flicker{ animation: sig-flicker calc(6s * var(--sig-rate)) steps(1, end) infinite; }

/* ---------------------------------------------------------------------------
   ④ 呼吸 sig-breathe —— 只動 opacity,不動幾何。給環、光暈、待命指示燈
   --------------------------------------------------------------------------- */
@keyframes sig-breathe{
  0%,100%{ opacity: calc(.62 * var(--sig-amp)); }
  50%    { opacity: calc(1   * var(--sig-amp)); }
}
.sig-breathe{ animation: sig-breathe calc(var(--beat-mid) * var(--sig-rate)) var(--ease-in-out) infinite; }
/* 大環、光暈這種「一直在背景漫」的加掛這個(2026-08-29 加):改走氛圍那一檔,而且再放慢一倍。
   為什麼不直接改上面那條:同一個 class 也給**待命指示燈**用,而指示燈在 alert 本來就該跳得快。
   活教材:system 面板進 alert 後,球外那圈金環實測 0.84 秒一次 —— 那是頻閃不是呼吸。 */
.sig-breathe--tide{ animation-duration: calc(var(--beat-mid) * 2 * var(--sig-tide)); }

/* ---------------------------------------------------------------------------
   ⑤ 掃描線 sig-sweep —— 一條亮線由上而下走完整個容器
   頭尾各留 4% 淡入淡出,不然接縫會看得到
   --------------------------------------------------------------------------- */
@keyframes sig-sweep{
  0%   { opacity: 0; transform: translateY(-4%); }
  4%   { opacity: calc(1 * var(--sig-amp)); }
  96%  { opacity: calc(1 * var(--sig-amp)); }
  100% { opacity: 0; transform: translateY(104%); }
}
.sig-sweep{ animation: sig-sweep calc(var(--beat-slow) * 2 * var(--sig-rate)) linear infinite; } /* motion-ok: 掃描線是等速平移,同等速自轉那條例外 —— 加減速會讓它不像機器在掃 */

/* ---------------------------------------------------------------------------
   ⑥ 故障 sig-glitch —— clip-path 隨機切片 + 一兩像素位移,兩層錯開
   ⚠ 這一招很容易過量。只給「錯誤狀態」與「解密瞬間」,不要當常態裝飾。
   --------------------------------------------------------------------------- */
@keyframes sig-glitch-a{
  0%,100%{ clip-path: inset(38% 0 60%); transform: translateX(-2px); }
  20% { clip-path: inset(90% 0 2%);  transform: translateX(2px); }
  40% { clip-path: inset(44% 0 2%);  transform: translateX(0); }
  60% { clip-path: inset(24% 0 58%); transform: translateX(-1px); }
  80% { clip-path: inset(54% 0 8%);  transform: translateX(1px); }
}
@keyframes sig-glitch-b{
  0%,100%{ clip-path: inset(10% 0 84%); transform: translateX(2px); }
  20% { clip-path: inset(66% 0 4%);  transform: translateX(-2px); }
  40% { clip-path: inset(32% 0 42%); transform: translateX(1px); }
  60% { clip-path: inset(76% 0 18%); transform: translateX(-1px); }
  80% { clip-path: inset(2% 0 70%);  transform: translateX(0); }
}
.sig-glitch{ position: relative; }
.sig-glitch::before,.sig-glitch::after{
  content: attr(data-text); position: absolute; inset: 0; pointer-events: none;
}
.sig-glitch::before{ color: var(--cyan);  animation: sig-glitch-a .7s steps(2,end) infinite; }
.sig-glitch::after { color: var(--amber); animation: sig-glitch-b .7s steps(2,end) infinite; }

/* ---------------------------------------------------------------------------
   ⑦ 自轉 sig-orbit —— 唯一准用 linear 的地方(等速自轉,見 _視覺語言 二之二①)
   --------------------------------------------------------------------------- */
@keyframes sig-orbit{ from{ transform: rotate(0); } to{ transform: rotate(1turn); } }
.sig-orbit    { animation: sig-orbit var(--beat-drift) linear infinite; transform-origin: center; } /* motion-ok: 等速自轉,_視覺語言 二之二① 明列的例外 */
.sig-orbit-rev{ animation: sig-orbit calc(var(--beat-drift) * 1.6) linear infinite reverse; transform-origin: center; } /* motion-ok: 同上,等速自轉 */

/* ---------------------------------------------------------------------------
   ⑧ 漂移 sig-drift —— 不回原點的三角形路徑,讓靜止的東西看起來活著
   --------------------------------------------------------------------------- */
@keyframes sig-drift{
  0%,100%{ transform: translate(0,0); }
  33% { transform: translate(4px,-8px); }
  66% { transform: translate(-4px,4px); }
}
.sig-drift{ animation: sig-drift calc(var(--beat-drift) / 1.5) var(--ease-in-out) infinite; }

/* ---------------------------------------------------------------------------
   ⑨ 錯開 —— 一群相同的東西各差一個 --stagger,整群才會讀成「一個活的系統」
   不錯開的話 N 個同步跳動 = 一個東西在跳,不是 N 個
   --------------------------------------------------------------------------- */
.sig-stagger > *:nth-child(1){ animation-delay: calc(var(--stagger) * 0); }
.sig-stagger > *:nth-child(2){ animation-delay: calc(var(--stagger) * 1); }
.sig-stagger > *:nth-child(3){ animation-delay: calc(var(--stagger) * 2); }
.sig-stagger > *:nth-child(4){ animation-delay: calc(var(--stagger) * 3); }
.sig-stagger > *:nth-child(5){ animation-delay: calc(var(--stagger) * 4); }
.sig-stagger > *:nth-child(6){ animation-delay: calc(var(--stagger) * 5); }
.sig-stagger > *:nth-child(7){ animation-delay: calc(var(--stagger) * 6); }
.sig-stagger > *:nth-child(8){ animation-delay: calc(var(--stagger) * 7); }
/* 波與波之間要留白的那種(雷達),間隔拉大。
   🔴 用整輪的四分之一,不寫死秒數(2026-08-29 改):四個波要平均分布在一輪裡,
   而「一輪多長」是 --beat-swell × --sig-tide 決定的。寫死 .9s 的話,狀態一換
   就會四個波擠在前半圈、後半圈空著 —— 那正是「太快」看起來的樣子。 */
.sig-stagger-wide > *:nth-child(1){ animation-delay: 0s; }
.sig-stagger-wide > *:nth-child(2){ animation-delay: calc(var(--beat-swell) * var(--sig-tide) * .25); }
.sig-stagger-wide > *:nth-child(3){ animation-delay: calc(var(--beat-swell) * var(--sig-tide) * .5); }
.sig-stagger-wide > *:nth-child(4){ animation-delay: calc(var(--beat-swell) * var(--sig-tide) * .75); }

/* ---------------------------------------------------------------------------
   ⑩ 接上 / 沒接上 —— 這是資訊,不是裝飾
   一張能力圖上「還沒接的那幾格」要看得出來是灰的,不要假裝全部都亮著。
   --------------------------------------------------------------------------- */
[data-link="on"] { color: var(--sig-hue, var(--cyan)); opacity: 1; }
[data-link="off"]{ color: var(--slate); opacity: .38; }
[data-link="off"] *{ animation: none !important; }   /* motion-ok: 這是關掉動畫不是動畫,沒有緩動可言(沒接上的東西不准動) */

/* ---------------------------------------------------------------------------
   ⑪ 無障礙 —— 一律尊重(_視覺語言 二之二⑥)
   --------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce){
  *,*::before,*::after{
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
}
