/* ==========================================================================
   许愿墙 2.9 — 前台样式（青春校园 / 手账风）
   ========================================================================== */
:root {
    --ink: #343549;
    --ink-soft: #55566a;
    --muted: #7f8194;
    --line: rgba(52, 53, 73, .11);
    --mint: #dff5e8;
    --mint-deep: #2f7d5b;
    --mint-dark: #245f47;
    --sky: #e2f1ff;
    --cream: #fff6c9;
    --pink: #fde6ef;
    --lavender: #efe8ff;
    --paper: #fffdf7;
    --white: #ffffff;
    --shadow-sm: 0 4px 14px rgba(58, 73, 65, .07);
    --shadow-md: 0 14px 36px rgba(58, 73, 65, .12);
    --shadow-lg: 0 26px 60px rgba(58, 73, 65, .16);
    --radius: 18px;
    --font-sans: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC", system-ui, -apple-system, "Segoe UI", sans-serif;
    --font-hand: "Kaiti SC", "STKaiti", "KaiTi", "Songti SC", "Noto Serif SC", serif;
    --container: 1120px;
    /* 纸飞机：卡片纸面和详情页信纸共用同一张图，只改尺寸和落点。
       画在背景层而不是加 DOM 元素：一页 25 张卡片，加元素等于加 150 个节点，
       而背景层天然在文字底下，不用再跟 .card-top / .cat-pill 抢 z-index。
       他要的是「铅笔在信纸上画的小涂鸦」：浅灰蓝（#6f8496）低透明度、折面只分三档淡淡明暗，
       机头朝右上飞，尾巴拖一根虚线轨迹 —— 所以不用墨色实心，靠 fill-opacity .13/.22/.16 出体积 */
    --plane: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 84'%3E%3Cg stroke='%236f8496' stroke-width='1.6' stroke-linejoin='round' stroke-linecap='round'%3E%3Cpath d='M106,16 L16,42 L44,62 Z' fill='%236f8496' fill-opacity='.13' stroke-opacity='.30'/%3E%3Cpath d='M106,16 L44,62 L82,74 Z' fill='%236f8496' fill-opacity='.22' stroke-opacity='.30'/%3E%3Cpath d='M44,62 L82,74 L54,78 Z' fill='%236f8496' fill-opacity='.16' stroke-opacity='.28'/%3E%3Cpath d='M106,16 L44,62' fill='none' stroke-opacity='.36'/%3E%3C/g%3E%3Cpath d='M6,82 C18,80 26,76 36,68' fill='none' stroke='%236f8496' stroke-opacity='.30' stroke-width='1.6' stroke-linecap='round' stroke-dasharray='5 7'/%3E%3C/svg%3E");
}

*,
*::before,
*::after {
    box-sizing: border-box;
}

html {
    /* 不要写 scroll-behavior: smooth —— 刷新时的滚动还原会被它改成一段滑动，
       而且需要平滑的地方都由 JS 显式请求（回到顶部、极验定位），已按 prefers-reduced-motion 降级 */
    -webkit-text-size-adjust: 100%;
    scrollbar-gutter: stable;
}

body {
    margin: 0;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    color: var(--ink);
    font-family: var(--font-sans);
    font-size: 16px;
    line-height: 1.7;
    background-color: #f8fcf7;
    overflow-x: hidden;
}

/* 页面背景层：用一块固定在视口上的图层承担背景图，而不是 background-attachment: fixed
   （后者每次滚动都要重绘整张背景）。
   主题背景图由 layout.php 通过 --page-bg / --page-bg-size 传进来，没传时用下面这组默认渐变 */
body::after {
    content: "";
    position: fixed;
    inset: 0;
    z-index: -2;
    pointer-events: none;
    background-image: var(--page-bg,
        radial-gradient(920px 500px at 8% -6%, rgba(223, 245, 232, .9), transparent 68%),
        radial-gradient(760px 480px at 96% 4%, rgba(226, 241, 255, .92), transparent 66%),
        radial-gradient(680px 520px at 46% 108%, rgba(255, 246, 201, .55), transparent 70%),
        linear-gradient(180deg, #fbfdf8 0%, #fffdf4 46%, #f5fbff 100%));
    background-size: var(--page-bg-size, auto);
}

body::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    opacity: .5;
    background-image: radial-gradient(rgba(47, 125, 91, .13) .7px, transparent .7px);
    background-size: 22px 22px;
    mask-image: linear-gradient(180deg, #000 0%, transparent 60%);
}

img {
    max-width: 100%;
    height: auto;
}

a {
    color: var(--mint-deep);
    text-decoration: none;
    transition: color .18s ease, opacity .18s ease;
}

a:hover {
    color: var(--mint-dark);
}

button,
input,
textarea,
select {
    font: inherit;
    color: inherit;
}

h1, h2, h3, h4, p, ul, ol, figure {
    margin: 0;
}

ul, ol {
    padding: 0;
    list-style: none;
}

:focus-visible {
    outline: 3px solid rgba(47, 125, 91, .45);
    outline-offset: 3px;
    border-radius: 6px;
}

.container {
    width: 100%;
    max-width: var(--container);
    margin: 0 auto;
    padding: 0 24px;
}

.sr-only {
    position: absolute !important;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

.skip-link {
    position: absolute;
    left: 16px;
    top: -64px;
    z-index: 100;
    padding: 10px 18px;
    border-radius: 0 0 12px 12px;
    background: var(--mint-deep);
    color: #fff;
}

.skip-link:focus {
    top: 0;
    color: #fff;
}

/* ---------- 顶部导航 ---------- */
/* container 这个类挂在 header 上（不是 header-inner）：这样 header 的内宽
   就等于下方 .category-strip 的外宽，条子做到逐像素对齐，24/18 两处内距由 .container 自己管，
   不在这里重复写数字。上下 8px 用 longhand 只补纵向，别用 padding 简写把横向内距抹成 0 */
.site-header {
    position: sticky;
    top: 0;
    z-index: 50;
    padding-top: 8px;
    padding-bottom: 8px;
}

.header-inner {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
    align-items: center;
    gap: 18px;
    min-height: 58px;
    /* 内距 14 = 下面那条分类条自己的左右内距，两条同宽同内距才是同一个东西 */
    padding: 0 14px;
    /* 55% 不透明：底色让出近一半，滚动时背景图从条子后面透出来，字仍压在模糊层上，读数不受影响 */
    background: rgba(255, 253, 246, .55);
    backdrop-filter: blur(14px) saturate(1.1);
    -webkit-backdrop-filter: blur(14px) saturate(1.1);
    border: 1px solid rgba(47, 125, 91, .1);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
}

.brand {
    display: inline-flex;
    align-items: center;
    color: var(--ink);
    justify-self: start;
}

.brand:hover {
    color: var(--ink);
}

/* 34 不是随手定的：它等于右边「我要许愿」那颗按钮的高度（.btn-small 的 min-height 34，
   窄屏字号降到 12px 也仍被 min-height 钉在 34），logo 必须在任何可见档位跟它同高。 */
.brand-mark {
    display: grid;
    place-items: center;
    width: 34px;
    height: 34px;
    border-radius: 11px 9px 12px 8px;
    background: var(--cream);
    color: #866a22;
    box-shadow: inset 0 0 0 1px rgba(176, 138, 46, .16), 0 4px 10px rgba(176, 138, 46, .12);
}

.brand-mark svg {
    width: 20px;
    height: 20px;
}

/* 后台上传了网站 LOGO 时图自己说了算：纸片底色、内描边、投影撤掉，盒子改按图片宽度撑开。
   高度仍锁 34（跟右边「我要许愿」按钮同高这条不能破），最宽 140 是防止超宽字标把导航挤变形。 */
.brand-mark.is-image {
    width: auto;
    background: none;
    box-shadow: none;
}

.brand-mark.is-image img {
    display: block;
    width: auto;
    height: 34px;
    max-width: 140px;
    object-fit: contain;
}

.header-actions {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    justify-self: center;
    min-width: 0;
}

.header-actions .btn {
    flex: 0 0 auto;
}

/* 当前页那一颗：底色保持浅的（他要求这两颗任何状态都浅于「我要许愿」，所以不再像以前那样翻成深绿），
   只加一圈内描边当选中记号——不改变盒子尺寸，宽度、行高、和旁边那颗的间距都不动 */
.header-actions .btn.is-current {
    background: var(--mint);
    color: var(--mint-dark);
    box-shadow: inset 0 0 0 1.5px rgba(47, 125, 91, .45);
}

.header-search {
    position: relative;
    justify-self: end;
    width: 100%;
    max-width: 187px;
}

.header-search input {
    width: 100%;
    height: 34px;
    padding: 0 30px;
    border: 1.5px solid rgba(47, 125, 91, .16);
    border-radius: 999px;
    background: rgba(255, 255, 255, .72);
    font-size: 12px;
    text-align: center;
    transition: border-color .18s, background .18s, box-shadow .18s;
}

.header-search input::placeholder {
    color: #a3a5b3;
}

.header-search input:focus {
    outline: none;
    border-color: var(--mint-deep);
    background: #fff;
    box-shadow: 0 0 0 4px rgba(47, 125, 91, .1);
}

.header-search button {
    position: absolute;
    top: 50%;
    right: 3px;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: transparent;
    color: var(--muted);
    line-height: 0;
    cursor: pointer;
    transform: translateY(-50%);
}

.header-search button svg {
    display: block;
}

.header-search button:hover {
    background: var(--mint);
    color: var(--mint-deep);
}

/* ---------- 按钮 ---------- */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    min-height: 42px;
    padding: 10px 22px;
    border: 1.5px solid transparent;
    border-radius: 999px;
    font-size: 15px;
    font-weight: 600;
    line-height: 1.2;
    text-align: center;
    cursor: pointer;
    transition: transform .18s ease, box-shadow .18s ease, background .18s ease, color .18s ease, border-color .18s;
}

.btn:hover {
    transform: translateY(-2px);
}

.btn:active {
    transform: translateY(0);
}

.btn-primary {
    background: var(--mint-deep);
    color: #fff;
    box-shadow: 0 8px 18px rgba(47, 125, 91, .22);
}

.btn-primary:hover {
    background: var(--mint-dark);
    color: #fff;
    box-shadow: 0 12px 24px rgba(47, 125, 91, .26);
}

/* 导航里「梦想成真 / 祝福排行」用的浅一档：同一个绿色家族，底色换成站点已有的浅薄荷
   （和分类胶囊、卡片「祈祷中」那两处是同一个 --mint），字色用深绿保证读得清。
   「我要许愿」仍是实心深绿 + 白字，一眼就是主操作 */
.btn-soft {
    background: var(--mint);
    color: var(--mint-deep);
    box-shadow: none;
}

.btn-soft:hover {
    background: #cdebd9;
    color: var(--mint-dark);
}

/* 「梦想成真 / 祝福排行」这两颗字是蓝色（跟右侧四颗挂件同一个蓝），其余按钮仍是绿色家族。
   当前页态那条规则（.header-actions .btn.is-current，深绿字）优先级更高，所以这里连同 is-current 一起写，
   不然一进 /fulfilled.html、/likes.html 字又绿回去；悬停用深一档的蓝，不跟着 .btn-soft:hover 变绿 */
.btn-soft.btn-blue,
.header-actions .btn-soft.btn-blue.is-current {
    color: #2f6fae;
}

.btn-soft.btn-blue:hover {
    color: #24587f;
}

.btn-ghost {
    background: rgba(255, 255, 255, .72);
    border-color: rgba(47, 125, 91, .22);
    color: var(--mint-deep);
}

.btn-ghost:hover {
    background: #fff;
    border-color: var(--mint-deep);
    color: var(--mint-dark);
}

.btn-danger {
    background: #b8485c;
    color: #fff;
}

.btn-danger:hover {
    background: #9c374a;
    color: #fff;
}

.btn-small {
    min-height: 34px;
    padding: 7px 15px;
    font-size: 13px;
}

.btn-large {
    min-height: 50px;
    padding: 13px 30px;
    font-size: 16px;
}

/* ---------- Flash ---------- */
.flash-wrap {
    padding-top: 16px;
}

.flash {
    margin-bottom: 8px;
    padding: 12px 18px;
    border-radius: 12px;
    font-size: 14px;
    border: 1px solid transparent;
}

.flash-success {
    background: #e9f8ef;
    border-color: #bfe6d0;
    color: #225f45;
}

.flash-error {
    background: #fdecef;
    border-color: #f3cbd2;
    color: #8e3044;
}

/* ---------- 通用文字 ---------- */
.eyebrow {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    margin-bottom: 14px;
    color: var(--mint-deep);
    font-size: 13px;
    font-weight: 600;
    letter-spacing: 2px;
}

/* ---------- 分类导航 ---------- */
.category-strip {
    position: relative;
    /* 与顶部导航的净空：原来 16，他说过大 → 8。详情页 .detail-wrap 的上内距跟这同一个数，两边一致 */
    margin: 8px 0 0;
    padding: 9px 14px;
    border: 1px solid rgba(47, 125, 91, .12);
    border-radius: 16px;
    background: rgba(255, 255, 255, .62);
    box-shadow: var(--shadow-sm);
}

/* 信息条（分类页/首页共用），与上方分类条同宽同风格 */
.cat-lead-band {
    display: flex;
    align-items: center;
    min-height: 56px;
    padding: 8px 14px;
    border: 1px solid rgba(47, 125, 91, .12);
    border-radius: 16px;
    background: rgba(255, 255, 255, .62);
    box-shadow: var(--shadow-sm);
}

/* 分类页：位于 container 内，紧贴分类条下方 */
.container > .cat-lead-band {
    margin-top: 8px;
}

/* 首页：位于 wishes-section 内，左文案右排序按钮 */
.home-lead-band {
    justify-content: space-between;
    gap: 10px;
    flex-wrap: wrap;
}

.wishes-section > .cat-lead-band {
    margin-top: 0;
    margin-bottom: 8px;
}

.cat-lead-band .page-lead {
    margin-top: 0;
}

.category-strip::after {
    content: "";
    position: absolute;
    top: 1px;
    right: 1px;
    bottom: 1px;
    width: 46px;
    border-radius: 0 15px 15px 0;
    background: linear-gradient(to left, rgba(255, 255, 255, .95), rgba(255, 255, 255, 0));
    pointer-events: none;
    opacity: 0;
    transition: opacity .18s ease;
}

.category-strip.is-more::after {
    opacity: 1;
}

.category-scroll {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 7px;
    overflow-x: auto;
    padding: 0;
    scrollbar-width: none;
    scroll-snap-type: x proximity;
}

.category-scroll::-webkit-scrollbar {
    display: none;
}

.cat-chip {
    flex: 0 0 auto;
    padding: 9.5px 14px;
    border: 1.5px solid rgba(47, 125, 91, .16);
    border-radius: 12px;
    background: var(--chip-bg, #ecf8f1);
    color: var(--ink-soft);
    font-size: 12.5px;
    white-space: nowrap;
    scroll-snap-align: start;
    transition: background .18s, color .18s, border-color .18s, transform .18s;
}

.cat-chip:hover {
    border-color: var(--chip-accent, rgba(47, 125, 91, .42));
    color: var(--chip-accent, var(--mint-deep));
}

.cat-chip.is-active {
    background: var(--chip-bg, #ecf8f1);
    border-color: var(--chip-accent, var(--mint-deep));
    color: var(--chip-accent, var(--mint-deep));
    font-weight: 600;
    box-shadow: 0 3px 10px rgba(47, 125, 91, .14);
}

/* ---------- 区块标题 ---------- */
.wishes-section {
    padding-top: 8px;
    padding-bottom: 12px;
}

.sort-tabs {
    display: inline-flex;
    padding: 4px;
    border: 1px solid var(--line);
    border-radius: 999px;
    background: rgba(255, 255, 255, .72);
}

.sort-tab {
    padding: 4px 13px;
    border-radius: 999px;
    color: var(--muted);
    font-size: 12px;
}

.sort-tab:hover {
    color: var(--mint-deep);
}

.sort-tab.is-active {
    background: var(--mint);
    color: var(--mint-dark);
    font-weight: 600;
}

/* ---------- 愿望卡片 ---------- */
.wish-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 14px 12px;
    /* 第一排要往上让出的不是「网格顶」而是「卡片视觉顶」：胶带 .card-tape 是 top:-9px 绝对定位，
       量出来它探出卡顶 10.2px（各档都一样）。这一档的目标是「跟分类条离信息条那一档同距」= 8px，
       于是 8（上一栏 margin-bottom）+ 10.2 − 10.2（胶带探出）= 8.0，逐档实测都是 8。
       上一版给的是 16（净空 13.8），他看图说大了，指着分类条与信息条之间那 8px 说要照那个来。
       这里必须用 padding 不能用 margin：分类页/排行页/得偿所愿页的网格是 `.wishes-section` 的第一个子元素，
       margin-top 会从父块上边缘「漏」出去并与上一栏的 margin-bottom 合并成 max(8,10.2)，
       首页的网格前面还有信息条所以不合并 —— 同一份 CSS 会在两类页面上给出两种净空。
       padding 不参与合并，两类页面都拿到 8 + 10.2 = 18.2（到卡纸边）/ 8（到胶带顶）。 */
    padding-top: 10.2px;
}

/* 搜索结果条数可多可少，轨道不拉伸到整行、整体居中才不会「一条结果贴着左边」。
   上限 204px 是刻意对齐愿望墙的列数：容器最宽 1072px 时墙面是 5 列 ×204.8px，
   这里 5×204+4×12=1068 刚好也放 5 列，两个页面列数一致 */
.wish-grid-centered {
    grid-template-columns: repeat(auto-fit, minmax(200px, 204px));
    justify-content: center;
}

.wish-card {
    --lh: 24px;   /* 信纸格距 = 正文行高，取整数才能让每条线都落在行底 */
    /* 卡片比详情页少一栏：--rows 是横线条数、--lines = --rows + 1 是正文可用格数
       （多出来那一格的「底线」就是页脚那条虚线）。要再加减一栏只改这两个数，别去动 min-height */
    --rows: 4;
    --lines: 5;
    position: relative;
    display: flex;
    flex-direction: column;
    isolation: isolate;   /* 给纸飞机那一层当裁剪边界：见 .wish-card::before */
    /* 上内距 24：胶带跨在纸边上占掉卡顶往里 9.5px，原来 16 只剩 5.8px 净空，
       愿号框跟分类胶囊几乎贴在一起；加到 24 让这一格有 13.8px（窄屏另有 12px 那档） */
    padding: 24px 14px 9px;
    border-radius: 5px 11px 7px 9px;
    /* 纸色可按后台「首页愿望卡片透明度」调淡：--card-alpha 是 0~100 的百分比，
       100 与原来完全不透明一样。第二条老浏览器不认时整条丢掉，退回上一行，不会变成看不见 */
    background-color: var(--paper, #fff6c9);
    background-color: color-mix(in srgb, var(--paper, #fff6c9) calc(var(--card-alpha, 100) * 1%), transparent);
    background-image:
        linear-gradient(180deg, rgba(255, 255, 255, .34), rgba(255, 255, 255, 0) 38%),
        radial-gradient(120% 90% at 100% 100%, rgba(0, 0, 0, .035), transparent 62%);
    box-shadow:
        0 1px 0 rgba(255, 255, 255, .8) inset,
        0 7px 14px rgba(70, 74, 56, .1);
    transition: transform .25s ease, box-shadow .25s ease;
}

.wish-card::after {
    content: "";
    position: absolute;
    right: 5px;
    bottom: 5px;
    width: 18px;
    height: 18px;
    border-radius: 0 0 7px 0;
    background: linear-gradient(135deg, transparent 48%, rgba(0, 0, 0, .045) 50%, rgba(255, 255, 255, .42) 100%);
    pointer-events: none;
}

.wish-card:hover {
    z-index: 2;
    transform: translateY(-3px);
    box-shadow: 0 14px 26px rgba(70, 74, 56, .16);
}

/* 纸飞机：卡片一只、信纸桌面两只（大带小）、信纸窄屏一只，用的是 :root 那同一张图。
   画在 ::before 而不是卡片的 background-image 里，是为了能单独管这一层——
   信纸的底纹（纸色上那两层渐变）是写在卡片自身上的，飞机另起一层才能整层换掉或调浓淡，
   不然两者混在同一张 background-image 里，一撤就把纸纹也撤了。
   z-index:-1 要配父级的 isolation：这样它压在纸色之上、所有文字之下，
   也不会跑到卡片背景底下去（没有 isolation 时 -1 会被整张卡的底色盖住，等于看不见） */
.wish-card::before,
.detail-card::before {
    content: "";
    position: absolute;   /* 脱离 flex 流：卡片是纵向 flex，进来一个子项就会多占一格 */
    /* 不写 inset:0 而写四条边：老 Safari 不认 inset 时，inset 那版会变成零尺寸盒子，飞机直接消失 */
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: -1;
    pointer-events: none;
    background-repeat: no-repeat;
}

/* 卡片这一只：他要「不要居中，放在文字下方、横线的空隙处」，所以离开正中，落到偏右下的留白里；
   60% 64% 是量过的 —— x 落在卡内中线右侧约 15px、y 落在第 4~5 格，短文时是空白纸面，
   长文时压在最后两行字底下（飞机是背景层、透明度 .13~.22，读起来是纸上的铅笔印，不挡字） */
.wish-card::before {
    background-image: var(--plane);
    /* 高度写 auto：比例由图自己的 120×84 决定，卡片宽窄变化也拉不扁 */
    background-size: 44px auto;
    background-position: 60% 64%;
}

.card-tape {
    position: absolute;
    /* 愿号要写进这框，15px 装不下可读的数字，所以加高到 18；同时把上缘从 -7 提到 -9，
       让框仍然跨在纸边上、下缘只比原来深 1px，不至于压到下面卡顶那一行 */
    top: -9px;
    left: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 56px;
    height: 18px;
    margin-left: -28px;
    background: rgba(255, 255, 255, .52);
    border-left: 1px dashed rgba(0, 0, 0, .07);
    border-right: 1px dashed rgba(0, 0, 0, .07);
    box-shadow: 0 1px 3px rgba(0, 0, 0, .04);
    transform: rotate(-2.5deg);
    backdrop-filter: blur(1px);
}

/* 胶带里的编号只有 56×18，走单独一档字号；卡片以外用到 .wish-seq 的地方（详情页、还愿、删除、后台）不受影响 */
.card-tape > .wish-seq {
    font-size: 12px;
    line-height: 1;
    letter-spacing: .5px;
    text-indent: .5px; /* 字距会跟在最后一个数字后面，补一格才能让号数在框里真正居中 */
}

/* 三列：中间分类贴卡片正中、右边是点赞爱心（编号搬进胶带后，第一列留空撑住居中） */
.card-top {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 5px;
}

/* 编号搬进胶带后，卡顶只剩胶囊和爱心。必须写死列号：
   少了第一列那个元素，自动放置会把胶囊塞进第 1 列，实测它离卡片中线偏 46~74px */
.card-top > .cat-pill {
    grid-column: 2;
    justify-self: center;
}

.card-top > .like-form {
    grid-column: 3;
    justify-self: end;
}

.wish-seq {
    font-family: var(--font-hand);
    font-size: 16px;
    font-weight: 700;
    line-height: 1.2;
    letter-spacing: .4px;
    color: var(--accent, #866a22);
    white-space: nowrap;
}

.cat-pill {
    position: relative;
    z-index: 2;
    display: inline-block;
    padding: 1px 7px;
    border: 1px solid rgba(255, 255, 255, .7);
    border-radius: 999px;
    background: rgba(255, 255, 255, .66);
    color: var(--accent, #7e6733);
    font-size: 11px;
    line-height: 1.5;
    letter-spacing: .3px;
}

.cat-pill:hover {
    background: #fff;
    color: var(--accent, #7e6733);
}

/* 排行榜卡片的名次：单独占一行。375 宽两列时卡顶内容只有 138px，
   编号 + 分类胶囊 + 「第 N 名 + N 赞」实测会叠在一起，硬挤只能牺牲信息；
   另起一行则三样都保留 */
.card-rank {
    display: grid;
    grid-template-columns: 1fr auto 1fr;        /* 和 .card-top 同一种三列：名次贴左，赞数落在卡片中线上 */
    align-items: center;
    align-self: stretch;                        /* 要占满卡片宽度，中间那一列才真的是卡片正中 */
    gap: 5px;
    margin-top: 5px;
    min-width: 0;
    line-height: 20px;                          /* 整数：见 .author-name 的说明，分数行高会让卡片高度带小数，横线就会一格清楚一格发虚 */
}

.card-rank .rank-no {
    justify-self: start;
    font-family: var(--font-hand);
    font-size: 13px;
    font-weight: 700;
    letter-spacing: .3px;
    color: var(--accent, #866a22);
    white-space: nowrap;
}

.card-rank .rank-likes {
    justify-self: center;
    padding: 1px 7px;
    border: 1px solid rgba(255, 255, 255, .7);
    border-radius: 999px;
    background: rgba(255, 255, 255, .66);
    color: var(--accent, #7e6733);
    font-size: 11px;
    line-height: 16px;                          /* 16 + 上下内衬 2 + 边框 2 = 20，和 .card-rank 的行高对齐，名次行整体是整数格 */
    letter-spacing: .3px;
    white-space: nowrap;
}

.card-link {
    display: flex;
    flex-direction: column; /* 必须是纵向：横向 flex 里子项按内容收缩，纸面就只有一行字那么宽，短愿望的横线会「只画半截」 */
    flex: 0 0 auto;
    margin-top: 8px;
    color: inherit;
}

.card-link:hover {
    color: inherit;
}

.card-content {
    display: -webkit-box;
    /* 省略号必须落在纸面最后一格上：浏览器的限行数只到「盒子自己的高度」为止，
       写死一个比格数小的数会被当成没限制——字照样铺满，省略号却画在半路，看着像切坏了 */
    -webkit-line-clamp: var(--lines);
    -webkit-box-orient: vertical;
    flex: 0 0 auto;     /* 不拉伸：纸面高度 = 格距 × 格数，永远是个整数格，最后一格到虚线的距离才和别的格一样 */
    overflow: hidden;
    min-height: calc(var(--lh) * var(--lines));
    text-align: center;                         /* 信纸居中：短愿望不再左边空一块，横线上的字排成一条中轴 */
    color: #3e3f4f;
    font-size: 14px;
    /* 字与字之间拉开一档。居中文字本来最怕字距在行尾留一个空，实测五种宽度、四种列表页
       每行墨迹中心偏离中线 ≤0.01px，所以不用 text-indent 补，也不用分窄屏档 */
    letter-spacing: 1.5px;
    line-height: var(--lh);
    word-break: break-word;
    /* 信纸横线：间距 = 行高，所以每一行字底下都有一条线，字没写满也接着有。
       渐变只铺 --rows 格（最后一行字的底线由页脚虚线充当），所以 no-repeat + 限宽 */
    background-image: repeating-linear-gradient(to bottom,
        transparent 0, transparent calc(var(--lh) - 1px),
        var(--line) calc(var(--lh) - 1px), var(--line) var(--lh));
    background-size: 100% calc(var(--lh) * var(--rows));
    background-repeat: no-repeat;
}

/* 正文已经写到虚线这一格了（--lines 比 --rows 多一行），所以正文框和虚线要重叠 1px：
   虚线的顶到上一条横线的顶仍然正好差一个格距，那一格才不会比别的格矮 1px */
.card-foot {
    display: flex;
    align-items: stretch;
    gap: 0;
    margin-top: -1px;
    padding-top: 8px;
    border-top: 1px dashed rgba(52, 53, 73, .16);
}

/* 名字和日期各自整行居中，爱心已经不在这一行，页脚宽度就是整张卡的内容宽 */
.foot-text {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1px;
    min-width: 0;
}

.card-meta {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    gap: 4px 8px;
    width: 100%;
}

.card-stamp {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: 20px;                               /* 固定这一行的高度：没盖章的卡也占同样一格，已实现/未实现两种卡片的总高和外形完全一致 */
    padding: 0 8px;
    border: 1.5px solid rgba(169, 58, 46, .42);
    border-radius: 6px;
    background: rgba(255, 255, 255, .28);
    color: rgba(169, 58, 46, .78);               /* 印泥朱红：和 10 种纸色都有明度差，不再用容易和薄荷绿/嫩芽绿纸糊在一起的绿 */
    font-size: 13px;
    font-weight: 700;
    line-height: 16px;                          /* 整数：行高 + 上下边框 3 = 19，加 padding 后正好落在 20 的框里 */
    letter-spacing: 1px;
    white-space: nowrap;
}

/* 还没实现的愿望占同一格、写「祈祷中」：外形和朱红章一模一样，只把印泥换成站点那支绿（#2F7D5B）。
   绿不透明度拉满是量出来的：照朱红用 .78 时十种纸只有 3.04~3.17:1，比红章的 3.72~3.88 明显弱一档 */
.card-stamp.is-pray {
    border-color: rgba(47, 125, 91, .55);
    color: #2f7d5b;
}

.card-author {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    min-width: 0;
}

.avatar {
    display: grid;
    place-items: center;
    flex: 0 0 auto;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: rgba(255, 255, 255, .78);
    color: var(--accent, #7e6733);
    font-family: var(--font-hand);
    font-size: 10.5px;
    box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .05);
}

.author-name {
    max-width: 96px;
    overflow: hidden;
    color: #4a4b5c;
    font-size: 11px;
    /* 分数行高（11×1.3=14.3）会让卡片高度带小数，网格每行的起点就落在不同的小数位上，
       1px 的分栏横线于是「一行清楚、一行只有半格粗」。页脚两行统一用整数 px，卡片高度就是整数。 */
    line-height: 14px;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.card-time {
    color: #6b6c78;
    font-size: 10px;
    line-height: 14px;                          /* 同上：整数行高，见 .author-name */
    letter-spacing: .1px;
    font-variant-numeric: tabular-nums;
    /* 爱心搬去右上角后整行都是日期可用的宽度，「实现于 2026-10-29 07:04」实测 119.4，
       最窄那一档也还剩 130 以上，所以不用折行 */
    white-space: nowrap;
    text-align: center;
    max-width: 100%;
}

/* ---------- 点赞 ---------- */
.like-form {
    position: relative;
    display: inline-flex;
    margin: 0; /* form 默认的块级外边距会把卡顶那一行撑高，爱心也跟着偏移 */
    flex: 0 0 auto;
}

.like-btn {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 4px;
    padding: 0;
    border: 0;
    background: none;
    color: rgba(92, 93, 116, .55);
    font: inherit;
    line-height: 1;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}

.like-mark {
    flex: 0 0 auto;
    width: 16px;
    height: 16px;
}

.like-num {
    font-variant-numeric: tabular-nums;
    letter-spacing: .2px;
}

/* 空心 → 实心：点过一次，这个爱心就一直是红的 */
.like-btn.is-liked {
    color: #d8566f;
}

.like-btn.is-liked .like-mark path {
    fill: currentColor;
}

.like-btn:hover {
    color: #d8566f;
}

/* 已实现的爱心封版：还在原位，只是压成更淡的灰，hover 也不再变红（点下去只会出一句提示） */
.like-btn.is-closed,
.like-btn.is-closed:hover {
    color: rgba(92, 93, 116, .34);
}

.like-btn.is-busy {
    opacity: .55;
}

/* 卡片右上角的爱心：图标 16px，按钮外框给到 24×22 才够点 */
.like-form-card .like-btn {
    min-width: 24px;
    min-height: 22px;
}

/* 详情页胶带正下方：浅色小圆带，心 + 赞数（margin-top 18px 是为了完全离开 30px 高的胶带） */
.detail-like {
    display: flex;
    justify-content: center;
    margin-top: 18px;
    margin-bottom: 2px;
}

/* 详情页的爱心和赞数：先按他的要求放大一倍（34/26/68/28），这一轮再缩小四分之一，
   落到 3/4 档——爱心 26、数字 20、外框 51 高、左右各 21 内边距、间距 6。
   每个数都是整数（25.5/19.5 会被浏览器舍成小数高度，页脚那一行就又不齐了） */
.like-form-detail .like-btn {
    height: 51px;
    gap: 6px;
    padding: 0 21px;
    border: 1px solid rgba(255, 255, 255, .7);
    border-radius: 999px;
    background: rgba(255, 255, 255, .42);
    color: rgba(92, 93, 116, .72);
    font-size: 20px;
}

.like-form-detail .like-mark {
    width: 26px;
    height: 26px;
}

/* 详情带上的按钮自带灰色文字色（为了那串数字），所以点赞态要在这里显式改回红色，才和卡片那个爱心一致 */
.like-form-detail .like-btn.is-liked {
    color: #d8566f;
    background: rgba(255, 255, 255, .62);
}

/* 点完弹的非阻塞小提示，1.6 秒自己收回去；不遮挡、也不需要点掉 */
.like-tip {
    position: absolute;
    left: 50%;
    bottom: calc(100% + 6px);
    z-index: 3;
    padding: 4px 10px;
    border-radius: 10px;
    background: rgba(58, 60, 82, .9);
    color: #fff;
    font-size: 12px;
    line-height: 1.35;
    white-space: nowrap;
    opacity: 0;
    pointer-events: none;
    transform: translate(-50%, 4px);
    transition: opacity .18s ease, transform .18s ease;
}

.like-tip.is-show {
    opacity: 1;
    transform: translate(-50%, 0);
}

/* 爱心在卡片右上角，气泡再往按钮上方长就会顶出卡片、压到导航，所以改成向下展开；
   同时从按钮右缘往左长、允许换行。140px 是实测出来的：最左列爱心的右缘在 360px 下只有 152px，
   再宽就会顶到屏幕左边（700px 四列时同样只有 154px），140 加 8px 余量在哪个断点都留在屏内 */
/* 「第1528愿 祝福成功1次」中间那一个空格既是这句唯一的折行机会、也是它的间隔：
   空格本身 4px，再补 4px 词距 = 实测 7.5px，跟原先两个不换行空格（7.09px）几乎同宽，
   而折行时行尾的空格连同这点词距一起被丢掉，两行才各自居中。
   只挂祝福这两颗气泡，提交页和还愿簇那两颗没有这个空格，不跟着改。 */
.like-form-card .like-tip,
.like-form-detail .like-tip {
    word-spacing: 4px;
}

.like-form-card .like-tip {
    /* 汉字之间到处都能断，默认会把「次」一个字孤零零甩到第二行；keep-all 把内部断点堵掉，
       这句就只能在上面那个空格处折。代价是这条气泡里的文案以后不能写超过 120px（=10 个 12px 汉字）
       且不带空格的句子 —— 这颗气泡可能收到的另外 8 条固定文案（front_like() 的 6 条 + app.js 的
       「网络不好，再试一次」「没点上，再试一次」）逐条量过，最宽 108px，都在 120 净宽内
       （逗号后面 Chrome 仍允许折，所以那几条就算超长也不是没有退路） */
    word-break: keep-all;
    left: auto;
    bottom: auto;
    top: calc(100% + 6px);
    right: 0;
    width: max-content; /* 只给 max-width 的话，收缩宽度会按这个 30px 宽的 form 算，挤成一字一行 */
    max-width: 140px;
    white-space: normal;
    text-align: center;
    transform: translateY(-4px);
}

.like-form-card .like-tip.is-show {
    transform: translateY(0);
}

/* ---------- 空状态 ---------- */
.empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 58px 24px;
    border: 1.5px dashed rgba(47, 125, 91, .22);
    border-radius: 22px;
    background: rgba(255, 255, 255, .55);
    text-align: center;
}

.empty-icon {
    display: grid;
    place-items: center;
    width: 64px;
    height: 64px;
    margin-bottom: 16px;
    border-radius: 20px;
    background: var(--cream);
    color: #866a22;
    transform: rotate(-3deg);
}

.empty-state h3 {
    font-family: var(--font-hand);
    font-size: 24px;
}

.empty-state p {
    margin: 8px 0 20px;
    color: var(--muted);
    font-size: 14px;
}

/* ---------- 分页 ---------- */
/* 分页条吸底：愿望卡片少的时候它不再浮在卡片下面（实测宽屏能离页脚 332px），
   而是固定压在页脚上方那一档。
   链条：body(flex 列) → .site-main(flex 列) → .wishes-section(flex 列) → .pagination(margin-top:auto)。
   body 本来就是纵向 flex，.site-main 又是它的可伸展项；把这两层也改成纵向 flex 之后，
   富余高度才能一路传到分页条的前沿，margin-top:auto 把它顶到 section 的底边（也就是页脚上方）。
   只有带分页条的列表页才接这条链（:has），详情页/发布页/关于我们那些单块页面一行不动；
   万一浏览器不认 :has()，整条规则作废，分页条退回今天的位置（下面 padding-top 那条仍然成立）。
   margin-top 原来那个 24px 改成 padding-top:auto 之外的 padding-top:24px ——
   margin-top 被 auto 占住以后，卡片多的那一档（没有富余高度）会变成 0，卡片和分页条贴死；
   挪成内距，两档都是 24。 */
.site-main:has(.pagination) {
    display: flex;
    flex-direction: column;
}

.wishes-section:has(.pagination) {
    flex: 1 0 auto;
    display: flex;
    flex-direction: column;
}

.pagination {
    display: flex;
    justify-content: center;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: auto;
    padding-top: 24px;
}

.page-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 38px;
    height: 38px;
    padding: 0 13px;
    border: 1px solid rgba(47, 125, 91, .16);
    border-radius: 11px;
    background: rgba(255, 255, 255, .72);
    color: var(--ink-soft);
    font-size: 13px;
}

/* 当前页那颗（is-current）本身已是深绿底白字，悬停不能再把字改成绿；禁用那颗点不动，也不给它变色反馈 */
.page-btn:not(.is-disabled):not(.is-current):hover {
    border-color: var(--mint-deep);
    color: var(--mint-deep);
}

.page-btn.is-current {
    background: var(--mint-deep);
    border-color: var(--mint-deep);
    color: #fff;
    font-weight: 600;
}

.page-btn.is-disabled {
    opacity: .45;
    cursor: not-allowed;
}

.page-ellipsis {
    color: var(--muted);
    padding: 0 4px;
}

/* ---------- 内页头部 ---------- */
.page-hero {
    padding-top: 28px;
    padding-bottom: 4px;
}

.page-hero.narrow {
    max-width: 760px;
}

.page-title {
    font-family: var(--font-hand);
    font-size: clamp(30px, 3.4vw, 38px);
    line-height: 1.25;
    letter-spacing: 1.2px;
}

.page-lead {
    max-width: 660px;
    margin-top: 8px;
    color: var(--ink-soft);
    font-size: 14px;
}

/* ---------- 页尾插画（详情页与许愿页共用一张图） ---------- */
/* 一张图两处用，靠的是两块的宽本来就相等：详情页是整幅容器内宽，许愿页用网格整行项跨满
   「表单 + 侧栏」那一行（1120 − 2×24 = 1072）。同比例的原图在两边等比缩放，画的就是同一只纸飞机。
   上方留 8（第七十一轮：他要求内容页把插画往信纸与页脚两头收到最小，原为 30）。
   许愿页那一侧的 30 是 `.submit-layout` 的行距给的（`.submit-band` 把自己的 margin 归零），
   这一条改不动它 —— 两边上距从此是两个数（详情页 8 / 许愿页 30），要不要一起收到 8 等他定。
   ≤760 不画：那一档音乐长条常驻屏幕底，正好盖在这块空地上；1200 宽的横画缩到 354 也只剩 25px 高，
   看不出画的是什么。 */
.wish-band {
    display: block;
    width: 100%;
    height: auto;
    margin-top: 8px;
    pointer-events: none;
    user-select: none;
}

.submit-band {
    grid-column: 1 / -1;
    margin-top: 0;
}

/* ---------- 详情页 ---------- */
.detail-wrap {
    /* 与顶部导航的净空跟着分类条那一条走（他要求内容页与首页分类栏同距）：30 → 8 */
    padding-top: 8px;
    /* 第七十一轮收到 8（原 88）。88 是第五十二轮为常驻音乐长条写的页尾净空（76+12），
       那一档（≤760）现在另有一条兜着；宽屏这一侧音乐那颗在右侧挂件列里，压的是插画的**透明**像素：
       量法是把 SVG 画进离屏画布、逐像素扫 alpha>8 求「墨迹真界」，再和各挂件的 rect 求交 ——
       1440 档挂件整体在插画右边界之外（列左 1253 vs 插画右 1249），800~1200 档 rect 与插画盒相交但交点全在透明区，
       墨迹行到底 83，压墨 0。所以这一条可以只留 8。 */
    padding-bottom: 8px;
}

.breadcrumb {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 7px;
    margin-bottom: 20px;
    color: var(--muted);
    font-size: 12px;
}

.breadcrumb a {
    color: var(--muted);
}

.breadcrumb a:hover {
    color: var(--mint-deep);
}

.detail-card {
    --lh: 46px; /* 信纸横线间距 = 正文行高，取整数才能让每条线都落在行底 */
    --rows: 7;  /* 默认这张纸有几格；纸的高度只由格数决定，卡片高度就是格距的整数倍，最后一格到虚线不会比别的格矮 */
    position: relative;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    isolation: isolate;   /* 给纸飞机那一层当裁剪边界：见 .detail-card::before */
    padding: 16px 14px 9px;
    border-radius: 10px 24px 14px 20px;
    /* 同卡片：--detail-alpha 由后台「内容页卡片透明度」给，100 = 原来的不透明信纸 */
    background-color: var(--paper, #fff6c9);
    background-color: color-mix(in srgb, var(--paper, #fff6c9) calc(var(--detail-alpha, 100) * 1%), transparent);
    background-image:
        linear-gradient(180deg, rgba(255, 255, 255, .38), rgba(255, 255, 255, 0) 34%),
        radial-gradient(120% 90% at 100% 100%, rgba(0, 0, 0, .035), transparent 60%);
    box-shadow: 0 26px 55px rgba(70, 74, 56, .14);
}

/* 信纸上的两只要比卡片大：他给的是「2 倍」——原来的 150 → 300、60 → 120（同一张图，只改宽）。
   两只摆成对角线：大的在左下、小的在右上，机头都朝右上，读起来是一前一后往右上飞。
   落点他调过三回：先两只各往右上挪一个小飞机宽（120）→ 大 150/176、小 116/0；
   再各往左下挪半个小飞机宽（60）→ 大 90/236、小 176/60；
   第五十五轮只挪大的那只，再往左下 25 → 大 65/261（小的那只不动）。 */
.detail-card::before {
    background-image: var(--plane), var(--plane);
    background-size: 300px auto, 120px auto;
    background-position: left 65px top 261px, right 176px top 60px;
}

/* 卡片正中左右两颗 <>（左 = 上一愿，右 = 下一愿）。
   外层 nav 铺满整张纸、不吃点击（pointer-events:none），两颗自己把点击收回来，
   这样 nav 不会变成 flex 列里的一格（.detail-card 是纵向 flex，多一格就把纸的格距顶歪了）。
   nav 现在挂在 .detail-content 里面（第七十三轮）：定位基准换成信纸的格子，
   尖端才能精确落在某一条横线上 —— 挂在卡片上时基准是卡片正中，卡片一高一矮（页脚/按钮换行）那颗就跟着偏。
   z-index 这一行是真浏览器量出来的缺陷补的：nav 与 .detail-content 都是定位元素，DOM 里 nav 在前，
   所以正文那一层（带信纸横线背景）压在两颗上面，卡片中间左右各 20px 的点全被正文吃掉 ——
   13 档宽度 × 三张纸共 48 次 elementFromPoint 实测拿到的都是 detail-content。抬到 1 就两样都对了。 */
.detail-edges {
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
}

.detail-edge {
    position: absolute;
    /* 尖端落在信纸的横线上：横线在正文这一格的坐标是 k × --lh − 0.5px（每行一条），
       取这张纸正中间那一条 —— 纸固定七格（--rows:7，没有哪一处会改它），正中间就是第四条。
       这里本来还想用 CSS round() 按 --rows 算中间那条，实测（headless Edge 156）连
       round(nearest, 46px, 161px) 这种全写死的都算成 0px，函数认得但值不对，所以只留写死这一条。
       正文超过七行时纸会长高，这两颗仍钉在从上往下第四条线上（照样在横线上，只是不再跟着长高的纸走中间）。 */
    top: calc(4 * var(--lh) - 36.5px);
    display: flex;
    align-items: center;
    justify-content: center;
    /* 盒子大小不变（点击范围还是这么大）：桌面 40×72，窄屏 34×60。
       里面的图形换成 SVG，盒子居中 → 墨迹中心 = 盒子中心 = 横线中心。 */
    width: 40px;
    height: 72px;
    margin-top: 0;
    pointer-events: auto;
    user-select: none;
    /* 淡蓝：比上一档 #4a8ac4 再淡一点（他要求的「颜色再淡一点」）。
       上一档是「十种纸色上都不低于 3:1」的最淡一档（最低 3.08，在浅紫纸上），
       再淡就跌破 3:1 这条非文字对比度的底线 —— 这一档最低 2.74，悬停回到 #2f6fae（最低 4.41）。 */
    color: #5a93c9;
}

/* 两颗的图形：原来是一个 < / > 文字（52px 粗体），墨迹 26×28.5（窄屏 22×24）。
   文字那一版笔画是 6~7px 粗的实心块，想改细只能换图形 —— 换细体字会把整个墨迹缩掉（大小就不变了）。
   换成 SVG：viewBox 就是原来那个墨迹框，描边 4px（比原来细 4 成），圆头圆角，
   墨迹大小不变，位置也不变（盒子里居中）。 */
.detail-edge svg {
    width: 26px;
    height: auto;
    fill: none;
    stroke: currentColor;
    stroke-width: 4;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.detail-edge:hover {
    color: #2f6fae;
}

/* 左右各贴 2px —— 基准从卡片换成了 .detail-content（卡片内边距 14 / 窄屏 10），
   所以这里的负值要把那 14 / 10 补回来，横向位置跟改前一个像素不差。 */
.edge-prev {
    left: -12px;
}

.edge-next {
    right: -12px;
}

.detail-tape {
    position: absolute;
    top: 0;
    left: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 150px;
    height: 30px;
    margin-left: -75px;
    background: rgba(255, 255, 255, .5);
    border-left: 1px dashed rgba(0, 0, 0, .07);
    border-right: 1px dashed rgba(0, 0, 0, .07);
    transform: rotate(-1deg);
}

/* 愿号原来在纸右上角当 140px 的大水印，他让移进胶带那个白色长方形里，所以这里不再是绝对定位。
   字号由框子决定：胶带 150×30，20px 粗体在框内上下各余 5.9、左右各余 51.33，墨迹最低点离下面的赞数带还有 11.81px
   （胶带自己的视觉下缘离赞数带只有 2.69px，字再大就要压上去）；就算愿号长到 8 位数，左右仍各余 30.5px。
   20px 粗体按 WCAG 算大字（门槛 3:1）：十种纸色最低是奶油黄的 3.13:1，其余 4.79~6.84:1。 */
.detail-watermark {
    color: var(--accent, #866a22);
    font-family: var(--font-hand);
    font-size: 20px;
    font-weight: 700;
    line-height: 1;
    letter-spacing: 1.5px;
    text-indent: 1.5px; /* 字距会跟在最后一个数字后面，补一格让号数在框里真正居中 */
    opacity: 1;
    pointer-events: none;
    user-select: none;
}

.detail-head {
    position: relative;
    display: flex;
    flex: 0 0 auto;
    align-items: flex-start;
    justify-content: space-between;
    gap: 18px;
}

.detail-head > div:first-child {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 12px;
}

.wish-seq-lg {
    font-size: 30px;
}

.cat-pill-lg {
    padding: 4px 14px;
    font-size: 12px;
}

.detail-icon {
    display: flex;   /* 必须是 flex/块级：里面是 26×26 的行内 SVG，行盒会按基线加 7.5px 下沉，纸头高度就变成 33.5 这种小数（窄屏纸头由它决定，整张信纸跟着带小数） */
    color: var(--accent, #866a22);
    opacity: .7;
}

.detail-icon img {
    width: 38px;
    height: 38px;
}

.detail-content {
    position: relative;
    flex: 0 0 auto;
    min-height: calc(var(--lh) * var(--rows));
    margin: 32px 0 calc(var(--lh) - 1px); /* 下边距 = 格距 − 1px：虚线正好落在正文的下一格上 */
    /* 左右各让出 32px：两颗 <> 住在这条空白里（.detail-edge，第七十三轮起它们的定位基准就是这一格，
       左颗 left:-12px + 盒宽 40 → 盒子右缘在正文这一格起算 +28px，实测墨迹右缘 219、正文盒左缘 230，还空 11px）。
       第五十九轮那颗加大一倍（26 → 52），墨迹跟着变宽，所以让位从 24 加到 32；第七十三轮换成 SVG 后墨迹仍是 26 宽，没再动这个数。
       只让横向、纵向一格不动，所以横线格距、每行字踩线、居中排布都不受影响（两边一样宽，纸还是对称的）。 */
    padding: 0 32px;
    color: #333444;
    font-family: var(--font-hand);
    font-size: 23.625px;    /* 31.5 的四分之三 */
    text-align: center;     /* 信纸居中排：横线格距不变，字还是踩在 lines 上 */
    line-height: var(--lh);
    letter-spacing: 1.5px;   /* 与卡片正文同一个值；居中偏差实测 ≤0.01px，不用 text-indent 补 */
    word-break: break-word;
    /* 信纸横线：间距 = 行高，每一行字底下都有一条线 */
    background-image: repeating-linear-gradient(to bottom,
        transparent 0, transparent calc(var(--lh) - 1px),
        var(--line) calc(var(--lh) - 1px), var(--line) var(--lh));
}

.detail-foot {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 12px;
    padding-top: 18px;
    border-top: 1px dashed rgba(52, 53, 73, .16);
}

.detail-author {
    display: inline-flex;
    align-items: center;
    gap: 10px;
}

.avatar-lg {
    width: 38px;
    height: 38px;
    font-size: 20px;
    font-weight: 700;
}

.detail-author strong {
    display: block;
    font-size: 14px;
    line-height: 19px;                          /* 整数（原来是 1.35→18.9）：页脚里只要有一项带小数，整张信纸的高度就会变成 57.69 这种小数 */
    margin-bottom: 3px;
    text-align: center;
}

.detail-author time {
    display: block;
    margin-top: 0;
    color: #7d7e8c;
    font-size: 12px;
    line-height: 17px;                          /* 整数（原来是 1.4→16.8）：名字 19 + 3 + 日期 17 = 39（比头像 38 高，所以页脚内容就是 39），加 padding-top 18 + 虚线 1 = 58 整数 */
    font-variant-numeric: tabular-nums;
}

.detail-tools {
    display: grid;
    /* 1fr auto 1fr 才让中间那一簇真的落在卡片正中：两侧各占等分、中间按内容宽。
       用 space-between 不行——左右两颗宽度不一样，中间那簇会偏。 */
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    column-gap: 10px;
    flex: 0 0 auto;
    margin-top: 22px;
    padding-top: 16px;
    border-top: 1px dashed rgba(52, 53, 73, .1);
}

.tool-left {
    grid-column: 1;
    justify-self: start;
}

.tool-mid {
    grid-column: 2;
}

.tool-right {
    grid-column: 3;
    justify-self: end;
}

/* 这一行的每一颗（含中间那簇里的两颗）：字号 16px，但高度仍钉在 34（上下内距从 7 降到 5 抵掉涨出来的那 2.2px）；
   背景按他说的 80% 不透明，悬停那颗仍旧翻白——不补这条会被上面的规则盖掉 */
.detail-tools .btn {
    font-size: 16px;
    padding: 5px 15px;
}

.detail-tools .btn-ghost {
    background: rgba(255, 255, 255, .8);
}

.detail-tools .btn-ghost:hover {
    background: #fff;
}

@media (max-width: 560px) {
    /* 窄屏三颗挤不进一行：两颗仍一左一右，中间那一簇（还愿 + 删除）单独占第二行、还是居中 */
    .detail-tools {
        grid-template-columns: 1fr 1fr;
        row-gap: 10px;
    }

    .tool-right {
        grid-column: 2;
    }

    .tool-mid {
        grid-column: 1 / -1;
        grid-row: 2;
        justify-self: center;
    }
}

/* 卡片那一行中间的一簇「还愿 + 删除」（外加已还愿时那颗灰芯片）：
   两颗之间 16px——比左右两格与它的 10px 列距还宽一点，这样这两颗各自独立、不会看着像一颗连体。
   气泡（祝福提示 / 「没留邮箱」那句）以这一簇为中心，所以那颗空的 .like-tip 必须留在这个盒子里
   （它是 absolute，不占 flex 的格子，也不跟着吃 gap）。 */
.detail-claim {
    position: relative;
    display: flex;
    align-items: center;
    gap: 16px;
}

.btn-claim-delete {
    background: rgba(255, 255, 255, .55);
    border: 1.5px solid rgba(184, 72, 92, .28);
    border-radius: 999px;
    color: #b8485c;
}

.btn-claim-delete:hover {
    background: #fff;
    border-color: #b8485c;
    color: #a33c4f;
}

.detail-claim .is-disabled {
    opacity: .55;
    pointer-events: none;
}

/* ---------- 许愿页 ---------- */
.submit-hero {
    /* 第八十一轮：他圈的是「导航底 → 标题墨迹顶」那一条空地，连着要了三次 ——
       48 →（减少一半）24 →（再减三分之一）16 →（再减成）12。
       这条空地 = 内距 + 手写体行盒自带的上距（墨迹在行盒里往下挪 4；900 档字号小一档，是 3），
       所以内距写 8 而不是 12：8 + 4 = 12 正好他要的那个数（900 档量到 11，差的就是那 1px 字距）。
       ≤760 这一档本来就没有这条空地（下面 `.container { padding: 0 18px }` 的简写把纵向内距抹成 0，
       实测标题贴着导航，只剩字体那 3px），本轮没动它。 */
    padding-top: 8px;
    padding-bottom: 16px;
}

.submit-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 320px;
    /* 第八十三轮：start → stretch，让「表单」和右侧「写之前，我要告诉你」这两张卡在并排那一档等高。
       原来 start 各自贴行顶，右侧那张自然高 560、表单 555，差 5px（他看图要求「跟左侧保持一致」）。
       stretch 取的是行高 = 两者里较高的那个，所以表单会被拉到和右卡一样（多出来的 5px 落在表单下内距里），
       不会出现「为了对齐把右卡的文字裁掉」。
       只在并排那两列成立：≤980 是单列，两张卡各占一行，行高等于自己 ⇒ 这一条自动变成空操作，
       窄屏不会因此撑出一张 200px 空白的便签。第三行那张共用插画（.submit-band，跨整行、单独一行）同理不受影响。 */
    align-items: stretch;
    gap: 30px;
    padding-top: 10px;
    /* 第八十轮：他圈的就是插画底到页脚那一条，要求和内容页同一个数 ⇒ 88 → 8
       （`.detail-wrap` 宽屏那条就是 8，第七十一轮定的）。宽屏实测：发布页那条缝 18 = 这一条 8 + 页脚上距 10；
       内容页 21~40（同一份 8，但那页比视口短，吸底页脚把它顶开几 px —— 那是 .main{flex:1 0 auto} 的既有行为）。
       窄屏那一档另有一条 88 兜着：≤760 的 `.container { padding: 0 18px }` 简写会把这里的纵向内距抹成 0，
       常驻音乐长条（视口底上 80~124 那一段）就压在最后一块内容上 —— 窄屏右侧卡排在表单下面，压的是那张卡的尾巴。 */
    padding-bottom: 8px;
}

.wish-form {
    position: relative;
    padding: 26px 32px 22px;
    border: 1px solid rgba(47, 125, 91, .12);
    border-radius: 24px;
    background: rgba(255, 253, 247, .94);
    box-shadow: var(--shadow-md);
}

.wish-form::before {
    content: "";
    position: absolute;
    top: -12px;
    left: 50%;
    width: 110px;
    height: 26px;
    margin-left: -55px;
    background: rgba(255, 246, 201, .9);
    box-shadow: 0 2px 5px rgba(0, 0, 0, .04);
    transform: rotate(-1.5deg);
}

.form-errors {
    margin-bottom: 20px;
    padding: 14px 18px;
    border: 1px solid #f0c9d1;
    border-radius: 12px;
    background: #fdeef1;
    color: #8e3044;
    font-size: 14px;
}

.form-errors ul {
    margin-top: 5px;
    padding-left: 18px;
    list-style: disc;
}

.form-block {
    margin: 0;
    padding: 15px 0 17px;
    border: 0;
    border-top: 1px dashed rgba(52, 53, 73, .12);
}

.form-block:first-of-type {
    padding-top: 4px;
    border-top: 0;
}

.field-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
}

.field-row-submit {
    grid-template-columns: 1.15fr 1.35fr 168px .95fr;
}

/* 愿望提醒（排在「公开 / 私密」右边）：上面「年 / 天」分段、下面填数字，两行各 23、中间 4px 缝，
   合起来 50，和左边「公开 / 私密」的卡片本体（实测 50）、再左边两个输入框（49.5）同一条底边。
   两段各自封四边、只有外侧导角，配色跟「公开 / 私密」的选中态同一套薄荷（外圈除外，理由见下面 checked）。
   先前它和数字框拼成一张卡、选中那格下边故意不描，看着像没画完，字也只有 12px，压在数字框上沿很小气。 */
.field-remind {
    position: relative;
    display: grid;
    grid-template-rows: 23px 23px;
    gap: 4px;
}

.remind-units {
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-template-rows: 1fr;
    min-height: 0;
}

/* `.field label`（0,1,1）会把这些 label 打成 display:block + margin-bottom:5px，
   选择器多带一层 .field-remind 才压得住（0,2,0）。 */
.field-remind .unit-seg {
    position: relative;
    display: flex;
    margin: 0;
    min-width: 0;
    cursor: pointer;
}

.unit-seg input {
    position: absolute;
    opacity: 0;
    width: 1px;
    height: 1px;
}

.unit-seg > span {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    /* flex 项的默认最小高度是内容高（13 字行盒 + 上下 1.5 描边），不放开就会往下溢出 1.1px，
       把 4px 的缝压成 2.9；line-height:1 让内容盒只有 16px，稳稳坐在 23px 那一行里。 */
    min-height: 0;
    border: 1.5px solid rgba(52, 53, 73, .12);
    border-radius: 12px 0 0 12px;
    background: #fff;
    color: var(--ink-soft);
    font-size: 13px;
    font-weight: 600;
    line-height: 1;
    transition: border-color .16s, background .16s;
}

.unit-seg + .unit-seg > span {
    border-left: 0;
    border-radius: 0 12px 12px 0;
}

.unit-seg:hover > span {
    border-color: rgba(47, 125, 91, .35);
}

/* 选中态用薄荷描边 + 浅薄荷底 + 薄荷字，不跟「公开 / 私密」那样加 3px 外圈：
   那两张卡之间有 12px 空隙，外圈有地方摊开；这两格是拼在一起的，外圈会骑到邻格身上、
   还会把下面 4px 的缝吃到只剩 1px（实测截图看过，像没对齐）。 */
.unit-seg input:checked + span {
    border-color: var(--mint-deep);
    background: #f2fbf6;
    color: var(--mint-dark);
}

.unit-seg input:focus-visible + span {
    outline: 2px solid var(--mint-deep);
    outline-offset: -2px;
}

/* 中间那条分隔线恒为灰：两格是拼在一起的，分隔线只有「年」这一格的右边框，
   不钉住的话选「年」时它是薄荷、选「天」时它是灰，同一处两个颜色。 */
.field-remind .unit-seg:first-child > span {
    border-right-color: rgba(52, 53, 73, .12);
}

.field.field-remind input.remind-num {
    min-height: 0;
    padding: 0 6px;
    border: 1.5px solid rgba(52, 53, 73, .12);
    border-radius: 12px;
    font-size: 13px;
    line-height: 1.2;
    text-align: center;
}

.field.field-remind input.remind-num::placeholder {
    font-size: 12px;
}

.field.field-remind input.remind-num:focus {
    box-shadow: none;
}

/* 没填邮箱时不是 disabled（disabled 点不出提示），只是压暗 + 只读，点击由 JS 给一句话 */
.field-remind.is-locked > :not(.like-tip) {
    opacity: .5;
}

.field-remind.is-locked {
    cursor: help;
}

/* 这句提示（19 字 ≈ 228px 单行）从格子右缘往左长：这一格在表单最右，
   照默认的「居中挂在宿主上方」会长出表单右缘 52px、压到右边的说明卡上。
   往左长在 320 档也留得住（格子右缘在 301，往左 228 到 73）。 */
.field-remind .like-tip {
    left: auto;
    right: 0;
    transform: translateY(4px);
}

.field-remind .like-tip.is-show {
    transform: translateY(0);
}

.field-row .radio-card > span {
    height: 50px;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0 18px;
}

.field-visibility {
    display: flex;
    gap: 0;
}

.field-visibility .radio-card {
    flex: 1;
    min-width: 0;
}

.field-visibility .radio-card:first-child > span {
    border-radius: 12px 0 0 12px;
}

.field-visibility .radio-card:last-child > span {
    border-radius: 0 12px 12px 0;
    border-left: 0;
}

.field {
    min-width: 0;
}

.field label {
    display: block;
    margin-bottom: 5px;
    color: var(--ink-soft);
    font-size: 13px;
    font-weight: 600;
}

.field input[type="text"],
.field input[type="email"],
.field input[type="password"],
.field input[type="search"],
.field input[type="url"],
.field input[type="number"],
.field textarea,
.field select {
    width: 100%;
    padding: 11px 14px;
    border: 1.5px solid rgba(52, 53, 73, .14);
    border-radius: 12px;
    background: #fff;
    font-size: 15px;
    transition: border-color .18s, box-shadow .18s;
}

.field input::placeholder,
.field textarea::placeholder {
    color: #b0b1bd;
}

.field input:focus,
.field textarea:focus,
.field select:focus {
    outline: none;
    border-color: var(--mint-deep);
    box-shadow: 0 0 0 4px rgba(47, 125, 91, .09);
}

.field textarea {
    min-height: 130px;
    resize: vertical;
    line-height: 1.85;
}

.hint {
    margin-top: 6px;
    color: var(--muted);
    font-size: 12px;
    line-height: 1.6;
}

.textarea-meta {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 14px;
    margin-top: 6px;
}

.textarea-meta .hint {
    margin-top: 0;
}

.counter {
    flex: 0 0 auto;
    color: var(--muted);
    font-size: 12px;
    font-variant-numeric: tabular-nums;
}

.counter.is-near {
    color: #b06a2a;
}

.counter.is-limit {
    color: #b8485c;
}

.category-picker {
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    gap: 9px;
}

.category-option input,
.radio-card input {
    position: absolute;
    opacity: 0;
    width: 1px;
    height: 1px;
}

.category-option-inner {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    padding: 9px 10px;
    border: 1.5px solid rgba(52, 53, 73, .12);
    border-radius: 11px;
    background: var(--chip-bg, #fff);
    color: var(--ink-soft);
    font-size: 13px;
    cursor: pointer;
    transition: border-color .16s, background .16s, color .16s, transform .16s;
}

.category-option-inner:hover {
    transform: translateY(-1px);
    border-color: var(--chip-accent, rgba(47, 125, 91, .35));
}

.category-option input:checked + .category-option-inner {
    border-color: var(--chip-accent, var(--mint-deep));
    background: var(--chip-bg, #edf9f1);
    color: var(--chip-accent, var(--mint-dark));
    font-weight: 600;
    box-shadow: 0 0 0 3px rgba(47, 125, 91, .07);
}

.category-icon {
    display: inline-grid;
    place-items: center;
    width: 16px;
    height: 16px;
    color: var(--chip-accent, var(--mint-deep));
}

.category-icon svg {
    width: 100%;
    height: 100%;
}

.publish-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 10px 18px;
}

.publish-row .submit-btn {
    margin-left: auto;
    min-height: 40px;
    padding-top: 0;
    padding-bottom: 0;
}

.radio-card {
    position: relative;
    display: block;
    cursor: pointer;
}

.radio-card > span {
    display: block;
    height: 100%;
    padding: 14px 16px;
    border: 1.5px solid rgba(52, 53, 73, .12);
    border-radius: 13px;
    background: #fff;
    transition: border-color .16s, background .16s, box-shadow .16s;
}

.radio-card > span strong {
    display: block;
    font-size: 14px;
}

.radio-card:hover > span {
    border-color: rgba(47, 125, 91, .35);
}

.radio-card input:checked + span {
    border-color: var(--mint-deep);
    background: #f2fbf6;
    box-shadow: 0 0 0 3px rgba(47, 125, 91, .08);
}

.captcha-answer {
    width: 104px;
    height: 40px;
    flex: 0 0 auto;
    padding: 0 10px;
    text-align: center;
    font-size: 13px;
    border: 1.5px solid rgba(52, 53, 73, .14);
    border-radius: 12px;
    background: #fff;
}

.captcha-img {
    width: 160px;
    height: 40px;
    border: 1px solid rgba(52, 53, 73, .12);
    border-radius: 9px;
    background: #fff;
    cursor: pointer;
}

.geetest-block {
    margin-top: 14px;
}

.geetest-block [data-geetest-slot] {
    min-height: 44px;
}

.honeypot {
    position: absolute !important;
    left: -9999px;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

/* ---------- 侧栏 ---------- */
.submit-aside {
    position: sticky;
    top: 100px;
    display: flex;
    flex-direction: column;
    gap: 20px;
}

.aside-note {
    position: relative;
    padding: 28px 24px 22px;
    /* 第八十三轮：四周圆角改成和左边表单卡同一个数（.wish-form 是 24，≤760 那档跟着它改成 18），
       原来是 8/16/10/14 四个不等的手撕纸角。
       flex:1 是让这张卡填满被 .submit-layout 拉高的那一行（父级 .submit-aside 本来就是纵向 flex）；
       只长不缩 —— 弹性盒子在列方向的自动最小尺寸是内容高，所以文字比表单高时它不会被裁。 */
    border-radius: 24px;
    flex: 1;
    background: var(--paper);
    box-shadow: var(--shadow-sm);
    transform: rotate(.6deg);
}

.aside-note h2 {
    margin-bottom: 14px;
    font-family: var(--font-hand);
    font-size: 21px;
    /* 第七十五轮：他要这一行居中。量的是「文字行矩形」的中心，不是 h2 盒子的中心 ——
       h2 是块级、本来就横满内容宽，盒子中心永远等于卡片中心，判据会空转。 */
    text-align: center;
}

.soft-list li {
    position: relative;
    padding: 7px 0 7px 18px;
    color: var(--ink-soft);
    /* 第八十七轮：只有卡片下面的内容点名微软雅黑，标题（.aside-note h2）留楷体。
       全站 --font-sans 把苹方、冬青黑排在雅黑前面，Windows 上那两支没装才正好落到雅黑
       （实测渲染就是 Microsoft YaHei），Mac 走苹方、安卓走思源黑体 ⇒ 这条真正改的是 Mac/安卓；
       真没有雅黑的机器再退回全站那一套 */
    font-family: "Microsoft YaHei", var(--font-sans);
    font-size: 13px;
    line-height: 1.75;
    border-bottom: 1px dashed rgba(52, 53, 73, .09);
}

.soft-list li:last-child {
    border-bottom: 0;
}

.soft-list li::before {
    content: "✦";
    position: absolute;
    left: 0;
    top: 7px;
    color: #c9a645;
    font-size: 10px;
}

.soft-list span {
    color: var(--mint-deep);
    font-weight: 600;
}

/* ---------- 成功页 ---------- */
.success-wrap {
    max-width: 820px;
    padding-top: 54px;
    padding-bottom: 70px;
}

.success-card {
    position: relative;
    overflow: hidden;
    padding: 54px 48px 40px;
    border: 1px solid rgba(47, 125, 91, .12);
    border-radius: 26px;
    background:
        radial-gradient(520px 260px at 50% -10%, rgba(223, 245, 232, .9), transparent 70%),
        rgba(255, 253, 247, .94);
    box-shadow: var(--shadow-md);
    text-align: center;
}

.success-card .eyebrow {
    margin-bottom: 12px;
}

.success-card h1 {
    font-family: var(--font-hand);
    font-size: clamp(30px, 4vw, 42px);
    line-height: 1.45;
    letter-spacing: 1px;
}

.seq-highlight {
    color: var(--mint-deep);
    font-size: 1.15em;
}

.success-lead {
    max-width: 560px;
    margin: 16px auto 0;
    color: var(--ink-soft);
    font-size: 15px;
}

.success-actions {
    display: flex;
    justify-content: center;
    flex-wrap: wrap;
    gap: 10px;
    margin-top: 26px;
}

.success-note {
    max-width: 560px;
    margin: 30px auto 0;
    padding: 18px 22px;
    border: 1px dashed rgba(47, 125, 91, .22);
    border-radius: 14px;
    background: rgba(255, 255, 255, .62);
    color: var(--ink-soft);
    font-size: 13px;
    line-height: 1.9;
}

.confetti span {
    position: absolute;
    width: 8px;
    height: 14px;
    border-radius: 2px;
    opacity: .55;
    animation: confetti 5s ease-in-out infinite;
}

.confetti span:nth-child(1) { left: 8%; top: 16%; background: #f6d978; transform: rotate(18deg); animation-delay: -.4s; }
.confetti span:nth-child(2) { left: 18%; top: 64%; background: #a9d6bf; transform: rotate(-24deg); animation-delay: -1.2s; }
.confetti span:nth-child(3) { left: 30%; top: 10%; background: #f3b8cb; transform: rotate(40deg); animation-delay: -2s; }
.confetti span:nth-child(4) { right: 12%; top: 18%; background: #bcd8f2; transform: rotate(-14deg); animation-delay: -.8s; }
.confetti span:nth-child(5) { right: 20%; top: 60%; background: #f6d978; transform: rotate(30deg); animation-delay: -2.6s; }
.confetti span:nth-child(6) { right: 7%; bottom: 18%; background: #d8c8f0; transform: rotate(12deg); animation-delay: -1.6s; }
.confetti span:nth-child(7) { left: 7%; bottom: 14%; background: #a9d6bf; transform: rotate(-36deg); animation-delay: -3s; }
.confetti span:nth-child(8) { left: 46%; top: 6%; background: #f3b8cb; transform: rotate(8deg); animation-delay: -2.2s; }

@keyframes confetti {
    0%, 100% { transform: translateY(0) rotate(0); }
    50% { transform: translateY(10px) rotate(18deg); }
}

/* ---------- 还愿认领 ---------- */
.claim-layout {
    max-width: 520px;
    padding-top: 18px;
    padding-bottom: 70px;
}

.claim-form {
    padding: 26px 26px 22px;
    border: 1px solid rgba(52, 53, 73, .1);
    border-radius: 18px;
    background: rgba(255, 253, 247, .92);
    box-shadow: var(--shadow-sm);
}

/* 没留邮箱的认领：没有表单可填，只给一句话；外框和上面那张认领表单保持一致 */
.claim-notice {
    padding: 26px 26px 22px;
    border: 1px solid rgba(52, 53, 73, .1);
    border-radius: 18px;
    background: rgba(255, 253, 247, .92);
    box-shadow: var(--shadow-sm);
}

.claim-notice-text {
    margin: 0;
    font-size: 15px;
    line-height: 1.8;
}

/* 已还愿的那条既不能再标记也不能删除，用一行说明占住按钮的位置，卡片高度不跳 */
.claim-done-note {
    display: inline-flex;
    align-items: center;
    min-height: 50px;
    color: var(--ink-soft);
    font-size: 13px;
}

.claim-list {
    display: grid;
    gap: 14px;
    max-width: 640px;
    padding-top: 18px;
    padding-bottom: 70px;
}

.claim-card {
    padding: 22px 24px 18px;
    border-radius: 15px;
    background: var(--paper);
    box-shadow: var(--shadow-sm);
}

.claim-card-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
}

.done-badge {
    padding: 3px 12px;
    border: 1px solid rgba(52, 53, 73, .14);
    border-radius: 999px;
    background: rgba(255, 255, 255, .75);
    color: #2E7D46;
    font-size: 12px;
    font-weight: 600;
    white-space: nowrap;
}

.done-celebrate {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    gap: 4px 10px;
    padding: 6px 13px;
    border-radius: 12px;
    background: rgba(255, 255, 255, .18);
}

.done-check {
    width: 30px;
    height: 25px;
    color: rgba(169, 58, 46, .62);               /* 和卡片上的章同一支印泥，勾和字分开两种颜色会像贴错了 */
    opacity: .78;
}

.done-word {
    color: rgba(169, 58, 46, .88);
    font-size: 16px;                            /* 窄屏也留这一档（原先 ≤760 降到 15px），跟卡片那排「还愿」逐字同大 */
    line-height: 24px;                          /* 整数：band 高 = 6 + 24 + 6 = 36，低于页脚名字那侧的 39，所以完成态不会把页脚顶高 */
    font-weight: 700;
    white-space: nowrap;
}

.done-time {
    color: rgba(169, 58, 46, .66);
    font-size: 13px;
    line-height: 18px;                          /* 整数，同上 */
    letter-spacing: .2px;
    font-variant-numeric: tabular-nums;
    align-self: flex-end;
}

/* 详情页未实现那一侧：横幅位置、字号、行高全照「梦想成真」，只把字换成绿色（和卡片那枚章同色） */
.done-celebrate.is-pray .done-word {
    color: #2f7d5b;
}

.claim-content {
    margin: 12px 0;
    color: #3e3f4f;
    font-size: 14px;
    line-height: 1.9;
    word-break: break-word;
}

.claim-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    margin-top: 14px;
}

.claim-actions .btn {
    min-height: 50px;
}

/* ---------- 还愿 / 删除弹层（详情页那两颗按钮打开，面板由 app.js 从 /claim 取回来塞进 <dialog>） ---------- */
/* 盒子外形照 /claim 那张认领表单（同一圈描边、同一个 18px 导角、同一种纸色），
   只是把它的内距挪到面板身上 —— 滚动条要贴着盒子内缘，不能贴着字。 */
.claim-dialog {
    width: min(440px, calc(100vw - 32px));
    /* 浏览器给 modal 的 dialog 自带一条 max-width（实测每边留 18px），会把我上面那个 width 顶掉：
       窄屏上量到的是 354 而不是 358。所以要一起显式写死，两边各 16px 才算数。 */
    max-width: min(440px, calc(100vw - 32px));
    max-height: calc(100vh - 32px);
    margin: auto;
    padding: 0;
    border: 1px solid rgba(52, 53, 73, .1);
    border-radius: 18px;
    background: rgba(255, 253, 247, .98);
    color: var(--ink);
    box-shadow: var(--shadow-lg);
}

.claim-dialog::backdrop {
    background: rgba(52, 53, 73, .45);
}

.claim-panel {
    max-height: calc(100vh - 64px);
    padding: 22px 24px 20px;
    overflow-y: auto;
}

.claim-panel-title {
    margin: 0 0 10px;
    font-size: 17px;
    font-weight: 700;
    line-height: 24px;
    /* 第七十五轮他把许愿页便签卡的标题居中，这一轮轮到弹层：「还愿认领」「删除这条愿望」居中。
       这一颗标题在弹层五个步骤（form/code/action/blocked/done）是同一个元素，所以一处改完全对。 */
    text-align: center;
}

.claim-panel-text {
    margin: 0 0 14px;
    color: var(--ink-soft);
    font-size: 14px;
    line-height: 1.8;
}

/* 认领表单里那两块 .field 之间本来没有缝（.field 全站只有 min-width，间距靠提交页的网格给），
   弹层是竖着排的两格，所以这里自己补 14px。整页那一版没动，仍是原来那样。 */
.claim-panel .field + .field {
    margin-top: 14px;
}

.claim-panel .form-errors {
    margin: 2px 0 14px;
}

/* 步骤三里那条愿望的预览：只留一圈淡描边，不再叠一层阴影，免得弹层里出现「卡中卡」 */
.claim-panel-card {
    margin-top: 14px;
    padding: 14px 16px 12px;
    border: 1px solid var(--line);
    border-radius: 14px;
    background: rgba(255, 255, 255, .62);
}

.claim-panel-card .claim-content {
    margin: 10px 0 8px;
}

/* 弹层里的按钮退回 .btn 本来的 42px：整页那版是 50px，跟 34px 的详情页按钮摆一起会显得笨重 */
.claim-dialog .claim-actions .btn {
    min-height: 42px;
    padding: 9px 18px;
    font-size: 14px;
}

/* ---------- 搜索 ---------- */
.search-hero {
    padding-bottom: 18px;
    text-align: center;
}

.search-hero .page-lead {
    margin-right: auto;
    margin-left: auto;
}

.search-form {
    display: flex;
    gap: 10px;
    max-width: 747px;
    margin-top: 20px;
    margin-right: auto;
    margin-left: auto;
}

.search-form input {
    flex: 1;
    min-width: 0;
    height: 46px;
    padding: 0 18px;
    border: 1.5px solid rgba(47, 125, 91, .18);
    border-radius: 999px;
    background: #fff;
    font-size: 15px;
    text-align: center;
}

.search-form input:focus {
    outline: none;
    border-color: var(--mint-deep);
    box-shadow: 0 0 0 4px rgba(47, 125, 91, .09);
}

/* ---------- 错误页 ---------- */
.error-wrap {
    display: flex;
    flex-direction: column;
    align-items: center;
    max-width: 620px;
    padding-top: 90px;
    padding-bottom: 100px;
    text-align: center;
}

.error-icon {
    display: grid;
    place-items: center;
    width: 72px;
    height: 72px;
    margin-bottom: 18px;
    border-radius: 22px;
    background: var(--cream);
    color: #866a22;
    transform: rotate(-4deg);
}

.error-wrap h1 {
    font-family: var(--font-hand);
    font-size: 36px;
}

.error-wrap p {
    margin-top: 10px;
    color: var(--muted);
}

.error-actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 10px;
    margin-top: 24px;
}

/* ---------- 页脚 ---------- */
/* 吸底页脚：内容不足一屏时页脚贴住视口底部 */
.site-main {
    flex: 1 0 auto;
}

/* 宽度跟顶部导航一样：外框直接挂 .container（宽屏 1120 - 2×24、窄屏 2×18 自动跟随），
   所以这里不写任何 padding——≤760 那条 .container 简写会把纵向内距一起抹平，
   纵向和底色都放在 .footer-inner 上就不会踩到。 */
.site-footer {
    margin-top: 10px;
    color: #e8f2ec;
}

.footer-inner {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    /* 链接与备案号整体居中；换行时每行也从中间摆开 */
    justify-content: center;
    gap: 6px 22px;
    /* 左右 14 = 导航条 header-inner 自己的内距，两块条子看起来才是同一套东西 */
    padding: 10px 14px 9px;
    /* 底色回到原来的深绿 #2c4a3d，透出背景图的 20%（.65 时白底照片能把链接对比度压到 3:1 以下，.8 + 实色文字最差也有 4.8:1） */
    background: rgba(44, 74, 61, .8);
    /* 四角倒 5px */
    border-radius: 5px;
}

.footer-links {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 4px 14px;
    font-size: 11px;
}

/* 备案号要跟链接一模一样，所以两者共用同一套字色与悬停。
   用实色不用透明度：底下是透出照片的深绿，透明字色在最亮照片上会跌破 4.5:1 */
.footer-links a,
.footer-meta a {
    color: #e8f2ec;
}

.footer-links a:hover,
.footer-meta a:hover {
    color: #fff;
    text-decoration: underline;
    text-underline-offset: 3px;
}

.footer-meta {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 4px 12px;
    padding: 0;
    border: 0;
    color: rgba(232, 242, 236, .8);
    font-size: 11px;
}

/* ---------- 背景音乐（右侧挂件列里的一颗，位置跟着 .side-dock 走） ---------- */
.music-widget {
    display: flex;
    align-items: center;
    flex: none;
}

.music-widget > audio {
    display: none;
}

.music-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    padding: 0;
    border: 1px solid rgba(47, 125, 91, .2);
    border-radius: 12px;
    background: rgba(255, 255, 255, .92);
    color: #2f6fae;
    box-shadow: var(--shadow-md);
    cursor: pointer;
    transition: background .18s, color .18s, transform .18s;
    flex: none;
}

.music-toggle:hover {
    background: #24587f;
    border-color: #24587f;
    color: #fff;
    transform: translateY(-2px);
}

.music-toggle:hover .music-disc {
    background: rgba(255, 255, 255, .24);
    animation: spin 2s linear infinite;
}

.music-disc {
    display: grid;
    place-items: center;
    width: 29px;
    height: 29px;
    border-radius: 50%;
    background: #e6eff9;
}

.music-widget.is-playing .music-disc {
    animation: spin 6s linear infinite;
}

@keyframes spin {
    to { transform: rotate(360deg); }
}

/* ---------- 回到顶部 ---------- */
.back-top {
    position: fixed;
    /* 悬浮在愿望展示区右缘外侧，不压卡片；窄屏时退回 16px 兜底 */
    right: max(16px, calc(50% - 584px));
    /* --rail-lift：往上让出的距离，由 app.js 按「滚到卡片底为止」算（愿望墙用卡片网格的底，
       内容页用那张信纸 .detail-card 的底）。上面那一列 .side-dock 用同一个数，所以一起停。
       没算出来（无 JS、窄屏）就是 0px，即钉在屏幕右下角的样子 */
    bottom: calc(26px + var(--rail-lift, 0px));
    z-index: 60;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    padding: 0;
    border: 1px solid rgba(47, 125, 91, .2);
    border-radius: 12px;
    background: rgba(255, 255, 255, .92);
    color: #2f6fae;
    box-shadow: var(--shadow-md);
    cursor: pointer;
    transition: background .18s, color .18s, transform .18s, opacity .18s, visibility .18s;
}

.back-top:hover {
    background: #24587f;
    border-color: #24587f;
    color: #fff;
    transform: translateY(-2px);
}

/* 「回到顶部」不常驻：还没滚离顶部、或整页本来就滚不动时收起来（app.js 挂 .is-hidden）。
   为什么用 opacity + visibility 而不是 display:none —— 下面那条抬升量要读这颗按钮的
   getBoundingClientRect() 当底线，display:none 时 rect 全是 0，抬升直接失效；
   而且这一列会跟着往上跳一格。visibility 收起来仍占着原来的位置，列不动、也拿得到 rect，
   顺带把它从 Tab 键序里摘掉（opacity:0 单独用还能被聚焦，那才是无障碍缺陷）。
   没 JS 时不会有 .is-hidden，按钮照常常驻。 */
.back-top.is-hidden {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
}

/* ---------- 侧边挂件列（首页 / 许愿 / 音乐 / 打赏） ---------- */
/* 整列只写一条 bottom：列底距视口底 80px，底下留 10px 给「回到顶部」（那颗自己钉在 26px）。
   音乐和打赏都在这个容器里，所以打赏关掉时整列少一颗、自动往下收一格，不会在列里留空洞；
   抬升量 --rail-lift 也只在这一处生效，不用像原来那样记着给三件各写一遍。 */
.side-dock {
    position: fixed;
    right: max(16px, calc(50% - 584px));
    bottom: calc(80px + var(--rail-lift, 0px));
    z-index: 60;
    display: flex;
    flex-direction: column;
    gap: 10px;
}

/* ≤1199 时右缘公式退到 16px 兜底，整列压进卡片右缘——恰好是每张卡心形那角。
   挪到左下角对称位置：心形在卡片右上角、详情页那颗居中，左缘没有可交互元素；
   1200 起公式正好贴住网格右缘（584 = 网格半宽 540 + 列宽 44），维持原位不动。
   「回到顶部」用同一条公式钉在列下面，一并挪，两件保持上下叠放。
   ≤760 不在这条里（手机上只有音乐挂件，动它会盖住靠左的页脚 ICP 链接）。 */
@media (min-width: 761px) and (max-width: 1199px) {
    .side-dock,
    .back-top {
        right: auto;
        left: 16px;
    }
}

.dock-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border: 1px solid rgba(47, 125, 91, .2);
    border-radius: 12px;
    background: rgba(255, 255, 255, .92);
    color: #2f6fae;
    box-shadow: var(--shadow-md);
    text-decoration: none;
    transition: background .18s, color .18s, transform .18s;
}

/* 右侧这一列（首页 / 许愿 / 音乐 / 打赏，下面另钉一颗「回到顶部」）的图形统一是蓝色，蓝得跟导航那两颗一致。
   底色仍是白纸片 + 那圈极淡的绿边，悬停/当前页才翻成深蓝底白图形。
   打赏那颗不放图标、直接印一个「赏」字，字号/行高单独定：20px 粗体在 44×44 里与旁边 26px 的图标
   同一视觉高度，行高 1 才不把字在框里顶偏；上提 1px 是量出来的（微软雅黑的「赏」字形墨迹比行盒
   中心低 1.25px，截图逐像素量），提回 1px 就对齐了 */
.dock-btn-reward .dock-char {
    font-size: 20px;
    font-weight: 700;
    line-height: 1;
    transform: translateY(-1px);
}

.dock-btn:hover {
    background: #24587f;
    border-color: #24587f;
    color: #fff;
    transform: translateY(-2px);
}

.dock-btn.is-current {
    background: #24587f;
    border-color: #24587f;
    color: #fff;
}

/* ---------- 装到桌面（挂件列最上面那颗 + iOS 那一块引导） ---------- */
/* 这颗默认不画，app.js 真拿到 beforeinstallprompt（或确认是 iOS）才补 .is-on。
   不用 hidden 属性来收：它上面的 .dock-btn 已经写了 display:flex，
   UA 样式表里那条 [hidden]{display:none} 权重更低，会被直接盖掉——按钮照样出现在屏幕上。 */
.dock-btn-install {
    display: none;
}

.dock-btn-install.is-on {
    display: flex;
}

/* 引导那块是 body 的直接子元素（不在 .side-dock 里）：挂件列只有 44px 宽，
   把它当绝对定位塞进那一列，300px 的卡片会以列的左缘往外溢，窄屏就出界了。
   left/right 都归零 + margin:auto 让它自己居中，比 mirror 挂件列那套
   「≥1200 贴右缘 / 761~1199 挪左下 / ≤760 回右缘」的横向公式少三条媒体查询。
   底距 190px：手机上这一列最高到屏幕底 178px（列底 80 + 音乐 44 + 间距 10 + 安装 44），
   这块卡在它上面还留 12px；桌面上这一列贴左右两缘，卡片在正中，横向本来就碰不上。 */
.pwa-guide {
    display: none;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 190px;
    z-index: 61;
    margin: 0 auto;
    width: min(300px, calc(100vw - 32px));
    padding: 16px 18px;
    border: 1px solid rgba(47, 125, 91, .2);
    border-radius: var(--radius);
    background: rgba(255, 255, 255, .96);
    box-shadow: var(--shadow-md);
    color: #000;
}

.pwa-guide.is-on {
    display: block;
}

.pwa-guide-title {
    margin: 0 0 8px;
    font-size: 15px;
    font-weight: 700;
    line-height: 1.4;
}

.pwa-guide-steps {
    margin: 0 0 14px;
    padding-left: 20px;
    font-size: 13px;
    line-height: 1.75;
}

.pwa-guide-steps li::marker {
    color: #2f6fae;
}

.pwa-guide-ok {
    display: flex;
    width: max-content;
    margin-left: auto;
}

/* ---------- 打赏页 ---------- */
.reward-body {
    display: grid;
    gap: 18px;
    max-width: 520px;
    padding-top: 18px;
    padding-bottom: 70px;
}

.reward-card {
    display: grid;
    justify-items: center;
    gap: 14px;
    margin: 0;
    padding: 26px 26px 22px;
    border: 1px solid var(--line);
    border-radius: 18px;
    background: rgba(255, 253, 247, .92);
    box-shadow: var(--shadow-sm);
}

.reward-qr {
    width: 220px;
    max-width: 100%;
    height: auto;
    aspect-ratio: 1 / 1;
    border-radius: 12px;
    background: #fff;
    object-fit: contain;
}

.reward-cap {
    color: var(--muted);
    font-size: 13px;
}

.reward-none {
    margin: 0;
    color: var(--ink-soft);
    font-size: 14px;
    line-height: 1.9;
    text-align: center;
}

.reward-actions {
    display: flex;
    gap: 10px;
}

.reward-actions .btn {
    flex: 1;
}

/* ---------- 关于我们 ---------- */
/* 这一页只有后台填的那段话，整页走居中轴：眉标是 inline-flex（不是块级），
   只在自己的 text-align 上写 center 挪不动它，得靠父级这一条把它当行内内容摆正；
   标题、正文各在自己的盒子里居中。字距跟在末字后面那一格会不会把墨迹推偏，
   卡片正文和详情页信纸早先都实测过（≤0.01px，见 .card-content / .detail-content 那两条注释），
   同一套值，这里不再补 text-indent。 */
.page-about .page-hero {
    text-align: center;
}

/* 这一页的「关于我们」在标题下方，所以 .eyebrow 的 14px 从下间距换成上间距；
   .eyebrow 是六个页面共用的，只在这里覆写，别的页不动。
   1440/1200/1024/900 实测「标题墨迹盒 → 眉标」正好 14，跟改前「眉标 → 标题」那一个数相同。
   整块头部因此比改前矮 2.9px（≤760 矮 0.59px），下面那张卡片跟着上移同样多：
   眉标是行内盒，基线下面那点下沉空间摆在行末和摆在行首不一样，不是间距算漏了 */
.page-about .eyebrow {
    margin-top: 14px;
    margin-bottom: 0;
}

/* 正文那一栏的 660 与上下内距是改前就有的，这轮只把它包进卡片里 */
.about-body {
    max-width: 660px;
    padding-top: 14px;
    padding-bottom: 70px;
}

/* 卡片照「打赏」那张的语言：1px 淡描边 + 18px 圆角 + 纸色底 + 最轻的投影，
   底色逐字抄 .reward-card（同为「一页一张内容卡」）。
   故意不跟后台「首页愿望卡片透明度」(--card-alpha) 联动：那条设置的名字只管首页，
   挂上来就等于后台改首页时把这一页一起调淡——这轮只动这一页的前台样式，不扩那条设置的作用范围。 */
.about-card {
    padding: 26px 28px 28px;
    border: 1px solid var(--line);
    border-radius: 18px;
    background: rgba(255, 253, 247, .92);
    box-shadow: var(--shadow-sm);
}

/* 正文抬到 15px（改前 14）：整页只有这一段要读，外面还包了 28px 内距，14 在里面显小；
   字距 1.5px 与卡片正文、详情页信纸同一个值，行高仍是 1.9（实测 28.5px） */
.about-text {
    margin: 0;
    color: var(--ink-soft);
    font-size: 15px;
    line-height: 1.9;
    letter-spacing: 1.5px;
    text-align: center;
}

/* ---------- 响应式 ---------- */
@media (max-width: 980px) {
    .header-inner {
        gap: 14px;
    }

    .submit-layout {
        grid-template-columns: 1fr;
    }

    .submit-aside {
        position: static;
        display: grid;
        grid-template-columns: 1fr;
        align-items: start;
    }

    .category-picker {
        grid-template-columns: repeat(4, minmax(0, 1fr));
    }

    .detail-card {
        padding: 16px 14px 9px;
    }
}

@media (max-width: 760px) {
    body {
        font-size: 15px;
    }

    .container {
        padding: 0 18px;
    }

    /* 上面那条简写会把纵向一起抹成 0。13 个页面靠这个 0 来收纵向内距，动不得；
       顶部导航现在也挂着 .container，它那上下 8px 悬浮缝被简写吃掉（≤760 实测头高=条高、
       圆角贴住屏幕顶），所以只在这里按 header 自己补回来。 */
    .site-header {
        padding-top: 8px;
        padding-bottom: 8px;
    }

    .header-inner {
        min-height: 54px;
        gap: 8px;
        padding-top: 0;
        padding-bottom: 0;
    }

    .header-search {
        display: flex;
        max-width: 187px;
        margin: 0;
    }

    .header-search input {
        height: 32px;
        padding: 0 27px;
        background: rgba(255, 255, 255, .9);
    }

    .header-actions .btn {
        padding: 7px 12px;
        font-size: 12px;
    }

    .header-actions {
        gap: 8px;
    }

    .wishes-section {
        padding-top: 8px;
    }

    /* 手机端首页信息条：导语一行、排序按钮一行，两块都居中；宽屏仍是左文案右按钮 */
    .home-lead-band {
        flex-direction: column;
        justify-content: center;
        align-items: center;
        gap: 8px;
        text-align: center;
    }

    .wish-grid {
        grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
        gap: 10px 8px;
        /* 上内距不再单独给：第一排那一档现在跟宽屏同一个目标（上一栏底 → 胶带顶 = 8px），
           基块那条 padding-top 直接管到这一档，实测 760/560/390/320 都是 8.0 */
    }

    /* 手机上两列本来就窄，让空轨道塌掉、剩余卡片铺满，避免出现半行空白 */
    .wish-grid-centered {
        grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    }

    .wish-card {
        --lh: 21px;  /* 窄屏格距小一档，行高和横线跟着它 */
        --rows: 4;
        --lines: 5;  /* 格数与宽屏一样比原来少一格：4 条横线 + 虚线收尾那一格 = 5 行正文 */
        /* 上内距与宽屏同一档 24：胶带跨在纸边上占掉卡顶往里 9.5px，原来的 12 只剩 1.8px，
           愿号框几乎贴在下面的分类胶囊上（宽屏第六十四轮已改 24，窄屏这档漏跟） */
        padding: 24px 10px 7px;
    }

    /* 分页条往上顶 88px：这一行本来只离文档底 48px，正落在右下角那两颗常驻挂件的竖条里
       （音乐 80~124、回到顶部 26~70），窄屏分页最右一颗被盖住点不到；顶开之后整条走出这两块。
       88 = 124 + 12，与详情页 .detail-wrap 那条页尾净空同一个数。 */
    .pagination {
        margin-bottom: 88px;
    }

    /* 窄屏纸小一圈，飞机跟着收一档；落点仍是那个 60%/64% 的偏右留白，不会顶到左右边 */
    .wish-card::before {
        background-size: 36px auto;
    }

    .wish-seq {
        font-size: 14px;
    }

    .card-stamp {
        height: 18px;
        padding: 0 6px;
        font-size: 12px;
        line-height: 14px;
        border-width: 1.5px;
        letter-spacing: .5px;
    }

    .cat-pill {
        padding: 1px 6px;
        font-size: 10px;
    }

    .card-content {
        font-size: 13px;
    }

    .author-name {
        max-width: 80px;
        font-size: 10px;
    }

    .card-time {
        font-size: 9px;
    }

    /* 心还是那么大，但手机上的可点范围撑到 42px 见方 */
    .like-form-card .like-btn::before {
        content: "";
        position: absolute;
        inset: -9px -6px;
    }

    .like-form-card .like-mark {
        width: 15px;
        height: 15px;
    }

    .page-hero {
        padding-top: 20px;
    }

    /* 同 .detail-wrap 那条：≤760 的 `.container { padding: 0 18px }` 简写把纵向内距
       一起抹成 0，这里给关于页重新写回来。下内距 88 是躲常驻音乐长条的（76+12），
       不然滚到底时卡片最后几行压在长条底下 */
    .about-body {
        padding-top: 18px;
        padding-bottom: 88px;
    }

    .about-card {
        padding: 22px 20px 24px;
    }

    .detail-wrap {
        padding-top: 8px; /* 与 .category-strip 那条同一个数（他要求内容页 = 首页分类栏离导航的距离） */
        /* ≤760 那条 `.container { padding: 0 18px }` 的简写会把纵向内距一起抹成 0，
           页尾净空得在这儿重新写一次；窄屏长条常驻展开，缺了它信纸里那一行按钮直接被压住 */
        padding-bottom: 88px;
    }

    /* 发布页同一个坑：宽屏那条收到 8 之后，这一档还得把 88 写回来（简写抹掉的正是它）。
       实测（≤760 滚到底）：缺了它 `.submit-layout` 的内容底边落在 852，常驻音乐长条占 776~820，
       最后一块（窄屏排在表单下面的右侧卡）被压进 76px；留着它内容底边 764，比长条顶还高 12。
       上距 10 也是被同一条简写抹掉的，本轮没动它（他圈的是页尾那一条）。 */
    .submit-layout {
        padding-bottom: 88px;
    }

    .detail-card {
        --lh: 40px;
        padding: 12px 10px 7px;
        border-radius: 8px 18px 12px 16px;
    }

    /* 窄屏信纸只画一只、放在正中间（他定的：角落那几只是桌面才有的）。
       尺寸跟着「2 倍」走：原来和列表卡片一样 40，现在 80。
       位置交给 center：::before 铺满信纸的 padding box，卡片内边距上下左右都算进去后
       这个 center 就是信纸的正中心，跟 --rows/--lh/赞数行多高都没关系，纸变宽变窄都跟着走 */
    .detail-card::before {
        background-image: var(--plane);
        background-size: 80px auto;
        background-position: center center;
    }

    /* 窄屏纸窄（卡片内边距只有 10），盒子 34×60、图形 22px 宽：
       墨迹 22×24，跟原来那个文字版一样大，描边比原来细 4 成。
       纵向同宽屏那一条：钉在这张纸正中间那条横线上（七格的第四条），半盒高 30 → 减 30.5px。 */
    .detail-edge {
        width: 34px;
        height: 60px;
        top: calc(4 * var(--lh) - 30.5px);
        margin-top: 0;
    }

    .detail-edge svg {
        width: 22px;
    }

    .edge-prev {
        left: -8px;
    }

    .edge-next {
        right: -8px;
    }

    .detail-like {
        margin-top: 22px; /* 窄屏卡内边距只有 12px，胶带旋转后的最低点在 31px，18px 会让心带被压住 1.3px */
    }

    .done-check {
        width: 26px;
        height: 21px;
    }

    .detail-content {
        font-size: 21.375px; /* 28.5 的四分之三，和桌面同一档 */
        margin: 26px 0 calc(var(--lh) - 1px);
    }

    .wish-seq-lg {
        font-size: 25px;
    }

    .wish-form {
        padding: 28px 20px 22px;
        border-radius: 18px;
    }

    /* 第八十三轮：跟着左边那张一起收到 18，两档各自成对（宽屏 24 / 窄屏 18），
       不是把右侧写死成一个数 —— 否则窄屏就又不一致了。 */
    .aside-note {
        border-radius: 18px;
    }

    .field-row,
    .field-row-submit {
        grid-template-columns: 1fr;
    }

    .category-picker {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }

    .category-option-inner {
        padding: 10px 11px;
    }

    .captcha-img {
        /* 与 /captcha.png 的画布同宽（160×40），改成 140 会把数字横向压扁 12.5% */
        width: 160px;
        height: 40px;
    }

    .captcha-answer {
        height: 40px;
    }

    .publish-row .submit-btn {
        width: 100%;
        min-height: 40px;
    }

    .success-wrap {
        padding-top: 30px;
    }

    .success-card {
        padding: 40px 22px 30px;
    }

    .claim-layout {
        max-width: none;
    }

    .search-form {
        flex-direction: column;
    }

    .search-form .btn {
        width: 100%;
    }

    .footer-inner {
        gap: 4px 14px;
    }

    /* 吸底那一行占掉屏幕最底下 44px（离底 16 起），页脚条整体上提 70（44 + 16 + 10 净空），
       滚到底时「关于我们 / Sitemap / 站长入口 / 备案号」都在挂件行上方，点得到。
       多出来的 70px 是背景图，只在页面最底出现；安全区一并加，iPhone 上不会压到 home 条。 */
    .site-footer {
        padding-bottom: calc(70px + env(safe-area-inset-bottom, 0px));
    }

    /* 这一档收掉打赏那颗（.dock-btn 里藏掉，下面按颗打开要留的）；
       首页 / 许愿 / 安装 在下面单独放开，音乐不是 .dock-btn 所以一直在。 */
    .side-dock .dock-btn {
        display: none;
    }

    /* 手机档吸底居中：四颗（安装/首页/音乐/许愿）横排一行，整行落在屏幕正中，离底 16 + 安全区。
       横排是第六十六/九十六轮定下的——竖排滚到底会压住分页条末位页码和详情页按钮。
       容器必须按内容宽度收缩（left/right 归零 + width:max-content + 自动外边距），
       不能铺满整行：铺满后那条透明带会盖住同一高度上的「回到顶部」，点它变成点空列。
       --rail-lift 这一档不算（app.js 只给 ≥761 处理），所以 bottom 不写变量。
       顺序读 DOM：安装 → 首页 → 音乐 → 许愿（与桌面竖列同一份顺序）。 */
    .side-dock {
        left: 0;
        right: 0;
        bottom: calc(16px + env(safe-area-inset-bottom, 0px));
        width: max-content;
        margin: 0 auto;
        flex-direction: row;
        align-items: center;
        justify-content: center;
    }

    .side-dock .dock-btn-home,
    .side-dock .dock-btn-submit {
        display: flex;
    }

    /* 装到桌面那颗例外：手机上「加到主屏幕」恰恰是最需要这颗的地方，收掉它这条功能就只剩桌面端能点了。
       权重与上面那条一样（0,2,0），靠写在它后面取胜——所以两条必须在同一个媒体查询里、顺序不能换。 */
    .side-dock .dock-btn-install.is-on {
        display: flex;
    }

    /* 「回到顶部」这一档只钉位置、不接管显隐：app.js 的「滚离顶部 120px 内收起」照旧生效，
       下滑之后才出现。位置跟挂件行同一档高度：底 16、右 16（≤350 横向挤不下，由下面那条抬到行上方）。 */
    .back-top {
        right: 16px;
        bottom: calc(16px + env(safe-area-inset-bottom, 0px));
    }

    /* 页尾插画同样收掉：横画在这档宽度缩到 354 也只剩 25px 高，太挤（原先还有常驻展开的音乐长条压这块空地，那条已删） */
    .wish-band {
        display: none;
    }
}

/* ≤350：四颗 44 + 三缝 10 居中后，右缘离屏幕只剩不到一颗按钮的位置，
   再把「回到顶部」摆同一行就会压住最右那颗（320 宽实测叠 4.5px）。
   这档把「回到顶部」抬到挂件行上方 10px 处（它仍按滚动位置出现/收起），
   页脚相应再让出它的高度（70 → 120），
   三者从下到上是：挂件行 16~60 / 回到顶部 70~114 / 页脚条 120 起。 */
@media (max-width: 350px) {
    .back-top {
        bottom: calc(70px + env(safe-area-inset-bottom, 0px));
    }

    .site-footer {
        padding-bottom: calc(120px + env(safe-area-inset-bottom, 0px));
    }
}

/* ≤700：换两行，顺序按他指定的 —— 三颗按钮在上、搜索框在下，且搜索框和上排按钮组一样宽。
   做法是把两行都塞进同一条 `auto` 列（第 2 列）：auto 轨道宽 = 组里最宽那行的 max-content，
   搜索框 `justify-self:stretch` 就正好等于按钮组的宽，且组宽一变（≤420 按钮缩字号变窄到 210）
   搜索框跟着变，不写死 238/210。他 2026-10-30 要求窄屏不显示 logo，品牌整块 `display:none`。
   为什么搜索框必须有 `min-width:0`：grid 项目默认下限是 min-content，输入框的 min-content
   比按钮组宽，去掉这条它会把 auto 轨道撑到比自己该显示的更宽，「和按钮一样宽」立刻失效。
   为什么两侧还是 `minmax(0,1fr)`：左右轨道收成 0 但 `column-gap` 照样插在中间，
   所以按钮组和搜索框靠 `grid-column:2` + `justify-self:center/stretch` 对齐同一条轴，
   不再横跨整行（上一版 `1/-1` 是贴条子两缘，宽度由条子决定，跟按钮组无关）。
   为什么断点还是 700 而不是更低：一行时搜索框可用文字宽要到 640 才刚够提示语（98.5 vs 96.47），
   630 以下就被裁 —— 一行的瓶颈从来是搜索框而不是品牌宽，所以 logo 收不收到处都在 700 这条线上。 */
@media (max-width: 700px) {
    .header-inner {
        grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
        row-gap: 7px;
        padding-top: 7px;
        padding-bottom: 9px;
    }

    .header-actions {
        grid-row: 1;
        grid-column: 2;
        justify-self: center;
    }

    .brand {
        display: none;
    }

    .header-search {
        grid-row: 2;
        grid-column: 2;
        justify-self: stretch;
        min-width: 0;
        width: auto;
        max-width: none;
    }
}

/* 详情页两态等高（他说「以高的为标准」）：页脚里「梦想成真 ✓ + 实现于…」那一排比
   「祈祷中…」宽得多（实测 292.6 vs 89.4），所以在中间那一档放不下同排（页脚净宽 = 视口 − 56，
   470 放不下、471 放得下，即视口 ≤526）时，已实现换成了两排、未实现还是一排，两态差 48px。
   把横幅在这一档也横贯整张纸，两态就都是两排、页脚同为 106px。
   （这一档原来定在 520：那是「祈祷中」还是 15px 字号时量的，横幅改回 16px 后宽了 4px，
     521~526 会变成已实现两排、未实现一排，所以整档往上调 6。） */
@media (max-width: 526px) {
    .done-celebrate {
        width: 100%;
    }
}

/* 再窄一档（视口 ≤348）：横幅里的「✓ + 梦想成真 + 实现于 日期」单行需要 266.6px 净宽，
   348 这一档横幅 292 宽、去掉自身 13×2 内边距只剩 266，所以「实现于…」自己掉到第二行，
   已实现那一排从 36 变 58。给横幅占住两行的高度，「祈祷中…」跟着长到同一高，两态卡片同高。
   349 这一档实测横幅 293（净宽 267）放得下单行，两态本来就等高，这一段不补，免得反过来把矮的顶高。
   （这一档从 340 上调到 348：字号 15→16 让这一排宽了 4px，341~348 实测变成已实现三排、未实现两排。） */
@media (max-width: 348px) {
    .done-celebrate {
        min-height: 58px;
    }
}

@media (max-width: 420px) {
    .header-inner {
        gap: 6px;
    }

    .header-actions {
        gap: 6px;
    }

    .header-actions .btn {
        padding: 6px 9px;
        font-size: 11.5px;
    }

    .pagination {
        gap: 4px;
    }

    .page-btn {
        min-width: 34px;
        height: 34px;
        padding: 0 9px;
    }
}

@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: .001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .001ms !important;
    }

    /* 例外：音乐图标悬停旋转（站长本人浏览器常开减少动态，要求此特效始终生效） */
    .music-toggle:hover .music-disc {
        animation-duration: 2s !important;
        animation-iteration-count: infinite !important;
    }
}

@media print {
    .site-header,
    .site-footer,
    .music-widget,
    .side-dock,
    .pwa-guide,
    .back-top,
    .wish-band,
    .detail-tools,
    .detail-edges {
        display: none !important;
    }

    body {
        background: #fff;
    }

    .detail-card {
        box-shadow: none;
        border: 1px solid #ddd;
    }
}
