html,
body,
#app {
  height: 100%;
  margin: 0;
  padding: 0;
}

/*
 * 首屏遮罩在应用脚本执行前就已绘制，故取令牌并给出回退值。
 * 令牌由 styles/blade/boot.scss 在首帧前送达：构建产物并入主样式表，开发态经 index.html 的
 * <link> 直出。回退值兜的是该表缺席的场景。此处增删令牌须同步其 $blade-boot-keys。
 *
 * 动效一律只动 transform 与 opacity。此刻主线程正在解析并执行应用脚本，
 * 落在主线程上的动画（背景位置、宽度、滤镜）会随之一并卡住，只有合成器能独立走完。
 */
#loader-wrapper {
  position: fixed;
  inset: 0;
  z-index: 99999;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: var(--blade-surface-page, #f4f5f7);
  font-family: var(
    --blade-font-family,
    system-ui,
    -apple-system,
    'Segoe UI',
    Roboto,
    'PingFang SC',
    sans-serif
  );
  transition: opacity 0.42s cubic-bezier(0.2, 0.8, 0.2, 1),
    transform 0.42s cubic-bezier(0.2, 0.8, 0.2, 1), visibility 0.42s;
}

/* 挂载完成后由 main.ts 打上，淡出结束即移除。visibility 与 opacity 同步过渡，收尾前不截获指针 */
#loader-wrapper.is-leaving {
  opacity: 0;
  visibility: hidden;
  transform: scale(1.012);
  pointer-events: none;
}

.loader-stage {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
}

/*
 * 制图网格：硬边线条而非渐变，避开大面积渐变在 8 位色深下的色带；
 * 由遮罩向四周化开，只在字面周围留一层淡纹理，作这一屏的尺度参照。
 */
.loader-grid {
  position: absolute;
  left: 50%;
  top: 50%;
  width: min(960px, 84vw);
  height: min(560px, 62vh);
  transform: translate(-50%, -50%);
  background-image: linear-gradient(
      to right,
      rgba(var(--blade-brand-rgb, 37, 99, 235), 0.13) 1px,
      transparent 1px
    ),
    linear-gradient(to bottom, rgba(var(--blade-brand-rgb, 37, 99, 235), 0.13) 1px, transparent 1px);
  background-size: 44px 44px;
  -webkit-mask-image: radial-gradient(ellipse closest-side, #000 8%, transparent 76%);
  mask-image: radial-gradient(ellipse closest-side, #000 8%, transparent 76%);
  animation: loader-fade 1.1s cubic-bezier(0.2, 0.8, 0.2, 1) 0.1s both;
}

/*
 * 字面是这一屏的主体，尺度远大于底注与光条。字距按令牌放大三倍取用：
 * 这一档在浅色与暗色是负值、在锐利主题是正值，放大后大字随主题收紧或展开。
 */
.loader-mark {
  position: relative;
  overflow: hidden;
  font-size: clamp(42px, 6.2vw, 88px);
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: calc(var(--blade-tracking-display, -0.3px) * 3);
  color: var(--blade-text-primary, #111827);
}

/*
 * 擦出：与页底同色的遮片自左向右让开，字面随之露出，比整体淡入多一个方向。
 * 左缘留一段渐变而非硬边，字面渐次显形，不出现一道横扫的硬边。
 *
 * 遮片向左外扩一个渐变段的宽度：起始位置若与字面左边界齐平，那段渐变正压在首字母上，
 * 第一帧就会透出半个字。外扩后起点处落在字面上的是实色段，字面自零开始显形。
 */
.loader-mark__wipe {
  position: absolute;
  inset: 0 0 0 -22px;
  background-image: linear-gradient(90deg, transparent, var(--blade-surface-page, #f4f5f7) 22px);
  animation: loader-wipe 0.92s cubic-bezier(0.62, 0, 0.16, 1) 0.22s both;
}

/* 底注：字距展开、档位压弱，可读但从属于字面 */
.loader-sub {
  margin-top: clamp(16px, 1.8vw, 26px);
  font-size: clamp(13px, 1.55vw, 18px);
  letter-spacing: 0.42em;
  text-indent: 0.42em;
  color: var(--blade-text-tertiary, #9ca3af);
  animation: loader-rise 0.62s cubic-bezier(0.2, 0.8, 0.2, 1) 0.78s both;
}

/*
 * 持续状态：一段实色光在发丝线上往返。取不定量式：加载时长不可知，不给出进度比例。
 * 光段取不透明的主色、硬边收口，轨道取中性色的淡档：主色压淡后叠在深色底上会发浊
 * （亮黄叠黑底呈土黄），光段与轨道都不走主色的半透明档。
 */
.loader-bar {
  /* 光段占条宽的比例，光段宽度与往返行程共用 */
  --loader-seg: 0.34;

  position: relative;
  overflow: hidden;
  margin-top: clamp(26px, 3vw, 42px);
  width: clamp(200px, 24vw, 340px);
  height: 2px;
  border-radius: var(--blade-radius-full, 999px);
  background-color: color-mix(in srgb, var(--blade-text-tertiary, #9ca3af) 22%, transparent);
  animation: loader-fade 0.62s cubic-bezier(0.2, 0.8, 0.2, 1) 0.92s both;
}

/* 往返中光始终有一截留在条内，不出现空档；行程见 loader-slide */
.loader-bar > i {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: calc(var(--loader-seg) * 100%);
  border-radius: inherit;
  background-color: var(--blade-brand, #2563eb);
  animation: loader-slide 1.75s cubic-bezier(0.65, 0, 0.35, 1) infinite alternate;
}

/*
 * 主题专属造型：字标上方的徽记，以及替代光条的进度模块。默认不显示，
 * 由 styles/blade/loader 在所属主题下启用；进度模块的宽度、间距与入场节奏在此统一，与光条同一时序
 */
.loader-emblem,
.loader-hud,
.loader-orbit,
.loader-mech {
  display: none;
}

.loader-hud,
.loader-orbit,
.loader-mech {
  box-sizing: border-box;
  width: clamp(260px, 27vw, 380px);
  margin-top: clamp(26px, 3vw, 42px);
  animation: loader-fade 0.62s cubic-bezier(0.2, 0.8, 0.2, 1) 0.92s both;
}

/* 入场延后起步：网格 100ms 起淡入、字面 220ms 起擦出，此前挂载完的会话只看到纯底色，不会闪过半截加载态 */
@keyframes loader-fade {
  from {
    opacity: 0;
  }

  to {
    opacity: 1;
  }
}

@keyframes loader-rise {
  from {
    opacity: 0;
    transform: translateY(8px);
  }

  to {
    opacity: 1;
    transform: none;
  }
}

/* 遮片自左让开：字面随之由左向右显形 */
@keyframes loader-wipe {
  from {
    transform: translateX(0);
  }

  to {
    transform: translateX(101%);
  }
}

/*
 * 行程左右对称：起止两端都有光段自身的 52% 伸出条外。translateX 的百分比以光段自身宽度为基准，
 * 条宽即 100% / loader-seg，减去留在条内的 48% 就是终点，光段比例改动时终点随之换算
 */
@keyframes loader-slide {
  from {
    transform: translateX(-52%);
  }

  to {
    transform: translateX(calc(100% / var(--loader-seg) - 48%));
  }
}

/*
 * 声明减少动效时只留进度的往返并放慢：整屏静止看起来像卡死，
 * 传达不出「加载中」。主题进度模块各自的放慢写在 styles/blade/loader 的同名媒体查询里。
 */
@media (prefers-reduced-motion: reduce) {
  .loader-grid,
  .loader-mark__wipe,
  .loader-sub,
  .loader-bar,
  .loader-hud,
  .loader-orbit,
  .loader-mech {
    animation: none;
  }

  /* 动画被取消后遮片会停在起始位置、整块盖住字面，须在此直接让开 */
  .loader-mark__wipe {
    display: none;
  }

  .loader-bar > i {
    animation-duration: 3.5s;
  }
}
