/* ==========================================================================
   滑块验证（拼图）—— 浮层 + 拖动条
   ==========================================================================

   三个调用方共用这一份：平台登录页 /backend/login、商户登录页 /merchant/login、
   商户端「查询入驻进度」页。控件本身必须长得一模一样 —— 它是安全控件，各处
   版式不同只应体现在外围（卡片、标题），不该落在「验证长什么样」上。

   引入链路
   ---------------------------------------------------------------------------
     resources/views/auth/_slider-captcha.blade.php   结构（三个调用方 include）
       → 各页 @push('styles') 各引一行本文件
         → layouts/plain.blade.php → layouts/_head.blade.php 末尾 @stack('styles')
   所以它同样排在 rageframe.css / theme.css 之后，令牌（--rf-*）全部可用。

   ── 版式：整块浮在页面上（2026-10-24 改） ─────────────────────────────────
   以前是「常驻拖动条 + 按住才浮出的底图」，观感像表单项，且与提交的先后关系
   看不出来。现在整块是一个**浮层**，点提交（或点「获取验证码」）才出现：

     .rf-slider          铺满视口的定位层（display:flex 居中），默认收起
       .rf-slider-mask   半透明遮罩，点它收起（= 这次先不提交）
       .rf-slider-panel  浮起来的那块：标题栏 + 底图 + 拖动条
       input[hidden]     captcha_id —— 必须在**表单里**，所以整块留在 form 内

   ⚠️ 浮层是 position: fixed，而登录卡（.rf-auth-card）带 overflow: hidden。
      fixed 元素只有在祖先建立「包含块」（transform / filter / contain 等）时才
      会被裁 —— 这两个登录卡与入驻进度页的卡片链上都没有这类属性，所以浮层
      能盖到视口上。**改那些卡片时别顺手加 transform**：一加，浮层就会被裁进
      卡片里（现象是遮罩只剩卡片那么一块，且不报错）。

   几何：一切位置按比例算
   ---------------------------------------------------------------------------
   底图是固定自然尺寸（服务端 SliderCaptchaService::WIDTH × HEIGHT），页面里
   按容器宽度等比缩放。所以这里**不写任何像素级的图内坐标**，只做比例换算：

     拼图 left  = 滑块行程比 × (1 - 拼图宽/底图宽)
     拼图 top   = 缺口顶边 / 底图高
     拼图 宽    = 拼图宽 / 底图宽

   --rf-slider-w / -h / -pw / -y 四个变量由视图内联给出（服务端算出的自然像素），
   懒取图的场景（进度页）由脚本在拿到出参后写到根节点上。
   ========================================================================== */

/* ── 定位层：铺满视口、收起时不挡任何点击 ───────────────────────────────── */
.rf-slider {
    position: fixed;
    inset: 0;
    z-index: 1080; /* 压过外壳顶栏与弹层（Tabler modal 是 1055） */
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 16px;

    /* 收起用 visibility 而不只是 opacity：opacity:0 的元素仍在 Tab 序列与命中
       测试里（按钮会被「看不见地点到」）。visibility 会一并摘掉，且可参与
       过渡（transition 末尾把 visibility 一起切回来）。 */
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition:
        opacity var(--rf-dur) var(--rf-ease-curve),
        visibility 0s linear var(--rf-dur);
}

.rf-slider.is-open {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transition-delay: 0s;
}

/* ── 遮罩 ───────────────────────────────────────────────────────────────── */
/* 明暗两套皮各给一档不透明度：浅色皮下半透的黑最自然，深色皮要更重才压得住
   （用同一个值会让深色底看起来「没遮住」）。主色与页面底都随主题变，只有这层
   中性遮罩必须写死 —— 主题里的 --rf-ink 在深色皮里是**浅色**，拿它当遮罩会
   变成一层白雾。 */
.rf-slider-mask {
    position: absolute;
    inset: 0;
    background: rgba(23, 26, 33, .46);
}

[data-bs-theme="dark"] .rf-slider-mask {
    background: rgba(0, 0, 0, .62);
}

/* ── 浮起来的那块 ───────────────────────────────────────────────────────── */
/* 宽度取底图的自然宽（360），窄屏时收成 100%（两侧各留 16px 内边距）。
   描边 + 阴影两层：浮层要读成「浮在窗体之上」，只有一圈描边会像贴在页面里的块。 */
.rf-slider-panel {
    position: relative;
    width: min(100%, 360px);
    overflow: hidden;
    border-radius: var(--rf-r-sm);
    background: var(--rf-surface-2);
    box-shadow:
        0 0 0 1px var(--rf-line),
        var(--rf-shadow-pop);
    line-height: 0; /* 去掉 img 的基线空隙（否则底下多出 4px 白边） */
    transform: translate3d(0, 10px, 0) scale(.97);
    transition: transform var(--rf-dur) var(--rf-ease-curve);
}

.rf-slider.is-open .rf-slider-panel {
    transform: none;
}

/* ── 标题栏 ─────────────────────────────────────────────────────────────── */
/* 一行「安全验证」把这块定性成「浮起来的验证」，不是一张凭空出现的大图。
   line-height 要还原：外层为去图片基线空隙写死了 0，文字在这里会被压扁。 */
.rf-slider-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    height: 32px;
    padding-inline: 10px 4px;
    background: var(--rf-surface);
    border-bottom: 1px solid var(--rf-line-soft);
    line-height: 1.2;
}

.rf-slider-title {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: var(--rf-fs-xs);
    color: var(--rf-muted);
}

.rf-slider-title .rfi {
    font-size: 13px;
    color: var(--rf-accent);
}

.rf-slider-head-acts {
    display: inline-flex;
    align-items: center;
    gap: 2px;
}

/* ── 标题栏右侧两个按钮（换一张 / 关闭） ────────────────────────────────── */
/* 放在标题栏而不是探在图角上：图是随机色相，探一枚深底按钮会把「缺口的右上角」
   挡住一部分；挪到标题栏后图面完整，位置也和「这层验证」绑在一起。 */
.rf-slider-refresh,
.rf-slider-close {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    padding: 0;
    border: 0;
    border-radius: var(--rf-r-xs);
    background: transparent;
    color: var(--rf-faint);
    font-size: 13px;
    cursor: pointer;
    transition:
        background-color var(--rf-dur-fast) var(--rf-ease-curve),
        color var(--rf-dur-fast) var(--rf-ease-curve);
}

.rf-slider-refresh:hover,
.rf-slider-close:hover {
    background: var(--rf-surface-2);
    color: var(--rf-ink);
}

/* 换图途中点它没有意义（脚本也会挡），给个「忙着」的光标就够了 */
.rf-slider.is-checking .rf-slider-refresh {
    cursor: progress;
}

/* ── 图的画布 ───────────────────────────────────────────────────────────── */
/* 拼图块的定位基准 —— piece 的 top/left 都是相对它算的。
   图还没到（懒取第一张）时高度为 0：面板只剩「标题栏 + 拖动条」，脚本会把
   提示改成「正在加载验证图…」，不会出现一块空白。 */
.rf-slider-canvas {
    position: relative;
    min-height: 0;
}

.rf-slider-bg {
    display: block;
    width: 100%;
    height: auto;
}

/* 拼图块：位置由脚本按行程改写 left，这里只钉住「初始在哪、多大」。
   filter 给一道投影 —— 块拖过同色区域时要还能看出边界。 */
.rf-slider-piece {
    position: absolute;
    top: calc(100% * var(--rf-slider-y, 0) / var(--rf-slider-h, 180));
    left: 0;
    width: calc(100% * var(--rf-slider-pw, 55) / var(--rf-slider-w, 360));
    filter: drop-shadow(0 2px 3px rgba(0, 0, 0, .45));
    pointer-events: none;
    will-change: left;
}

/* 图上的操作提示：一张图不说话，得有一行字告诉人「拖这块」。白字压深色
   阴影，浅底深底都读得清；开拖之后自动淡出，别挡着看缺口。 */
.rf-slider-hint {
    position: absolute;
    inset-inline: 0;
    bottom: 6px;
    text-align: center;
    font-size: var(--rf-fs-xs);
    line-height: 1.4;
    color: #fff;
    text-shadow: 0 1px 3px rgba(0, 0, 0, .65);
    pointer-events: none;
    transition: opacity var(--rf-dur) var(--rf-ease-curve);
}

.rf-slider.is-dragging .rf-slider-hint,
.rf-slider.is-passed .rf-slider-hint {
    opacity: 0;
}

/* ── 拖动条 ─────────────────────────────────────────────────────────────── */
/* 与图之间留 10px：图是满幅贴边的，条子也贴边会读成「图下面粘了根控件」 */
.rf-slider-bar {
    position: relative;
    height: var(--rf-ctl-h-lg);
    margin: 10px;
    overflow: hidden;
    border-radius: var(--rf-r-sm);
    background: var(--rf-field-bg);
    box-shadow: 0 0 0 1px var(--rf-line);
    transition: box-shadow var(--rf-dur) var(--rf-ease-curve);
}

/* 通过：轨道转绿一圈（字与滑块同步换色，见下面的规则） */
.rf-slider.is-passed .rf-slider-bar {
    box-shadow: 0 0 0 1px color-mix(in oklab, var(--rf-ok) 55%, var(--rf-line));
}

/* 已拖过的那一段：淡淡的主色底 + 右缘一道竖线，进度看得见但不像「加载条」 */
.rf-slider-fill {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    width: 0;
    background: color-mix(in oklab, var(--rf-accent) 14%, transparent);
    border-inline-end: 1px solid color-mix(in oklab, var(--rf-accent) 34%, transparent);
}

.rf-slider-tip {
    position: absolute;
    inset: 0;
    z-index: 1; /* 压在进度段之上（进度段的右缘有一道竖线，别让它穿字） */
    display: flex;
    align-items: center;
    justify-content: center;
    padding-inline: 60px; /* 两侧各留出滑块宽度，字不会钻到滑块底下 */
    font-size: var(--rf-fs-sm);
    color: var(--rf-faint);
    pointer-events: none;
    transition: color var(--rf-dur) var(--rf-ease-curve);
}

.rf-slider.is-checking .rf-slider-tip {
    color: var(--rf-muted);
}

.rf-slider.is-passed .rf-slider-tip {
    color: var(--rf-ok-ink);
}

/* 通过后进度段一起转绿并**铺满整条**：滑块停在哪是用户拖到哪，但「通过」是整件事
   完成，条子留着一段空白会像没做完。宽度是脚本内联写的，所以这里要 !important。 */
.rf-slider.is-passed .rf-slider-fill {
    width: 100% !important;
    background: color-mix(in oklab, var(--rf-ok) 20%, transparent);
    border-inline-end: 0;
    transition: width .28s var(--rf-ease-curve-out);
}

/* 滑块本体：白底 + 主色图标，压在浅灰轨道上是一块「可以抓」的东西。
   touch-action: none —— 手机上按住拖动时不要触发页面滚动，否则拖到一半就
   变成下拉刷新。 */
.rf-slider-handle {
    position: absolute;
    top: 0;
    left: 0;
    z-index: 2; /* 压在进度段与提示字之上，滑块始终是一块完整的实体 */
    display: flex;
    align-items: center;
    justify-content: center;
    width: 52px;
    height: 100%;
    padding: 0;
    border: 0;
    border-radius: var(--rf-r-sm);
    background: var(--rf-surface);
    box-shadow: 0 0 0 1px var(--rf-line), var(--rf-shadow-sm);
    color: var(--rf-accent);
    font-size: 18px;
    cursor: grab;
    touch-action: none;
    transition:
        transform .18s var(--rf-ease-curve),
        background-color var(--rf-dur) var(--rf-ease-curve),
        color var(--rf-dur) var(--rf-ease-curve),
        box-shadow var(--rf-dur) var(--rf-ease-curve);
}

/* 悬停：图标加深一档，不动位置 —— 这个控件已经有一套动画，再晃会更吵 */
@media (hover: hover) {
    .rf-slider-handle:hover {
        color: var(--rf-accent-hover);
        box-shadow: 0 0 0 1px var(--rf-accent-line), var(--rf-shadow-sm);
    }
}

/* 拖动中：位置要跟手，不能再有过渡（有的话滑块会「追」着指针跑，手感发飘） */
.rf-slider.is-dragging .rf-slider-handle {
    cursor: grabbing;
    transition: none;
}

.rf-slider.is-checking .rf-slider-handle {
    cursor: progress;
}

/* 图还没到 / 请求在飞 / 已通过：锁住滑块（脚本也会挡，这里是看得见的那一半） */
.rf-slider.is-loading .rf-slider-handle,
.rf-slider.is-passed .rf-slider-handle {
    pointer-events: none;
}

/* 通过：滑块转成成功色并锁死（脚本同时会忽略后续拖动） */
.rf-slider.is-passed .rf-slider-handle {
    background: var(--rf-ok);
    color: #fff;
    cursor: default;
}

.rf-slider-handle:focus-visible {
    outline: 2px solid var(--rf-accent);
    outline-offset: 2px;
}

/* ── 滑块图标：拖拽箭头 / 通过对勾 ─────────────────────────────────────── */
/* 两个图标都渲染在 DOM 里，靠状态切换显示 —— 脚本不用碰图标，只管加类 */
.rf-slider-icon-done {
    display: none;
}

.rf-slider.is-passed .rf-slider-icon-drag {
    display: none;
}

.rf-slider.is-passed .rf-slider-icon-done {
    display: block;
    /* ⚠️ 别加 fill-mode: both —— 它会让元素停在 0% 帧（scale(.4) / opacity 0）。
       动画被跳过时（prefers-reduced-motion、页面在后台、或动画被覆盖）图标就永远
       看不见，而这里恰恰是「验证通过」唯一的状态提示。 */
    animation: rf-slider-pop .3s var(--rf-ease-curve-out);
}

@keyframes rf-slider-pop {
    0% { transform: scale(.4); opacity: 0; }
    60% { transform: scale(1.18); }
    100% { transform: scale(1); opacity: 1; }
}

/* ── 待操作提示：一道微光从文案上流过 ───────────────────────────────────── */
/* 动效只落在**提示文字**上，轨道（框）与滑块（图标）都完全静止。
   这不是随手选的落点 —— 试过两种都太吵：
   · 轨道上扫一条光带：光带被框裁着走，整条控件读起来像在闪，边框反而比字抢眼；
   · 图标往复：像有人在反复推这个控件，注意力一直被拽着。
   光只掠过字面，是「这里有一句话在说该干什么」的自然提示，视线落在字上，
   而字本来就是该看的地方。

   实现用 background-clip: text：渐变只落在字形上，字外面一点颜色都不漏。
   两层底色保证**任何一帧都是可读的**（动画被系统关掉、页面在后台、或
   动画被主题覆盖时都不会出现「字没了」）：
   · background-color 铺一层 --rf-faint —— 与被替换掉的原字色同一个令牌，
     所以静止时和改之前长得一模一样；
   · background-image 是那条会走的微光，两端回到同色，窗口永远看得见字。 */

.rf-slider:not(.is-dragging):not(.is-passed):not(.is-checking):not(.is-failed):not(.is-loading) .rf-slider-tip {
    background-color: var(--rf-faint);
    background-image: linear-gradient(
        90deg,
        var(--rf-faint) 0%,
        var(--rf-faint) 43%,
        color-mix(in oklab, #fff 70%, var(--rf-faint)) 50%,
        var(--rf-faint) 57%,
        var(--rf-faint) 100%
    );
    /* 尺寸 200% + 位置 100%→0% 的组合是算过的：光带在**屏幕上的宽度** =
       图宽的 20% ≈ 条宽的 40%，比一句提示略窄，所以看到的是「一道光走过去」，
       不是「整行一起变色」（尺寸取 300% 时带子接近整行宽，就是这个毛病）。
       位移则是从左沿一路扫到右沿，正好把居中的这行字完整掠一遍。 */
    background-size: 200% 100%;
    background-position: 100% 0;
    background-repeat: no-repeat;
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    /* 1.8s 里 1.4s 走完、剩下 0.4s 空着 —— 微光是「一晃而过」才像反光，
       慢下来就成了「字在变色」；空的那一下是让它有节奏，不是匀速机械 */
    animation: rf-slider-tip-sheen 1.8s linear infinite;
}

@keyframes rf-slider-tip-sheen {
    0% { background-position: 100% 0; }
    78%, 100% { background-position: 0% 0; }
}

/* ── 拼偏了 ─────────────────────────────────────────────────────────────── */
/* 条子横向抖一下：反馈要落在「刚拖的那个东西」上，只改一行字容易被当作没反应 */
@keyframes rf-slider-shake {
    0%, 100% { transform: translate3d(0, 0, 0); }
    25% { transform: translate3d(-6px, 0, 0); }
    75% { transform: translate3d(6px, 0, 0); }
}

.rf-slider.is-failed .rf-slider-bar {
    box-shadow: 0 0 0 1px var(--rf-danger);
    animation: rf-slider-shake .3s var(--rf-ease-curve);
}

.rf-slider.is-failed .rf-slider-tip {
    color: var(--rf-danger);
}

/* ── 错误槽（在表单里，不在浮层里） ─────────────────────────────────────── */
/* 有错才渲染（视图里 @error 包着整块），所以不预留高度：这里的话只来自服务端
   整页回来，不会在交互中突然出现 / 消失，没有「卡片跳一下」的风险。
   常驻一个空槽会让「密码 → 记住我」之间多出 30px 死空白，看着像漏了一个字段。 */
.rf-slider-error {
    margin: 4px 0 8px;
    font-size: var(--rf-fs-xs);
    line-height: 1.5;
    color: var(--rf-danger);
}

/* ── 降级：系统开了「减少动态效果」就别动 ───────────────────────────────── */
/* 前庭功能敏感的人会被循环动画影响；关掉动画不影响任何功能，只是不提示 */
@media (prefers-reduced-motion: reduce) {
    /* 微光停掉时**必须把字色还回去**：这套动效是 background-clip: text +
       -webkit-text-fill-color: transparent 撑起来的，动画一停、底色还在，字照样能读；
       但只要有人（主题、扩展、别的样式表）把 background-image 也清了，
       透明字就变成「一行空白」。这里显式把填充色还原成同一个令牌，最稳。 */
    .rf-slider-tip {
        animation: none !important;
        -webkit-text-fill-color: var(--rf-faint);
    }

    .rf-slider-icon-done,
    .rf-slider.is-failed .rf-slider-bar {
        animation: none !important;
    }

    .rf-slider,
    .rf-slider-panel,
    .rf-slider-handle {
        transition-duration: 1ms;
    }
}

/* 手机上高度更紧：面板收到 100%（两侧各留 16px），滑块保持 52px 的触控宽度不再缩 */
@media (max-width: 575.98px) {
    .rf-slider-tip {
        padding-inline: 56px;
        font-size: var(--rf-fs-xs);
    }
}
