/* ==========================================================================
   许愿墙 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: #b08a2e;
    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;
}

.spark {
    color: #d9ae3f;
    font-size: 15px;
}

/* ---------- 分类导航 ---------- */
.category-strip {
    position: relative;
    margin: 16px 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;
}

.section-head {
    margin-bottom: 14px;
}

.section-title {
    position: relative;
    z-index: 0;
    display: inline-block;
    padding: 3px 12px 3px 8px;
    font-family: var(--font-hand);
    font-size: 23px;
    letter-spacing: 1px;
}

.section-title::before {
    content: "";
    position: absolute;
    inset: 12% -8px;
    z-index: -1;
    border-radius: 14px;
    background: linear-gradient(100deg, rgba(223, 245, 232, .95), rgba(255, 246, 201, .88) 55%, rgba(226, 241, 255, .95));
    box-shadow: inset 0 0 0 1px rgba(47, 125, 91, .08);
}

.section-meta {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 8px 12px;
    margin-top: 8px;
}

.section-sub {
    display: inline-block;
    margin: 0;
    padding: 4px 12px;
    border: 1px solid rgba(47, 125, 91, .1);
    border-radius: 8px;
    background: rgba(255, 255, 255, .62);
    color: var(--muted);
    font-size: 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;
}

/* 搜索结果条数可多可少，轨道不拉伸到整行、整体居中才不会「一条结果贴着左边」。
   上限 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, #b08a2e);
    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, #b08a2e);
    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: #8a8b98;
    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: #b08a2e;
    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;
}

/* ---------- 分页 ---------- */
.pagination {
    display: flex;
    justify-content: center;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    margin-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;
}

.page-btn: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）。同比例的原图在两边等比缩放，画的就是同一只纸飞机。
   上方留 30：许愿页那 30 是网格行距给的，所以 .submit-band 把自己的 margin 归零，两边逐像素同距。
   ≤760 不画：那一档音乐长条常驻屏幕底，正好盖在这块空地上；1200 宽的横画缩到 354 也只剩 25px 高，
   看不出画的是什么。 */
.wish-band {
    display: block;
    width: 100%;
    height: auto;
    margin-top: 30px;
    pointer-events: none;
    user-select: none;
}

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

/* ---------- 详情页 ---------- */
.detail-wrap {
    padding-top: 30px;
    /* 88 = 音乐挂件那条 fixed 带子要求的净空 76 + 12 口气。
       挂件底边离屏幕底 80、高 44，滚到页底时它占住「文档底往上 124~80」这一段，
       而页脚自己占掉最后 38 + 上外边距 10 = 48 —— 正文要落在 124 以上，就得留 76。
       详情页信纸的底是那一行按钮（复制链接 / 还愿 + 删除 / 同分类愿望），按钮行下面只有信纸自己
       那点内距加这一条，所以净空写在这里；第五十二轮底部那条装饰插画删掉后，缺了它按钮行就落进挂件带。 */
    padding-bottom: 88px;
}

.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，多一格就把纸的格距顶歪了）。
   居中不用 transform：top:50% + margin-top 负一半，跟他那台关了动画的浏览器里胶带同一套做法。
   宽度给到 30（<> 的墨迹只有十几 px）：点得中的范围比字大，但又只吃纸两边的空白 ——
   正文左右各让了 24px（.detail-content 的 padding），实测最宽一行的墨迹离这里还有几 px。
   颜色不写：全站 a / a:hover 自带（绿→深绿），悬停只变色、不位移不放大。
   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;
    top: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 36px;
    margin-top: -18px;
    pointer-events: auto;
    font-size: 26px;
    font-weight: 700;
    line-height: 1;
    user-select: none;
}

.edge-prev {
    left: 4px;
}

.edge-next {
    right: 4px;
}

.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, #b08a2e);
    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, #b08a2e);
    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：虚线正好落在正文的下一格上 */
    /* 左右各让出 24px：卡片正中那两颗 <> 就住在这一条空白里（.detail-edge，桌面那颗的右缘在 34px，
       这里从卡片内边距 14px 起算，字从 38px 开始）。只让横向、纵向一格不动，
       所以横线格距、每行字踩线、居中排布都不受影响（两边一样宽，纸还是对称的）。 */
    padding: 0 24px;
    color: #333444;
    font-family: var(--font-hand);
    font-size: 31.5px;      /* 21 的一点五倍，他要正文再大一档 */
    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 {
    padding-top: 44px;
    padding-bottom: 16px;
}

.submit-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 320px;
    align-items: start;
    gap: 30px;
    padding-top: 10px;
    /* 88：与详情页 `.detail-wrap` 同一套页尾净空（原来是 64，插画接进来后长条的顶边会啃掉它下沿 12px）。
       两块共用一张图，页脚距插画底也顺理成章对齐成同一个数（98）。 */
    padding-bottom: 88px;
}

.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,
.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;
    border-radius: 8px 16px 10px 14px;
    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;
}

.soft-list li {
    position: relative;
    padding: 7px 0 7px 18px;
    color: var(--ink-soft);
    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;
}

.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: #b08a2e;
    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，透出背景图的 35% */
    background: rgba(44, 74, 61, .65);
    /* 四角倒 5px */
    border-radius: 5px;
}

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

/* 备案号要跟链接一模一样，所以两者共用同一套字色与悬停 */
.footer-links a,
.footer-meta a {
    color: rgba(232, 242, 236, .8);
}

.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;
}

.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;
}

/* ---------- 打赏页 ---------- */
.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;
}

/* 正文那一栏的 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;
    }

    .stat-label {
        font-size: 10.5px;
    }

    .stat-note,
    .stat-divider {
        display: none;
    }

    .stat {
        flex: 1 1 40%;
        flex-direction: column;
        align-items: flex-start;
        gap: 2px;
    }

    .section-head {
        align-items: center;
        flex-direction: row;
        flex-wrap: wrap;
        gap: 8px 12px;
    }

    .wish-grid {
        grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
        gap: 10px 8px;
    }

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

    .wish-card {
        --lh: 21px;  /* 窄屏格距小一档，行高和横线跟着它 */
        --rows: 4;
        --lines: 5;  /* 格数与宽屏一样比原来少一格：4 条横线 + 虚线收尾那一格 = 5 行正文 */
        padding: 12px 10px 7px;
    }

    /* 窄屏纸小一圈，飞机跟着收一档；落点仍是那个 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: 20px;
        /* ≤760 那条 `.container { padding: 0 18px }` 的简写会把纵向内距一起抹成 0，
           页尾净空得在这儿重新写一次；窄屏长条常驻展开，缺了它信纸里那一行按钮直接被压住 */
        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），两颗 <> 各收 4px 宽、往里挪 1px，
       给正文让出的那条 24px 空白留够余量（正文从 10+24=34 起，箭头右缘在 3+26=29） */
    .detail-edge {
        width: 26px;
        height: 32px;
        margin-top: -16px; /* 上面 margin-top 是负「一半」，这里跟着 32 走，不然两颗会偏下 2px */
        font-size: 22px;
    }

    .edge-prev {
        left: 3px;
    }

    .edge-next {
        right: 3px;
    }

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

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

    .detail-content {
        font-size: 28.5px; /* 19 的一点五倍，和桌面同一档放大 */
        margin: 26px 0 calc(var(--lh) - 1px);
    }

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

    .wish-form {
        padding: 28px 20px 22px;
        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;
    }

    /* ≤760 收起这一列的三颗链子（首页 / 许愿 / 打赏）：「我要许愿」头部按钮已有，打赏页在这档宽度没有入口了
       （页脚链接按站长的要求只剩关于我们 / Sitemap / 站长入口三颗）。音乐那颗不是 .dock-btn，
       仍留在列底 —— 改前是「整栏 display:none + 音乐另外单独 fixed 一支」，视觉效果一样，
       只是这里改成藏链子，好处是列不用再靠两条 bottom 对齐。 */
    .side-dock .dock-btn {
        display: none;
    }

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

/* ≤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;
    }

    .wish-card {
        padding: 12px 10px 7px;
    }

    .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,
    .wish-band,
    .detail-tools,
    .detail-edges {
        display: none !important;
    }

    body {
        background: #fff;
    }

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