/* ============================================================
   谷勒大宅 — 基础样式（手机优先）
   全屏图片 / 错落悬浮卡片 / 玻璃拟态，按参考板式 1:1 还原
   ============================================================ */

:root{
  /* base surfaces */
  --bg:            #07070A;
  --ink:           #FFFFFF;
  --ink-2:         rgba(255,255,255,.62);
  --ink-3:         rgba(255,255,255,.42);
  --ink-4:         rgba(255,255,255,.24);

  /* glass / card */
  --glass:         rgba(255,255,255,.055);
  --glass-2:       rgba(255,255,255,.085);
  --stroke:        rgba(255,255,255,.10);
  --stroke-2:      rgba(255,255,255,.16);

  /* accents */
  --gold:          #C9A46A;
  --amber:         #D8A94F;
  --amber-soft:    rgba(216,169,79,.16);
  --terra:         #C4703F;
  /*
   * 档期状态色。绿色 = 「可预约」，语义上用青瓷玉绿而不是标准 UI 绿
   * （#4CAF50 这类饱和绿在近黑 + 暖金的系统里会显廉价）。
   * 将来档期吃紧可回落到 --amber，约满用 --terra，形成一套状态语言。
   */
  --jade:          #6FBF9F;
  --jade-soft:     rgba(111,191,159,.15);

  /* geometry */
  --pad:           18px;
  --radius-l:      26px;
  --radius-m:      20px;
  --radius-s:      15px;

  --tabbar-h:      56px;
  --ease:          cubic-bezier(.22,1,.36,1);
  --ease-soft:     cubic-bezier(.4,0,.2,1);

  /* stops-rail geometry — the connecting line is derived from these */
  --node-cap-h:    22px;
  --node-dot:      11px;
  --node-dot-mt:   11px;

  --font-ui:       "Inter","HarmonyOS Sans SC","PingFang SC","Microsoft YaHei",system-ui,sans-serif;
  --font-disp:     "Cormorant Garamond","Instrument Serif","Noto Serif SC",Georgia,serif;
  --font-serif-cn: "Noto Serif SC","Songti SC",serif;
}

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

html,body{
  height:100%;
  background:#000;
  color:var(--ink);
  font-family:var(--font-ui);
  -webkit-font-smoothing:antialiased;
  -webkit-tap-highlight-color:transparent;
  overscroll-behavior:none;
}

body{ display:flex; align-items:center; justify-content:center; }

button{ font:inherit; color:inherit; background:none; border:0; cursor:pointer; }
img{ display:block; width:100%; height:100%; object-fit:cover; }

/* --------------------------------------------------------------------
   图片淡入 / 区块入场：**默认可见，只有确认 JS 可用时才隐藏**。

   这两条如果写成无条件 opacity:0，一旦 JS 报错（旧内核最容易踩的
   就是语法不兼容），页面就永远停在「全部透明」的状态 —— 配合黑色
   底就是又一次「黑屏」。

   开关是 html 上的 .gq-js，由 <head> 里一段纯 ES5 内联脚本加上。
   内联脚本没有语法门槛，任何内核都能执行，所以只要浏览器能跑脚本
   就有动画；跑不了脚本则内容直接可见，不会变成空白页。
   -------------------------------------------------------------------- */
html.gq-js img[data-img],
html.gq-js .hero-img{
  opacity:0;
  transition:opacity .9s var(--ease-soft);
}
html.gq-js img.is-loaded,
html.gq-js .hero-img.is-loaded{ opacity:1; }

/* ---- phone frame ---------------------------------------------------- */
.app{
  position:relative;

  /* ------------------------------------------------------------------
     ⚠ 兼容性红线 —— 这一段曾经造成手机端「黑屏」故障，不要改回去。

     min() 需要 Chrome 79+，dvh 需要 Chrome 108+。百度(T7)、夸克(U4)
     这类旧内核会把这两条声明整条丢弃，于是 .app 既没有宽度也没有
     高度；而它内部的子元素全部是 position:absolute（不贡献固有
     尺寸），.app 因此塌成 0×0，再被 overflow:hidden 一裁，
     整页只剩 html/body 的黑色底色 —— 表现就是「整屏纯黑」。

     微信/QQ 用的是较新的 XWeb/X5 内核，两条声明都生效，所以显示正常。

     正确做法：尺寸用全平台通吃的 width/max-width/height(vh) 打底，
     min() 与 dvh 只作为「支持的浏览器上的增强」，写在 @supports 里。
     ------------------------------------------------------------------ */
  width:430px;
  max-width:100%;
  height:100vh;
  max-height:932px;
  flex:0 0 auto;
  background:var(--bg);
  overflow:hidden;
  isolation:isolate;
}
/* 支持 dvh 的内核：修正移动端地址栏收放时的高度跳动 */
@supports (height:100dvh){
  .app{ height:100dvh; }
}
@media (min-width:520px){
  body{ background:#0B0B0D; }
  .app{
    height:100vh;
    max-height:900px;
    border-radius:44px;
    box-shadow:
      0 0 0 1px rgba(255,255,255,.07),
      0 60px 120px -30px rgba(0,0,0,.9);
  }
  @supports (height:100dvh){
    .app{ height:min(100dvh, 900px); }
  }
  /* keep the ambient wash from bleeding past the rounded frame */
  .app::before{ border-radius:inherit; }
}
/* 顶部状态栏底衬。
   这里原本是一条 30px 的纯黑渐变，首屏照片顶上因此压着一条黑边 ——
   手机端尤其刺眼，整页像被切掉一块。现在只保留极淡的一层，
   够状态栏文字读得清就行，不再形成「黑屏」。 */
.app::after{
  content:"";
  position:absolute; top:0; left:0; right:0;
  /* env() 需要 Chrome 69+；旧内核会给一个不带头条的高度，先兜底 */
  height:22px;
  height:calc(22px + env(safe-area-inset-top,0px));
  z-index:2;
  background:linear-gradient(180deg, rgba(0,0,0,.32) 0%, rgba(0,0,0,.12) 50%, transparent 100%);
  pointer-events:none;
}

/* ambient warm wash behind everything */
.app::before{
  content:"";
  position:absolute; top:0; right:0; bottom:0; left:0; inset:0;
  z-index:0;
  background:
    radial-gradient(120% 55% at 78% 4%, rgba(201,164,106,.14), transparent 62%),
    radial-gradient(90% 40% at 8% 30%, rgba(90,70,140,.10), transparent 70%);
  pointer-events:none;
}

/* ---- scroll container ---------------------------------------------- */
.scroll{
  position:absolute; top:0; right:0; bottom:0; left:0; inset:0;
  z-index:1;
  overflow-y:auto;
  overflow-x:hidden;
  scrollbar-width:none;
  -webkit-overflow-scrolling:touch;
  scroll-behavior:smooth;
  padding-bottom:calc(var(--tabbar-h) + 34px);
  padding-bottom:calc(var(--tabbar-h) + 34px + env(safe-area-inset-bottom,0px));
}
.scroll::-webkit-scrollbar{ display:none; }

/* 内页顶部留白。首页由 body.gq-front 归零：首屏直接顶到最上面。 */
.status-spacer{
  height:16px;
  height:calc(16px + env(safe-area-inset-top,0px));
}
body.gq-front .status-spacer{ height:0; }

/* ============================================================
   SITE HEAD — 全站站点标识
   首页由 CSS 绝对定位到首屏左上角，浮在照片上；内页是普通页头。
   同一份 DOM 贯穿全站，内页因此不再「上面光秃秃」。
   ============================================================ */
.site-head{
  position:relative;
  z-index:6;
  display:flex; align-items:center; justify-content:space-between;
  gap:12px;
  padding:12px var(--pad) 10px;
}
/* 首页：脱离文档流，直接压在首屏照片左上角 */
body.gq-front .site-head{
  position:absolute;
  top:0; left:0; right:0;
  padding-top:calc(12px + env(safe-area-inset-top,0px));
  padding-bottom:18px;
}

.sh-brand{
  display:flex; align-items:center; gap:11px;
  text-decoration:none;
  color:inherit;
}
.sh-mark{
  flex:0 0 auto;
  width:34px; height:34px;
  border-radius:11px;
  display:grid; place-items:center;
  font-family:var(--font-disp);
  font-size:15px; font-weight:600;
  letter-spacing:.01em;
  color:#14110C;
  background:linear-gradient(150deg,#EED7A9,#C9A46A);
  box-shadow:0 6px 18px -8px rgba(201,164,106,.7);
}
.sh-text{ display:flex; flex-direction:column; gap:3px; }
.sh-title{
  font-family:var(--font-disp);
  font-size:17px; font-weight:600;
  line-height:1;
  letter-spacing:-.01em;
}
.sh-sub{
  font-size:10px;
  letter-spacing:.06em;
  color:rgba(255,255,255,.55);
}

/* 右上角：复制微信。
   原来这里只是两个字「杭州」（纯装饰），同样一块地方改成可点的动作，
   信息密度更高但体型不变 —— 一颗小玻璃胶囊，浮在照片上也压得住。 */
.sh-act{
  flex:0 0 auto;
  display:inline-flex; align-items:center; gap:6px;
  padding:7px 12px 7px 10px;
  border:1px solid rgba(255,255,255,.16);
  border-radius:999px;
  font:inherit;
  font-size:10.5px; font-weight:500;
  letter-spacing:.06em;
  line-height:1;
  color:rgba(255,255,255,.84);
  background:rgba(255,255,255,.10);
  backdrop-filter:blur(14px) saturate(1.3);
  -webkit-backdrop-filter:blur(14px) saturate(1.3);
  box-shadow:0 8px 20px -12px rgba(0,0,0,.8);
  transition:color .3s var(--ease), background .3s var(--ease), border-color .3s var(--ease), transform .3s var(--ease);
}
.sh-act svg{ width:13px; height:13px; flex:0 0 auto; }
.sh-act:active{ transform:scale(.94); background:rgba(255,255,255,.19); }
/* 复制成功的短暂反馈，与脚本里的 .is-copied 呼应 */
.sh-act.is-copied{
  color:#F2DCB0;
  border-color:rgba(232,191,126,.46);
  background:rgba(232,191,126,.17);
}

/* ============================================================
   BREADCRUMB
   ============================================================ */
.crumb{
  position:relative;
  z-index:5;
  padding:0 var(--pad);
}
.crumb-list{
  list-style:none;
  display:flex; flex-wrap:wrap; align-items:center;
  gap:4px 8px;
  max-width:860px;
  margin-inline:auto;
  font-size:11.5px;
  line-height:1.6;
  color:var(--ink-4);
}
.crumb-item{
  display:inline-flex; align-items:center; gap:8px;
}
.crumb-item + .crumb-item::before{
  content:"›";
  font-size:12px;
  color:rgba(255,255,255,.20);
}
.crumb-item a{
  color:var(--ink-3);
  text-decoration:none;
  transition:color .3s var(--ease);
}
.crumb-item a:hover{ color:var(--gold); }
.crumb-item [aria-current]{
  color:rgba(255,255,255,.66);
}

/* ============================================================
   HERO
   ============================================================ */
.hero{
  position:relative;
  /* 首屏不再一路铺到底。
     ------------
     原来的 100vh 让整屏只剩左上角一小块文字，左下角空掉一大片；
     现在收成「约七成屏」，底部让出的一块正好被作品卡片栈压上来，
     视线从标题自然滑到卡片上 —— 这是参考版式的构图。
     ⚠ 与 .app 同一套兼容策略 —— 数值用 vh / calc 打底，
     dvh 只做增强，否则旧内核（百度 T7 / 夸克 U4）丢掉声明后首屏会塌。 */
  height:70vh;
  height:calc(100vh - 216px);
  min-height:420px;
  /* MUST clip: .hero-media is overscanned and the parallax pushes it
     further down, so without this the photo bleeds out behind the
     transparent sections that follow. The deck is a SIBLING of this
     element, so clipping here does not affect its overlap. */
  overflow:hidden;
  z-index:3;
}
@supports (height:100dvh){
  .hero{ height:calc(100dvh - 216px); }
}
.hero-media{
  position:absolute; top:-6%; right:0; bottom:-10%; left:0; inset:-6% 0 -10% 0;
  overflow:hidden;
  will-change:transform;
}
.hero-img{
  width:100%; height:100%;
  object-fit:cover;
  object-position:50% 46%;
  filter:saturate(1.06) contrast(1.03);
}
/* 左上角压暗：文案整体靠左上，所以暗部也必须压在左上。
   横向从左上向右下衰减，纵向再从顶部压一层，两层叠加保证
   标题、副标在任何一张照片上都读得清。 */
.hero-shade{
  position:absolute; top:0; right:0; bottom:0; left:0; inset:0;
  /* v1.9.6 随首图从城市夜景换成浅色室内场景同步减重：
     旧值（.74/.86）是按深色天际线调的，压在浅色图上会显得脏暗。
     减重后标题仍由左上两层渐变兜底，图面保持高调透亮。 */
  background:
    linear-gradient(180deg, rgba(6,7,12,.48) 0%, rgba(6,7,12,.22) 20%, rgba(6,7,12,0) 44%, rgba(7,7,10,.34) 80%, var(--bg) 100%),
    linear-gradient(104deg, rgba(6,7,12,.58) 0%, rgba(6,7,12,.32) 34%, rgba(6,7,12,.05) 62%, transparent 82%);
}
/* warm light bloom */
.hero-glow{
  position:absolute;
  width:58%;
  aspect-ratio:1;
  right:8%; top:20%;
  border-radius:50%;
  background:radial-gradient(circle, rgba(255,214,150,.36), rgba(255,190,120,.10) 42%, transparent 68%);
  filter:blur(26px);
  mix-blend-mode:screen;
  pointer-events:none;
}
/* aspect-ratio 需要 Chrome 88+；旧内核给一个等效的固定高度 */
@supports not (aspect-ratio:1/1){
  .hero-glow{ height:220px; }
}

/* 手写签名：落在右侧中段。
   卡片栈会从首屏底部压上来，所以不能再贴着底边。 */
.hero-script{
  position:absolute;
  right:22px; bottom:26%;
  z-index:2;
  text-align:center;
  font-family:"Cormorant Garamond",Georgia,serif;
  font-style:italic;
  font-weight:500;
  font-size:19px;
  line-height:1.12;
  color:rgba(255,255,255,.94);
  text-shadow:0 2px 14px rgba(0,0,0,.5);
  transform:rotate(-6deg);
  pointer-events:none;
}
.hero-script span{ display:inline-block; padding-left:10px; }

/* 文案靠左上：顶部让出站点标识（.site-head）的高度 */
.hero-inner{
  position:relative; z-index:2;
  height:100%;
  padding:86px var(--pad) 26px;
  display:flex; flex-direction:column; justify-content:flex-start;
  will-change:transform,opacity;
}

.hero-title-wrap{ padding-bottom:0; }
.hero-title{
  font-family:var(--font-disp);
  font-weight:400;
  font-size:clamp(42px, 12.4vw, 53px);
  line-height:.98;
  letter-spacing:-.015em;
  text-shadow:0 2px 30px rgba(0,0,0,.45);
}
.hero-sub{
  font-size:15.5px; font-weight:400;
  letter-spacing:.06em;
  color:rgba(255,255,255,.90);
  margin-top:9px;
}
.hero-date{
  font-size:13px; font-weight:400;
  color:rgba(255,255,255,.72);
  margin-top:13px;
}
.hero-tagline{
  font-family:var(--font-disp);
  font-style:italic;
  font-size:13px;
  line-height:1.6;
  color:rgba(255,255,255,.60);
  margin-top:13px;
}

/* ============================================================
   NEXT-STOP LAYERED DECK
   (layout + animation rules live in deck.css)
   ============================================================ */

/* ============================================================
   SECTION SHELL
   ============================================================ */
.sec{ padding:26px var(--pad) 0; }
/*
 * 「合作方式」（.sec-quick，padding-bottom 为 0）与「服务流程」之间的净间距
 * 就等于这里的 padding-top。原先只有 6px，实机量测下来是同页最挤的一条接缝
 * （作品→合作方式 22px、服务流程→服务区域 16px），卡片底边几乎贴住标题。
 * 这一块是全页信息密度最高的（时间轴 + 档期卡），留白本就该比普通节奏更大，
 * 所以给到 32px，而不是简单对齐 22px。
 * 桌面端 ≥1024px 由 desktop.css 的 .sec.sec-stops 接管（96px），不受此值影响。
 */
.sec-stops{ padding-top:32px; }

.sec-head{
  display:flex; align-items:flex-end; justify-content:space-between;
  gap:12px;
  margin-bottom:14px;
}
.sec-title{
  font-family:var(--font-disp);
  font-size:26px; font-weight:500;
  letter-spacing:-.01em;
  line-height:1.06;
  display:flex; align-items:baseline; gap:7px;
}
.sec-title .dot{ color:var(--ink-4); font-size:19px; }
.sec-title em{ font-style:normal; font-family:var(--font-disp); }
.ttl-sep{
  font-family:var(--font-ui);
  font-size:12px; font-weight:400;
  color:var(--ink-3);
  letter-spacing:.02em;
}

.pill-btn{
  display:inline-flex; align-items:center; gap:7px;
  padding:8px 15px;
  border-radius:999px;
  font-size:10.5px; font-weight:600;
  letter-spacing:.14em;
  color:rgba(255,255,255,.86);
  border:1px solid var(--stroke-2);
  background:rgba(255,255,255,.045);
  backdrop-filter:blur(12px);
  transition:all .35s var(--ease);
}
.pill-btn:active{ transform:scale(.95); background:rgba(255,255,255,.11); }
.pill-btn .arw{ font-size:11px; color:var(--gold); }

.link-btn{
  font-size:12px; font-weight:400;
  color:var(--ink-3);
  display:inline-flex; align-items:center; gap:3px;
}
.link-btn .arw{ font-size:14px; }

/* ============================================================
   STOPS CARD  —  timeline
   ============================================================ */
.stops-card{
  position:relative;
  border-radius:var(--radius-l);
  background:linear-gradient(180deg, rgba(255,255,255,.075), rgba(255,255,255,.032));
  border:1px solid var(--stroke);
  padding:16px 8px 18px;
  backdrop-filter:blur(18px);
  overflow:hidden;
}
.stops-card::after{
  content:"";
  position:absolute; top:0; right:0; bottom:0; left:0; inset:0;
  border-radius:inherit;
  background:radial-gradient(90% 60% at 50% 0%, rgba(201,164,106,.07), transparent 70%);
  pointer-events:none;
}
.stops-meta{
  display:flex; align-items:center; justify-content:flex-end; gap:7px;
  padding:0 12px 0;
  font-size:9.5px; font-weight:600;
  letter-spacing:.14em;
  color:var(--ink-3);
  margin-bottom:14px;
}
.stops-meta .sep{ color:var(--ink-4); }

.rail{
  position:relative;
  display:flex;
  align-items:flex-start;
  justify-content:space-between;
  padding:0 6px;
}
.rail-line{
  position:absolute;
  left:calc(6px + 12.5%);
  right:calc(6px + 12.5%);
  /* must pass exactly through the dot centres, so derive it from the
     node's own box instead of hard-coding a magic offset */
  top:calc(var(--node-cap-h) + var(--node-dot-mt) + var(--node-dot) / 2);
  height:1px;
  background:linear-gradient(90deg, rgba(255,255,255,.06), rgba(255,255,255,.26) 12%, rgba(255,255,255,.26) 88%, rgba(255,255,255,.06));
}
.rail-line::after{
  content:"";
  position:absolute; top:0; right:0; bottom:0; left:0; inset:0;
  background:linear-gradient(90deg, transparent, rgba(216,169,79,.55) 40%, transparent);
  opacity:0;
  transition:opacity .8s var(--ease);
}
.scroll.is-ready .rail-line::after{ opacity:1; }

.rail-node{
  position:relative;
  flex:1 1 0;
  min-width:0;
  display:flex; flex-direction:column; align-items:center;
  opacity:0;
  transform:translateY(14px);
  animation:nodeIn .85s var(--ease) forwards;
  animation-delay:calc(.34s + var(--i) * .075s);
}
@keyframes nodeIn{ to{ opacity:1; transform:none; } }

.node-cap{
  height:var(--node-cap-h);
  font-size:7.6px; font-weight:400;
  letter-spacing:.01em;
  color:var(--ink-4);
  white-space:nowrap;
  text-align:center;
  line-height:1.2;
}
.node-dot{
  width:var(--node-dot); height:var(--node-dot);
  border-radius:50%;
  margin:var(--node-dot-mt) 0 0;
  background:transparent;
  border:1.6px solid rgba(255,255,255,.58);
  transition:all .5s var(--ease);
  position:relative;
}
.rail-node.is-active .node-dot{
  background:var(--terra);
  border-color:var(--terra);
  box-shadow:0 0 0 4px rgba(196,112,63,.16), 0 0 14px rgba(196,112,63,.55);
}
.node-city{
  margin-top:11px;
  font-family:var(--font-disp);
  font-size:13.5px; font-weight:500;
  color:rgba(255,255,255,.90);
  white-space:nowrap;
  letter-spacing:.01em;
}
.node-cn{
  font-size:9.5px; font-weight:400;
  line-height:1.35;
  color:var(--ink-3);
  margin-top:3px;
  /* ⚠ 这里不能 nowrap。六格均分后每格只有约 42px 宽，
     而 9.5px 的中文 7 个字要 67px（实测「平面规划与调研」），
     nowrap 会直接压到右边那一格的字上（实测重叠 7px）。
     允许折行是兜底：默认文案已精简成 4 字（单行放得下），
     后台想改长也只会折成两行，不会跟邻格打架。 */
  white-space:normal;
  overflow-wrap:break-word;
}
.rail-node.is-active .node-city{ color:#fff; }
.rail-node.is-active .node-cn{ color:rgba(255,255,255,.58); }

/* 极窄屏収一号字：320px 上六格均分后每格只剩约 41px，
   13.5px 的英文名（Planning / Delivery 最长，约 48px）会顶到邻格。 */
@media (max-width:360px){
  .node-cap{ font-size:7px; }
  .node-city{ font-size:11px; }
  .node-cn{ font-size:9px; }
}

/* ============================================================
   NEXT BOOKING
   ============================================================ */
.next-card{
  margin-top:12px;
  display:flex; align-items:center; gap:12px;
  padding:14px 15px;
  border-radius:var(--radius-m);
  background:linear-gradient(180deg, rgba(255,255,255,.062), rgba(255,255,255,.028));
  border:1px solid var(--stroke);
  backdrop-filter:blur(16px);
  transition:transform .5s var(--ease), background .4s var(--ease);
}
.next-card:active{ transform:scale(.985); background:rgba(255,255,255,.075); }
.next-mark{
  flex:0 0 auto;
  width:8px; height:8px; border-radius:50%;
  background:var(--jade);
  box-shadow:0 0 0 3px rgba(111,191,159,.16), 0 0 12px rgba(111,191,159,.55);
}
.next-main{ flex:1 1 auto; min-width:0; }
.next-cap{
  font-size:9.5px; font-weight:400;
  color:var(--ink-3);
  letter-spacing:.01em;
}
.next-title{
  font-size:13.5px; font-weight:600;
  margin-top:5px;
  color:#fff;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.next-sub{
  font-size:10px; font-weight:400;
  color:var(--ink-3);
  margin-top:4px;
}
/* 右侧这颗原来是静态 <div>「正在接受预约」，现在渲染成 <button>：
   点一下把手机号复制走（去微信里搜这个号加好友）。
   button 的默认样式已由最上面的 reset 抹平，这里只补布局与反馈。 */
.next-count{
  flex:0 0 auto;
  display:inline-flex; align-items:center; gap:5px;
  font-size:10px; font-weight:500;
  padding:7px 12px;
  border-radius:999px;
  color:var(--jade);
  background:var(--jade-soft);
  border:1px solid rgba(111,191,159,.28);
  white-space:nowrap;
  transition:background .35s var(--ease), border-color .35s var(--ease);
}
.next-count svg{ width:11px; height:11px; flex:0 0 auto; }
.next-count.is-copied{
  background:rgba(111,191,159,.3);
  border-color:rgba(111,191,159,.55);
}

/* ============================================================
   ACTIVE CITY CARD STACK
   ============================================================ */
.sec-city{ padding-top:16px; }
.city-stack{
  position:relative;
  display:flex;
  gap:10px;
  padding-right:var(--pad);
  overflow-x:auto;
  overflow-y:hidden;
  scrollbar-width:none;
  scroll-snap-type:x proximity;
  -webkit-overflow-scrolling:touch;
  scroll-padding-left:0;
  transition:height .5s var(--ease);
  padding-bottom:2px;
}
.city-stack::-webkit-scrollbar{ display:none; }

.city-card{
  position:relative;
  flex:0 0 auto;
  width:calc(100% - 34px);
  height:214px;
  border-radius:var(--radius-l);
  overflow:hidden;
  scroll-snap-align:start;
  border:1px solid rgba(255,255,255,.10);
  transform:scale(.955);
  opacity:.62;

  /* 底色是「图还没解码」时的兜底 —— 没有它，滑动中一下子空出来的
     卡位会透出页面底色（白闪）。 */
  background:#14161C;

  /* ⚠ 这里原来还有 filter:saturate(.82) brightness(.78) 与 will-change:transform。
     滑动时这两条会打架：will-change 把每张卡提成独立合成层，而 filter 一变，
     浏览器就得把整层丢掉重新栅格化。10 张卡轮着变，就是用户看到的「滑一下闪很久」。
     现在压暗改由下面 ::after 那层遮罩的不透明度负责 —— 只动 opacity，
     走的是合成器，不再触发重绘。transform 取消、filter 取消。 */
  transition:
    transform .62s var(--ease),
    opacity .62s var(--ease),
    box-shadow .62s var(--ease);
}
.city-card:first-child{ margin-left:0; }
.city-card:last-child{ margin-right:14px; }

/* 非选中态的压暗层：替代原来的 filter。纯色 + opacity，代价极低。
   pointer-events:none 才不会挡住右下角那颗「查看该区域案例」的链接。 */
.city-card::after{
  content:'';
  position:absolute; top:0; right:0; bottom:0; left:0; inset:0;
  background:rgba(10,11,15,.44);
  opacity:1;
  pointer-events:none;
  transition:opacity .62s var(--ease);
}
.city-card.is-active::after{ opacity:0; }

.city-card.is-active{
  transform:scale(1);
  opacity:1;
  box-shadow:0 26px 60px -22px rgba(0,0,0,.85), 0 0 0 1px rgba(255,255,255,.06);
}
.cc-media{ position:absolute; top:0; right:0; bottom:0; left:0; inset:0; }
.cc-media img{ transform:scale(1.06); transition:transform 1.2s var(--ease); }
.city-card.is-active .cc-media img{ transform:scale(1); }
.cc-shade{
  position:absolute; top:0; right:0; bottom:0; left:0; inset:0;
  background:linear-gradient(180deg, rgba(0,0,0,.52) 0%, rgba(0,0,0,.16) 34%, rgba(0,0,0,.42) 66%, rgba(0,0,0,.80) 100%);
}
.cc-body{
  position:absolute; top:0; right:0; bottom:0; left:0; inset:0;
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  text-align:center;
  padding:18px;
}
.cc-label{
  font-size:9.5px; font-weight:400;
  letter-spacing:.05em;
  color:rgba(255,255,255,.72);
  margin-bottom:6px;
}
.cc-name{
  font-family:var(--font-disp);
  font-size:44px; font-weight:500;
  line-height:1;
  letter-spacing:-.02em;
  text-shadow:0 3px 26px rgba(0,0,0,.5);
}
.cc-cn{
  font-size:13px; font-weight:400;
  color:rgba(255,255,255,.82);
  margin-top:9px;
}
.cc-range{
  font-size:11px; font-weight:400;
  color:rgba(255,255,255,.60);
  margin-top:9px;
}
/* 这条现在是 <a>（跳到作品页），不是原来的静态 <div> */
.cc-cta{
  margin-top:17px;
  display:inline-flex; align-items:center; gap:5px;
  font-size:11px; font-weight:500;
  padding:8px 17px;
  border-radius:999px;
  /* backdrop-filter 拿掉了：它每帧都要重新采样身后那层背景，而父级卡片
     过去正好在逐帧动 filter，两者叠在一起是滑动闪屏的第二个来源。
     改成稍实的半透明白，观感几乎没差。 */
  background:rgba(255,255,255,.18);
  border:1px solid rgba(255,255,255,.26);
  color:rgba(255,255,255,.94);
  text-decoration:none;
  transition:background .35s var(--ease);
}
.cc-cta:active{ background:rgba(255,255,255,.30); }
.cc-cta .arw{ font-size:14px; line-height:1; }

/* progress dots */
.city-dots{
  display:flex; align-items:center; justify-content:center; gap:6px;
  margin-top:12px;
}
.city-dots i{
  width:5px; height:5px; border-radius:50%;
  background:rgba(255,255,255,.22);
  transition:all .5s var(--ease);
}
.city-dots i.on{ width:18px; border-radius:3px; background:var(--gold); }

/* ============================================================
   QUICK ACTIONS
   ============================================================ */
.sec-quick{ padding-top:22px; }
.quick-grid{
  display:grid;
  grid-template-columns:repeat(3,1fr);
  gap:10px;
}
.quick-card{
  display:flex; flex-direction:column; align-items:flex-start;
  gap:10px;
  padding:14px 13px 13px;
  border-radius:var(--radius-m);
  background:linear-gradient(180deg, rgba(255,255,255,.072), rgba(255,255,255,.030));
  border:1px solid var(--stroke);
  backdrop-filter:blur(16px);
  text-align:left;
  transition:transform .45s var(--ease), background .35s var(--ease), border-color .35s var(--ease);
}
.quick-card:active{
  transform:scale(.965);
  background:rgba(255,255,255,.10);
  border-color:var(--stroke-2);
}
.q-ico{
  width:36px; height:36px;
  border-radius:50%;
  display:grid; place-items:center;
  background:linear-gradient(150deg, rgba(232,196,132,.34), rgba(196,140,72,.18));
  border:1px solid rgba(226,190,124,.26);
  color:#EFCF97;
}
.q-ico svg{ width:18px; height:18px; }

.q-text{
  display:flex; flex-direction:column; gap:3px;
  width:100%;
}
.q-label{
  font-size:12.5px; font-weight:500;
  color:rgba(255,255,255,.94);
  letter-spacing:.01em;
  white-space:nowrap;
}
.q-cn{
  font-size:10px; font-weight:400;
  color:var(--ink-3);
}
.q-arw{
  position:absolute;
  right:11px; bottom:14px;
  font-size:15px;
  color:var(--ink-3);
  line-height:1;
}
.quick-card{ position:relative; }

/* ============================================================
   CITY PANELS (vertical snap)
   ============================================================ */
#cityPanels{ position:relative; }

.panel{
  padding:28px var(--pad) 0;
  scroll-snap-align:start;
  scroll-margin-top:52px;
  scroll-margin-top:calc(52px + env(safe-area-inset-top,0px));
}

/* the city header sticks so there is never a blank top band */
.panel-head{
  position:sticky;
  top:0;
  z-index:5;
  display:flex; align-items:flex-start; justify-content:space-between;
  gap:14px;
  margin:0 calc(var(--pad) * -1) 8px;
  padding:10px var(--pad) 12px;
  background:linear-gradient(180deg, var(--bg) 62%, rgba(7,7,10,.88) 82%, transparent 100%);
  backdrop-filter:blur(14px);
  -webkit-backdrop-filter:blur(14px);
}
.ph-name{
  font-family:var(--font-disp);
  font-size:36px; font-weight:500;
  line-height:1;
  letter-spacing:-.02em;
}
.ph-cn{
  font-size:13px; font-weight:400;
  color:var(--ink-2);
  margin-top:7px;
}
.ph-right{
  flex:0 0 auto;
  display:flex; flex-direction:column; align-items:flex-end;
  gap:8px;
  padding-top:2px;
}
.ph-range{
  font-size:10.5px; font-weight:400;
  color:var(--ink-3);
  letter-spacing:.01em;
}
.ph-weather{
  display:flex; align-items:center; gap:6px;
  padding:8px 11px;
  border-radius:14px;
  background:rgba(255,255,255,.055);
  border:1px solid var(--stroke);
  backdrop-filter:blur(14px);
  white-space:nowrap;
}
.w-ico{ color:var(--ink-3); line-height:0; }
.w-ico svg{ width:14px; height:14px; }
.w-temp{
  font-family:var(--font-disp);
  font-size:20px; font-weight:500;
  line-height:1;
}
.w-sub{ font-size:8.5px; color:var(--ink-3); }

.panel-day{
  font-size:11px; font-weight:400;
  color:var(--ink-3);
  letter-spacing:.04em;
  margin:12px 0 12px;
}

.day-block{ margin-bottom:20px; }
.day-label{
  font-size:10.5px; font-weight:400;
  color:var(--ink-3);
  margin-bottom:10px;
}

.site-media{
  position:relative;
  height:172px;
  border-radius:var(--radius-m);
  overflow:hidden;
  border:1px solid rgba(255,255,255,.09);
}
.site-media img{ transition:transform 1.4s var(--ease); }
.site-media:active img{ transform:scale(1.04); }
.site-tag{
  position:absolute; right:0; bottom:0;
  font-size:10px; font-weight:400;
  padding:6px 11px;
  color:rgba(255,255,255,.92);
  background:rgba(0,0,0,.52);
  border-top-left-radius:12px;
  backdrop-filter:blur(10px);
}

.site-row{
  display:flex; align-items:center; gap:9px;
  margin-top:13px;
}
.site-title{
  font-family:var(--font-serif-cn);
  font-size:15px; font-weight:600;
  letter-spacing:.01em;
}
.chip{
  font-size:9.5px; font-weight:500;
  padding:3px 8px;
  border-radius:6px;
  letter-spacing:.02em;
}
.chip-booked{
  color:rgba(255,255,255,.78);
  background:rgba(255,255,255,.10);
  border:1px solid rgba(255,255,255,.14);
}
.chip-pending{
  color:rgba(255,255,255,.62);
  background:rgba(255,255,255,.07);
  border:1px solid rgba(255,255,255,.12);
}
.site-note{
  font-size:11.5px; font-weight:400;
  color:var(--ink-3);
  margin-top:7px;
}

/* NEARBY strip */
.strip{
  display:flex; align-items:center; justify-content:space-between;
  gap:12px;
  margin-top:16px;
  padding:13px 15px;
  border-radius:var(--radius-s);
  background:rgba(255,255,255,.045);
  border:1px solid var(--stroke);
  backdrop-filter:blur(14px);
}
.strip-left{ display:flex; flex-direction:column; gap:4px; }
.strip-kicker{
  font-size:8.5px; font-weight:600;
  letter-spacing:.22em;
  color:var(--ink-4);
}
.strip-title{
  font-size:12.5px; font-weight:500;
  color:rgba(255,255,255,.88);
}
.strip-mini{
  width:32px; height:32px;
  flex:0 0 auto;
  border-radius:10px;
  display:grid; place-items:center;
  background:rgba(255,255,255,.07);
  border:1px solid var(--stroke-2);
  color:var(--gold);
  transition:all .4s var(--ease);
}
.strip-mini:active{ transform:scale(.9); background:rgba(255,255,255,.14); }
.sm-ico svg{ width:17px; height:17px; }

.nearby-row{
  display:grid; grid-template-columns:repeat(3,1fr);
  gap:9px;
  margin-top:10px;
}
.nb{
  display:flex; align-items:center; justify-content:center; gap:6px;
  padding:14px 6px;
  border-radius:var(--radius-s);
  background:rgba(255,255,255,.05);
  border:1px solid var(--stroke);
  font-size:11.5px; font-weight:400;
  color:rgba(255,255,255,.86);
  backdrop-filter:blur(12px);
}
.nb-dot{
  width:4px; height:4px; border-radius:50%;
  background:brown;
  background:var(--gold);
  opacity:.85;
  flex:0 0 auto;
}
.nb-wc .nb-dot{ background:var(--amber); }

.pending-cap{
  font-size:10.5px; font-weight:400;
  color:var(--ink-4);
  margin:4px 0 12px;
}

/* ============================================================
   TICKETS
   ============================================================ */
.sec-tickets{ padding-top:34px; }
.tk-row{
  display:flex; gap:10px;
  overflow-x:auto;
  scrollbar-width:none;
  margin:0 calc(var(--pad) * -1);
  padding:0 var(--pad) 4px;
  -webkit-overflow-scrolling:touch;
  scroll-snap-type:x proximity;
}
.tk-row::-webkit-scrollbar{ display:none; }

.tk-card{
  position:relative;
  flex:0 0 auto;
  width:148px;
  border-radius:var(--radius-m);
  overflow:hidden;
  scroll-snap-align:start;
  background:linear-gradient(180deg, rgba(255,255,255,.062), rgba(255,255,255,.028));
  border:1px solid var(--stroke);
  backdrop-filter:blur(16px);
  transition:transform .45s var(--ease);
}
.tk-card:active{ transform:scale(.97); }
.tk-media{
  position:relative;
  height:88px;
  overflow:hidden;
}
.tk-brand{
  position:absolute; left:8px; bottom:8px;
  font-size:9px; font-weight:700;
  letter-spacing:.09em;
  padding:4px 8px;
  border-radius:6px;
  color:#fff;
  background:rgba(12,12,14,.66);
  border:1px solid rgba(255,255,255,.16);
  backdrop-filter:blur(10px);
}
.tk-body{ padding:11px 12px 13px; }
.tk-title{
  font-size:11.5px; font-weight:600;
  line-height:1.34;
  color:rgba(255,255,255,.94);
  padding-right:16px;
  /* 卡宽 148px，「主材清单与预算控制表」这类长题会折两行，
     一行的题不预留高度会把 meta 顶得参差 —— 统一按两行占位（v1.9.8）。 */
  min-height:calc(1.34em * 2);
}
.tk-meta{
  font-size:9.5px; font-weight:400;
  color:var(--ink-3);
  margin-top:8px;
}
.tk-qr{
  position:absolute; right:11px; bottom:13px;
  color:rgba(255,255,255,.44);
  line-height:0;
}
.tk-qr svg{ width:15px; height:15px; }

/* ============================================================
   TAB BAR  —  floating glass
   ============================================================ */
.tabbar{
  position:absolute;
  left:14px; right:14px;
  bottom:12px;
  bottom:calc(12px + env(safe-area-inset-bottom,0px));
  z-index:60;
  height:var(--tabbar-h);
  display:grid;
  grid-template-columns:repeat(5,1fr);
  align-items:center;
  border-radius:20px;
  background:rgba(20,20,24,.62);
  border:1px solid rgba(255,255,255,.09);
  backdrop-filter:blur(30px) saturate(1.5);
  -webkit-backdrop-filter:blur(30px) saturate(1.5);
  box-shadow:
    0 18px 44px -14px rgba(0,0,0,.82),
    inset 0 1px 0 rgba(255,255,255,.09);
  /* 隐藏要跟手：滑动一开始就得让开，所以退出比入场快。 */
  transition:transform .32s var(--ease), opacity .24s var(--ease);
}
/* 滚动时收起、停住再浮回来（由 main.js 打时间戳决定，
   不是「滚过某个高度就永久消失」）。 */
.tabbar.is-hidden{
  transform:translateY(calc(100% + 24px));
  opacity:0;
  /* 收起后不该还能接住点击 —— 过渡途中它有一小段时间还在原位。 */
  pointer-events:none;
}

.tab{
  position:relative;
  height:100%;
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:4px;
  /* 未激活态原来只有 .44，在深色玻璃上偏「糊」；提到 .52 后
     非激活项也能一眼读清，整条导航干净不少。 */
  color:rgba(255,255,255,.52);
  transition:color .4s var(--ease);
}
.tab-ico{ line-height:0; }
.tab-ico svg{ width:20px; height:20px; transition:transform .45s var(--ease); }
.tab-label{
  font-size:9.5px; font-weight:400;
  letter-spacing:.03em;
  /* 「拨打电话」是四字，窄屏（320px）一列只有 ~58px，
     加一道 nowrap 保证它宁可紧一点也不折行。 */
  white-space:nowrap;
}
.tab.is-active{ color:#fff; }
.tab.is-active .tab-label{ font-weight:600; }
.tab.is-active .tab-ico{ color:#E8BF7E; }
.tab.is-active .tab-ico svg{
  transform:translateY(-1px);
  filter:drop-shadow(0 0 8px rgba(232,191,126,.5));
}
.tab:active .tab-ico svg{ transform:scale(.88); }
/* 动作项（拨打 / 复制）渲染成 <button>（不是链接）。
   ⚠ 这里不要再给 button 写 width / padding —— 移动端是 grid（自动 stretch
   到列宽），桌面端是 flex（宽度要由内容决定）；一旦写成 width:100%，
   桌面端会把这两个格子撑满整条导航，把品牌块挤到换行。 */
.tab.is-copied{ color:#fff; }
.tab.is-copied .tab-ico{ color:#E8BF7E; }

/* ---- 中心主按钮：拨打电话 ------------------------------------
   做法是「背景条做薄，图标探出去」：
   导航条本身只有 56px 高（比常规 64~72 的胶囊薄一截），
   金色圆钮 46px 用绝对定位挂在条顶，向上探出 10px。
   于是「条」看起来是薄而轻的，凸起的只是这一颗图标 ——
   而不是整条导航被撑高。
   （之前一直把圆钮往条里塞、越塞越小，方向是反的：条不薄，
   怎么压圆钮都还是显得笨。）
   几何是算过的：条高 56（含 1px 边框，内容区 54），圆钮 top:-16 高 44
   → 底边落在条内 28px 处；标签 bottom:8 高 12 → 顶边在 34px，
   两者中间让出 6px 呼吸。
   ⚠ 早前是「46px + top:-10」——底边 36px、标签顶边也在 36px，
     实测两者直接压在一起（重叠 2px），所以看着「里得太近」。
     这一版把圆钮收到 44px、top 保持 -16px：探出量与视觉总高
     都没变（15px / 71px），只是从圆钮那头让出了 6px 距离。
     标签位置一个字没动 —— 它要跟其余四格的标签对齐（都在 810px）。

   位置全部走绝对定位，就是为了让圆钮与标签能各自定位；
   若沿用 flex 居中，圆钮探出多少就会把标签一起顶出条外。

   颜色：图标改白 (#fff) 之后，金面不能是浅奶油金 ——
   白色压在 #FBF1DA 那种亮底上对比度不到 1.5:1，边缘会整个糊掉。
   但整体压深又会「发闷显旧」（用户反馈过两轮），所以亮底保留，
   靠两件轻手段把白线「扶」起来，而不是靠加深底色：
     · 球心一层极淡暖棕晕（凹槽感，只压图标落脚的那片）
     · svg 自带一圈紧贴线条的暖棕柔影（1.2px 模糊）——
       这是亮底放白图标的通行做法，效果等同描边但不显脏。
   实测球整体亮度 L≈0.36~0.48（暗版只有 0.26），球是亮的；
   而白线周边因柔影产生了稳定的局部暗环，线形清楚。
   边界不打黑边：1px 淡白环 + 柔和暗影 + 金色外发光。 */
.tab--call{ position:relative; }
.tab--call .tab-ico{
  position:absolute;
  left:50%;
  top:-16px;
  transform:translateX(-50%);
  width:44px; height:44px;
  border-radius:50%;
  display:grid; place-items:center;
  color:#fff;
  background:
    radial-gradient(62% 62% at 50% 50%, rgba(122,84,24,.20) 0%, rgba(122,84,24,.11) 56%, rgba(122,84,24,0) 100%),
    radial-gradient(125% 125% at 32% 20%, #FBF0D6 0%, #E4C894 24%, #C69E5A 54%, #A87B38 82%, #8A6323 100%);
  box-shadow:
    inset 0 1px 1px rgba(255,255,255,.58),
    inset 0 -2px 5px rgba(84,58,16,.28),
    0 0 0 1px rgba(255,255,255,.22),
    0 5px 12px -5px rgba(0,0,0,.5),
    0 10px 22px -10px rgba(201,164,106,.85);
  animation:gqCallPulse 3.4s var(--ease) infinite;
}
.tab--call .tab-ico svg{
  width:20px; height:20px;
  stroke-width:2.3;
  /* 紧贴白线的暖棕柔影 —— 界定边缘用，不是描边 */
  filter:drop-shadow(0 1.2px 1.4px rgba(112,76,20,.7));
}
.tab--call .tab-label{
  position:absolute;
  left:0; right:0;
  bottom:8px;
  text-align:center;
  color:rgba(255,255,255,.82);
}
.tab--call:active .tab-ico{ animation:none; }
/* 呼吸只扩最外层那一圈金色柔光，不再动深色底影 —— 动底影会闪。
   ⚠ 三层底影的数值必须与上面静态态逐字一致，否则动画首帧会跳一下。 */
@keyframes gqCallPulse{
  0%   { box-shadow:inset 0 1px 1px rgba(255,255,255,.58), inset 0 -2px 5px rgba(84,58,16,.28), 0 0 0 1px rgba(255,255,255,.22), 0 5px 12px -5px rgba(0,0,0,.5), 0 10px 22px -10px rgba(201,164,106,.85), 0 0 0 0 rgba(201,164,106,.32); }
  62%  { box-shadow:inset 0 1px 1px rgba(255,255,255,.58), inset 0 -2px 5px rgba(84,58,16,.28), 0 0 0 1px rgba(255,255,255,.22), 0 5px 12px -5px rgba(0,0,0,.5), 0 10px 22px -10px rgba(201,164,106,.85), 0 0 0 7px rgba(201,164,106,0); }
  100% { box-shadow:inset 0 1px 1px rgba(255,255,255,.58), inset 0 -2px 5px rgba(84,58,16,.28), 0 0 0 1px rgba(255,255,255,.22), 0 5px 12px -5px rgba(0,0,0,.5), 0 10px 22px -10px rgba(201,164,106,.85), 0 0 0 0 rgba(201,164,106,0); }
}

/* 复制反馈：底部导航上方浮一条提示 */
.gq-toast{
  position:absolute;
  left:50%;
  bottom:calc(var(--tabbar-h) + 26px);
  bottom:calc(var(--tabbar-h) + 26px + env(safe-area-inset-bottom,0px));
  transform:translate(-50%, 10px);
  z-index:70;
  padding:10px 18px;
  border-radius:999px;
  font-size:12px;
  white-space:nowrap;
  color:#F5EFE4;
  background:rgba(24,22,20,.92);
  border:1px solid rgba(255,255,255,.14);
  box-shadow:0 14px 34px -16px rgba(0,0,0,.85);
  opacity:0;
  pointer-events:none;
  transition:opacity .34s var(--ease), transform .34s var(--ease);
}
.gq-toast.on{
  opacity:1;
  transform:translate(-50%, 0);
}

/* ============================================================
   返回顶部
   ------------------------------------------------------------
   出现条件：滚动超过一屏（逻辑在 main.js，挂在**已有的** #scroll
   监听器上，没有新增监听器）。

   刻意做得低调 —— 底部导航才是这一屏的重心：
     · 38px，比导航条（56px）小一圈，不与之竞争
     · 中性玻璃色，既不用品牌金、也不用档期的状态绿
     · 只有 opacity 与 6px 位移，没有弹跳、没有光晕
   隐藏态必须带 pointer-events:none —— 过渡途中它仍在原位，
   不加就会替底下的元素接住点击。
   ============================================================ */
.gq-totop{
  position:absolute;
  right:14px;
  bottom:calc(var(--tabbar-h) + 26px);                                  /* env() 兜底 */
  bottom:calc(var(--tabbar-h) + 26px + env(safe-area-inset-bottom,0px));
  z-index:55;                       /* 低于 .tabbar 的 60，层级上不跟导航抢 */
  width:38px; height:38px;
  display:flex; align-items:center; justify-content:center;
  border-radius:50%;
  color:rgba(255,255,255,.56);
  background:rgba(255,255,255,.07);
  border:1px solid rgba(255,255,255,.12);
  opacity:0;
  transform:translateY(6px);
  pointer-events:none;
  transition:opacity .3s var(--ease), transform .3s var(--ease),
             background .25s var(--ease), color .25s var(--ease);
}
.gq-totop.is-on{ opacity:1; transform:none; pointer-events:auto; }
.gq-totop:active{ background:rgba(255,255,255,.14); color:rgba(255,255,255,.82); }
.gq-totop svg{ width:15px; height:15px; }
@media (hover:hover){
  .gq-totop:hover{ background:rgba(255,255,255,.11); color:rgba(255,255,255,.76); }
}

/* ============================================================
   REVEAL
   ============================================================ */
.reveal{
  transition:opacity .85s var(--ease), transform .85s var(--ease);
  transition-delay:calc(var(--i,0) * .07s);
}
html.gq-js .reveal{
  opacity:0;
  transform:translateY(20px);
  will-change:opacity, transform;
}
html.gq-js .reveal.in{ opacity:1; transform:none; }

/* tail breathing room */
.tail-space{ height:26px; }

@media (prefers-reduced-motion:reduce){
  *{ animation:none !important; transition-duration:.01ms !important; }
  html.gq-js .reveal{ opacity:1; transform:none; }
}
