/**
 * template-gallery.css — 模板画廊组件的样式(网格卡片 / hover 九宫格 / 全页预览弹窗)。
 *
 * 原来内联在 generate-flow.html 的 <style> 里,只有那一个页面能用。dashboard 和
 * templates 的模板库要复用同一套 UI/UX,就必须让样式可被别的页面引用——它们各自
 * 重写一套正是"三个页面三种手感"的由来。
 *
 * 与内联版逐行一致,只是换了住处。
 *
 * 依赖宿主页面提供这 13 个 CSS 变量:
 *   --font-h --font-b --t1 --t2 --t3 --accent --accent-dark
 *   --border --border-strong --surface --shadow --ease --r-pill
 * generate-flow.html 自己就有。挂到别的页面时若缺,需要在那边补上或映射。
 */

  
/* ── 组件默认变量 ────────────────────────────────────────────────────────────
   下面的规则用到 13 个 CSS 变量。generate-flow 自己在 :root 里全定义了(值与此处
   相同,且它的 <style> 在本文件之后,所以那边照旧由页面说了算)。
   但 dashboard / templates 只定义了 --r-pill 和 --surface,其余全缺——缺了变量
   var() 会整条声明作废,组件搬过去就会散架。所以在这里兜一份默认值。
   宿主页面若自带定义,只要样式表在本文件之后加载就仍然覆盖得掉。 */
:root {
  --font-h: 'Sora','PingFang SC','Hiragino Sans GB','Microsoft YaHei','Noto Sans SC',Arial,Helvetica,sans-serif;
  --font-b: 'Manrope','PingFang SC','Hiragino Sans GB','Microsoft YaHei','Noto Sans SC',Arial,Helvetica,sans-serif;
  --t1: #111111;
  --t2: #333333;
  --t3: #adadad;
  --accent: #45d0bd;
  --accent-dark: #108080;
  --border: rgba(0,0,0,0.07);
  --border-strong: rgba(0,0,0,0.14);
  /* --surface 不能在这里兜底。dashboard / templates 自己在 :root 上定义了它
     (深色 #1e293b、浅色 #ffffff),而本文件在 dashboard.css 之后加载、特异性又
     同是 :root——兜底反过来把宿主的值压死,深色模式下页面上每个用 var(--surface)
     的东西都被钉成白色(头像下拉菜单就是这么白的,Rainy 2026-08-04)。
     本文件只有 .tg-search 一处用到它,改成就地兜底,不再动全局。 */
  --shadow: 0 2px 8px rgba(0,0,0,0.06);
  --r-pill: 9999px;
  --ease: cubic-bezier(.22,.61,.36,1);
}

/* ── 挂载宿主 ────────────────────────────────────────────────────────────────
   组件由 template-preview-modal.js 注入,宿主容器可能自带布局(库里的
   #stylePreviewGrid 是个 4 列 grid),不中和就会把整个组件压进一个单元格。
   组件自带完整布局,这里把宿主还原成一个纯容器,并让它撑满可用高度。 */
/* ⚠ 这里是 flex 竖列,不是 block。原来写 block !important 是为了中和宿主
   #stylePreviewGrid 的 4 列 grid —— flex 同样中和了它,而且**下游的 flex:1
   才有意义**。写成 block 时 .tg-mmain 的 flex:1 完全不生效:它按内容撑高,
   把胶片条顶到卡片外面被 overflow:hidden 切掉,用户只看得到大图。 */
.tg-preview-host {
  display: flex !important;
  flex-direction: column;
  flex: 1;
  padding: 0 !important;
  gap: 0 !important;
  height: 100%;
  min-height: 0;
}
.tg-preview-host .tg-mmain { height: 100%; }

/* ── 大预览占满可用区域 ──────────────────────────────────────────────────
   舞台只有 `aspect-ratio:16/9`,**尺寸完全由宽度决定**,而宽度又被
   .style-modal-card 的 `max-width:1080px` 卡死。实测 1512×944 的屏幕上舞台
   只有 986×555:左右各空 217px、下方还剩 151px 没用,屏幕越大空得越多。

   两条限制必须一起解,单改任何一条都不行:
     · 只放宽卡片 → 舞台按宽度长高,超出可视区,底部被 footer 切掉;
     · 只加高度上限 → 卡片不长,舞台还是那么大。

   而且卡片必须**跟着舞台一起收**:若卡片放到最宽、舞台却被高度限住,
   多出来的宽度就成了舞台左右两侧新的白边 —— 白边从卡片外面搬到了里面。
   所以这里把卡片宽度直接**从可用高度反推**:先算这个高度下 16:9 能有多宽,
   再加回四周固定的内边距,卡片内沿于是正好等于舞台宽度,不留缝。

   94px = 卡片边框 2 + .style-modal-body 内边距 44 + .tg-mmain 内边距 48。
   300px = 弹窗外边距 48 + 头部 76 + body 内边距 44 + main 顶部 22,余下
   ~130 让下面的缩略图露一条边 —— 那是「还有更多页」的唯一提示。

   ★ 这里**不能**加 `max(1080px, …)` 那样的宽度下限。加过一版,在 1280×720
     上立刻复现了上面说的病:高度只够 747 宽的舞台,卡片却被下限撑到 1080,
     舞台左右各空 143px —— 白边从卡片外面搬进了里面,和没修一样。矮屏上
     弹窗跟着变窄是**对的**,那是 16:9 在这个高度下的真实尺寸。
   上限 1600 只防超宽屏上弹窗铺满整个显示器;命中上限时舞台取 100%,
   同样不留缝。

   `:has()` 把这一切限定在**托管模板预览的那个弹窗**:dashboard 的三选一
   弹窗、缩略图网格弹窗共用 .style-modal-card,它们保持 1080 不变。 */
.style-modal-card:has(.tg-preview-host) {
  /* 300 → 420:除了卡片边框和内边距,还要给**胶片条(80)和底部按钮条(69)**
     留位置。原来只算了舞台,于是舞台按满屏算宽、卡片跟着变高,胶片条被挤出去。
     (Rainy 2026-08-17:「把这个弹窗尺寸缩小一点点,尽量和图上一样就是直接能看到」。)

     94 → 94 + 2×46:两侧各让出 46px 当**箭头走廊**。箭头原来贴在 left:0/right:0,
     压在幻灯片和缩略图上。走廊必须加进卡片宽度里,否则等于从舞台身上割 —— 舞台
     会跟着缩,那是在用画面换按钮位置。(Rainy 2026-08-18:"箭头都往外一些"。)

     420 → 404:缩略图要大一档(她说"大一丢丢"),而缩略图的高由宽推出来,宽又跟着
     卡片走 —— 所以少留一点高度、让卡片长宽一点,整条胶片条跟着变大。 */
  /* ★ 公式模型修正(2026-08-23):旧式把胶片条记作固定 80px,但"一屏 5 张 +
   16:9"使条高 = 舞台宽*9/80,随宽度成比例长 —— 宽屏下真实条高 ~135px,
   超出预算的部分把缩略格压扁(aspect-ratio 被 stretch 覆盖)或把条挤出
   弹窗。现把比例入模:舞台+条 = 宽*(9/16+9/80) = 宽*27/40,可用高 H 下
   宽 = H*40/27;固定扣减相应 -80(条移出固定项)。vh 同时钳到 956(高度
   封顶 880 的生效点)。(Rainy:"preview 完全截断"。) */
  max-width: min(1560px, calc((min(100vh, 956px) - 324px) * 40 / 27 + 94px + 92px));
  max-height: min(92vh, 880px);
}
/* 弹窗骨架从卡片到舞台必须是一条**连通的 flex 竖列**,否则中间任何一层
   display:block 都会让下面的 flex:1 失效 —— 实测 .style-modal-body 是 block,
   .tg-mmain 的 flex:1 因此不起作用,它按内容撑到 709px 而 body 只有 651px,
   多出来的 58px 连同整条胶片条被卡片的 overflow:hidden 切掉。
   ⚠ 只 scope 到预览弹窗:.style-modal-body / .style-modal-grid 是几个弹窗共用的。 */
.style-modal-card:has(.tg-preview-host) .style-modal-body{
  display:flex;flex-direction:column;min-height:0;overflow:hidden}
.style-modal-card:has(.tg-preview-host) .style-modal-grid{
  display:flex;flex:1;min-height:0;overflow:hidden}
.style-modal-card:has(.tg-preview-host) .tg-preview-host{
  display:flex;flex:1;min-height:0}

.tg-preview-host .tg-mbig .stagewrap {
  width: min(100%, calc((min(100vh, 956px) - 220px) * 40 / 27));
  /* This hosted modal chooses the stage by width. Clear the generic
     `height:100%` rule above; keeping both an explicit width and height makes
     `aspect-ratio` lose and stretches a 16:9 deck into a tall rectangle.
     That distortion made Magnolia's correctly authored asymmetric image
     radius look enormous in preview even though the editor was correct. */
  height: auto;
  flex: 0 0 auto;
  aspect-ratio: 16 / 9;
  margin-inline: auto;
}

/* ═══ 模板选择页(第一屏,照 Pitch 入口:tab 分类 + hover 九宫格 + 全页预览) ═══ */
  #tgal{
    position:fixed;
    inset:0;
    z-index:70;
    background:
      /* 左上角原本有一层青色光晕(radial-gradient at 16% 0%),它会盖在
         模板卡片上方、把左上那几张的白底染色。模板缩略图本身就是这一屏
         要看的东西,背景不该跟它抢。(Rainy 2026-08-03) */
      linear-gradient(rgba(69,208,189,.045) 1px, transparent 1px),
      linear-gradient(90deg, rgba(69,208,189,.045) 1px, transparent 1px),
      #f7fbfc;
    background-size:64px 64px,64px 64px,100% 100%;
    overflow:auto;
    display:none
  }
  #tgal.show{display:block}

  /* ── 弹窗形态 ──────────────────────────────────────────────────────────
     从大纲页右上角换模板时,这一屏是盖在大纲上的一层,不是另一个页面。以前它整屏
     铺开、左上角还是个 Back,于是「大纲 → 画廊 → Back → 大纲」看起来像在两页之间
     打转,分不清自己在哪一层。改成居中卡片 + 右上角 X:盖着的东西用 X 关,退不回
     任何地方,因为本来就没走。
     平时(从入口进来选模板)不带这个类,这一屏和以前逐像素一致。
     (Rainy 2026-08-06:outline 右上角换模板直接开弹窗,右上角加个 X。) */
  #tgal.tg-as-modal{
    background:rgba(12,26,28,.46);
    -webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);
    background-size:auto;
    display:none;overflow:hidden;
    padding:clamp(16px,4vh,56px) clamp(16px,4vw,56px);
  }
  #tgal.tg-as-modal.show{display:flex;align-items:center;justify-content:center}
  #tgal.tg-as-modal .tg-shell{
    width:min(1180px,100%);max-height:100%;
    display:flex;flex-direction:column;min-height:0;
    background:#f7fbfc;border-radius:20px;overflow:hidden;
    box-shadow:0 1px 2px rgba(20,40,38,.06),0 40px 90px -30px rgba(20,40,38,.45);
  }
  #tgal.tg-as-modal .tg-wrap{overflow:auto;min-height:0;flex:1}
  /* 弹窗里 Back 让位给 X —— 两个都留着等于给同一个动作两个说法 */
  #tgal.tg-as-modal #tgBack{display:none}
  .tg-x{
    display:none;margin-left:auto;
    width:38px;height:38px;place-items:center;
    border:1px solid rgba(35,31,32,.10);border-radius:50%;
    background:rgba(255,255,255,.72);color:rgba(35,31,32,.72);
    font:400 24px/1 var(--font-h,system-ui);cursor:pointer;
    box-shadow:0 8px 22px -16px rgba(35,31,32,.26),inset 0 1px 1px rgba(255,255,255,.9);
    transition:background .16s ease,color .16s ease,border-color .16s ease;
  }
  #tgal.tg-as-modal .tg-x{display:grid}
  .tg-x:hover{background:rgba(255,255,255,.95);color:var(--t1);border-color:rgba(35,31,32,.16)}
  .tg-x:focus-visible{outline:2px solid rgba(69,208,189,.65);outline-offset:2px}
  /* Template gallery header mirrors the dashboard brand header: full-width,
     28px logo, airy spacing, and a compact nav control at the far left. */
  .tg-top{position:sticky;top:0;z-index:6;background:rgba(255,255,255,.78);-webkit-backdrop-filter:blur(18px) saturate(1.5);backdrop-filter:blur(18px) saturate(1.5);border-bottom:1px solid rgba(20,40,38,.06);box-shadow:0 1px 0 rgba(255,255,255,.65)}
  .tg-top-inner{width:100%;padding:18px 28px;display:flex;align-items:center;gap:14px}
  .tg-back{width:38px;height:38px;display:grid;place-items:center;border:1px solid rgba(35,31,32,.10);border-radius:50%;background:rgba(255,255,255,.72);color:rgba(35,31,32,.72);box-shadow:0 8px 22px -16px rgba(35,31,32,.26),inset 0 1px 1px rgba(255,255,255,.9);cursor:pointer;transition:background .16s var(--ease),transform .16s var(--ease),color .16s var(--ease),border-color .16s var(--ease)}
  .tg-back:hover{background:rgba(255,255,255,.95);color:var(--t1);border-color:rgba(35,31,32,.16);transform:translateX(-1px)}
  .tg-back svg{width:18px;height:18px;display:block}
  .stepbar .tg-back,
  .tg-top .tg-back{width:auto;min-width:0;height:38px;display:inline-flex;align-items:center;justify-content:center;gap:8px;padding:0 16px;border-radius:999px;font-family:var(--font-h);font-size:13px;font-weight:700;color:rgba(35,31,32,.72)}
  .stepbar .tg-back svg,
  .tg-top .tg-back svg{width:16px;height:16px}
  .stepbar .tg-back span,
  .tg-top .tg-back span{line-height:1}
  .tg-top .sb-logo-img{height:28px;width:auto;display:block}
  .tg-title-group{display:flex;align-items:center;gap:14px;min-width:0}
  .tg-title-group::before{content:"";width:1px;height:24px;background:rgba(35,31,32,.12)}
  .tg-top h2{font-family:var(--font-h);font-size:20px;font-weight:700;letter-spacing:-.015em;white-space:nowrap}
  .tg-wrap{max-width:1080px;margin:0 auto;padding:26px 28px 90px}
  .tg-tabs{display:flex;align-items:center;gap:3px;overflow-x:auto;scrollbar-width:none;border-bottom:1px solid rgba(20,40,38,.07);padding-bottom:9px;margin-bottom:2px}
  .tg-tabs::-webkit-scrollbar{display:none}
  .tg-tab{flex:0 0 auto;font-family:var(--font-h);font-size:13.5px;font-weight:600;color:var(--t3);background:none;border:none;padding:9px 14px;cursor:pointer;border-radius:var(--r-pill);white-space:nowrap;transition:color .15s,background .15s;position:relative}
  .tg-tab:hover{color:var(--t2);background:rgba(20,40,38,.04)}
  .tg-tab.on{color:var(--accent-dark);background:rgba(69,208,189,.12)}
  .tg-search{flex:0 0 auto;margin-left:12px;display:inline-flex;align-items:center;gap:7px;background:var(--surface,#ffffff);border:1px solid var(--border-strong);border-radius:var(--r-pill);padding:8px 14px;box-shadow:var(--shadow)}
  .tg-search svg{width:14px;height:14px;color:var(--t3)}
  .tg-search input{border:none;background:none;font-family:var(--font-b);font-size:12.5px;color:var(--t1);width:120px}
  .tg-search input:focus{outline:none}
  .tg-rec{display:flex;align-items:center;flex-wrap:wrap;gap:8px;margin:26px 0 22px;font-family:var(--font-h);font-size:13.5px;font-weight:600;color:var(--t1)}
  /* 前导文字和右侧胶囊要落在同一条中线上。两者字号不同(13.5 / 11.5)、
     胶囊还有 3px padding + 1px 边框,光靠父级的 align-items:center 只能
     让「行盒」居中,而西文的视觉重心偏行盒上方,看着就是没对齐。给两边
     定死同一个行高,视觉中心才真正重合。(Rainy 2026-08-03) */
  .tg-rec-lead{display:inline-flex;align-items:center;gap:7px;color:var(--t1);line-height:22px}
  .tg-rec-lead .spk{width:15px;height:15px;color:var(--accent);flex:0 0 auto}
  .tg-rec-chip{display:inline-flex;align-items:center;font-family:var(--font-b);font-size:11.5px;font-weight:600;color:var(--accent-dark);background:rgba(69,208,189,.1);border:1px solid rgba(69,208,189,.24);padding:0 11px;height:22px;border-radius:var(--r-pill)}
  .tg-rec-quiet{font-family:var(--font-b);font-size:12.5px;font-weight:500;color:var(--t3)}
  .tg-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:34px 28px;margin-top:16px}
  .tg-card{
    position:relative;
    cursor:pointer;
    background:rgba(255,255,255,.70);
    border:1px solid rgba(255,255,255,.8);
    border-radius:18px;
    padding:10px;
    text-align:left;
    animation:cardIn .4s var(--ease) both;
    display:flex;
    flex-direction:column;
    gap:8px;
    overflow:hidden;
    -webkit-backdrop-filter:blur(20px) saturate(160%);
    backdrop-filter:blur(20px) saturate(160%);
    box-shadow:
      inset 0 1px 1px rgba(255,255,255,.95),
      0 1px 2px rgba(35,31,32,.03),
      0 8px 20px -8px rgba(69,208,189,.14),
      0 18px 36px -16px rgba(35,31,32,.07);
    transition:transform .3s cubic-bezier(.2,.7,.2,1),box-shadow .3s cubic-bezier(.2,.7,.2,1);
  }
  .tg-card::before{
    content:"";
    position:absolute;
    inset:0;
    pointer-events:none;
    opacity:.25;
    mix-blend-mode:overlay;
    background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='1.5' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.6 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
    border-radius:inherit;
    z-index:0;
  }
  .tg-card>*{position:relative;z-index:1}
  .tg-card:hover{
    transform:translateY(-4px);
    box-shadow:
      inset 0 1px 1px rgba(255,255,255,1),
      0 1px 2px rgba(35,31,32,.04),
      0 20px 40px -12px rgba(69,208,189,.32),
      0 32px 64px -20px rgba(35,31,32,.12);
  }
  /* 16/9, not 16/10: the covers are true 1920x1080 slides and the img below is
     object-fit:cover — in a 16/10 box that scales to fill the height and crops
     ~5.5% off each side, which chopped the first letter off titles (citron
     "DATA COMPARISON", ember "Digicreate"). Matching the slide ratio fits the
     whole page with no crop. */
  .tg-prev{position:relative;aspect-ratio:16/9;border-radius:14px;overflow:hidden;border:0;box-shadow:none;background:#fbfcfc;transition:none}
  .tg-card:hover .tg-prev{transform:none;box-shadow:none}
  .tg-cover{position:absolute;inset:0;transition:opacity .25s var(--ease);background:#fff}
  .tg-cover img{width:100%;height:100%;object-fit:cover;display:block}
  .tg-cover.is-live{overflow:hidden}
  .tg-cover .style-card{position:absolute;inset:0;width:100%;height:100%;display:block;margin:0;padding:0;border:0;border-radius:inherit;background:transparent;box-shadow:none;overflow:hidden}
  .tg-cover .style-preview-host{position:absolute;left:0;top:0;width:180px;height:101.25px;transform-origin:top left}
  .tg-cover .style-preview{position:absolute;inset:0;width:100%;height:100%;aspect-ratio:auto;border:0;border-radius:inherit;overflow:hidden;container-type:inline-size}
  .tg-cover .style-preview-title{position:absolute;font-family:var(--font-h);font-weight:700;line-height:1.1;letter-spacing:0}
  .tg-cover .style-preview-sub{position:absolute;font-family:var(--font-b);font-weight:400;letter-spacing:0}
  .tg-cover .style-preview-deco{position:absolute;pointer-events:none}
  /* 悬停九宫格:盖在封面之上的前 9 页缩略图网格。默认 opacity:0 且不吃指针事件
     (卡片自身的 click/mouseover 委托靠冒泡,不需要它当命中目标)。 */
  .tg-grid9{position:absolute;inset:0;z-index:2;padding:5%;display:grid;grid-template-columns:repeat(3,1fr);gap:3px;opacity:0;transform:scale(1.05);transition:opacity .28s var(--ease),transform .3s var(--ease);background:#fff;pointer-events:none}
  .tg-grid9 img{width:100%;height:100%;object-fit:cover;border-radius:2px;border:1px solid var(--border);background:#fff;display:block}
  /* ★ 先渲、验过、才亮:.has9 是实时渲染真的落地之后才加上的。原来是无条件
     `:hover` 就翻,而九宫格的内容要么还在 fetch(白框)、要么是预填的
     slide-N.svg 装饰底(没有一个字)。两种都比"封面保持不动"难看。
     和 style-themes.js 里那张库卡片同一套顺序。 */
  .tg-card.has9:hover .tg-cover{opacity:0}
  .tg-card.has9:hover .tg-grid9{opacity:1;transform:scale(1)}
  /* 渲染失败的卡片打上 .no9:九宫格彻底退场,封面留在原地,不留一片白。 */
  .tg-card.no9 .tg-grid9{display:none}
  .tg-badge{position:absolute;top:11px;left:11px;z-index:3;display:inline-flex;align-items:center;gap:4px;font-family:var(--font-h);font-size:10.5px;font-weight:700;letter-spacing:.01em;padding:5px 11px 5px 9px;border-radius:var(--r-pill);background:rgba(255,255,255,.82);-webkit-backdrop-filter:blur(10px) saturate(1.5);backdrop-filter:blur(10px) saturate(1.5);box-shadow:0 3px 12px rgba(20,40,38,.14),inset 0 1px 0 rgba(255,255,255,.75)}
  .tg-badge .spk{width:11px;height:11px;flex:0 0 auto}
  .tg-badge.hi{color:var(--accent-dark)}
  .tg-badge.hi .spk{color:var(--accent)}
  .tg-badge.rel{color:var(--t3);padding:5px 11px}
  .tg-create{display:none}
  .tg-meta{text-align:center;margin-top:0;padding:2px 4px 4px}
  .tg-name{font-family:var(--font-b);font-size:12px;font-weight:500;color:rgba(35,31,32,.38);display:inline-flex;align-items:center;justify-content:center;gap:7px}
  .tg-position{display:flex;align-items:center;justify-content:center;gap:4px;min-height:18px;margin-top:5px;overflow:hidden}
  .tg-position span{display:inline-flex;align-items:center;height:17px;padding:0 7px;border-radius:999px;background:rgba(35,31,32,.045);color:rgba(35,31,32,.48);font-family:var(--font-b);font-size:8.5px;font-weight:600;white-space:nowrap}
  .tg-newtag{display:none}
  .tg-cat{display:none}
  /* 骨架屏:AI 打标返回之前占位,让画廊只渲染一次最终排序(见 startTemplatePick)。
     几何必须和真实 .tg-card 完全一致——同样的 padding/gap、同样的 16/9 预览框、
     同样高度的名称行——否则真卡换进来的瞬间整个网格会抖一下,那比原来的重排还难看。
     所以这里复用 .tg-card 和 .tg-prev 本身,只把交互和内容换掉。 */
  .tg-card.tg-skel{cursor:default;pointer-events:none;animation:none}
  .tg-card.tg-skel:hover{transform:none}
  .tg-skel .tg-prev{background:rgba(35,31,32,.045)}
  .tg-skel-line{display:block;height:12px;width:44%;margin:2px auto 23px;border-radius:6px;background:rgba(35,31,32,.07)}
  /* 微光扫过。两处共用同一条动画,错开 delay 让网格看起来在活动而不是冻住。 */
  .tg-skel .tg-prev,.tg-skel-line{
    background-image:linear-gradient(100deg,transparent 20%,rgba(255,255,255,.85) 50%,transparent 80%);
    background-size:220% 100%;background-repeat:no-repeat;
    animation:tgSkelShimmer 1.4s linear infinite;
  }
  @keyframes tgSkelShimmer{0%{background-position:140% 0}100%{background-position:-40% 0}}
  @media(prefers-reduced-motion:reduce){
    .tg-skel .tg-prev,.tg-skel-line{animation:none;background-image:none}
  }
  /* 全页预览弹窗: mirror the existing template library detail view. */
  .tg-ov{position:fixed;inset:0;background:rgba(35,31,32,0);display:none;place-items:center;padding:24px;z-index:90;transition:background .25s;overflow:auto}
  .tg-ov.in{display:grid;background:rgba(35,31,32,.32);-webkit-backdrop-filter:blur(10px) saturate(140%);backdrop-filter:blur(10px) saturate(140%)}
  .tg-modal{position:relative;width:100%;
    /* 宽度和托管弹窗同一套算法:从可用高度反推 16:9 舞台能有多宽,再加回四周
       固定的内边距和两侧的箭头走廊。原来写死 1120px —— 屏幕再大也不长,而托管
       那个会跟着长,于是同一个预览在 1920×1200 上一个 1560 宽一个 1120 宽,
       舞台差了 40%%。同一个东西在两个入口长得不一样,用户当然觉得不统一。
       (Rainy 2026-08-18:"这个弹窗为什么和前面 dashboard 和模版库的不统一"。)
       减数比托管的 404 大一点:这个弹窗自带头部和底栏(托管那个的头尾在宿主
       卡片上),要多留一档。 */
    max-width:min(1560px,calc((min(100vh, 956px) - 344px) * 40 / 27 + 94px + 92px));
    max-height:min(92vh,880px);display:flex;flex-direction:column;background:rgba(255,255,255,.85);border:1px solid rgba(255,255,255,.95);border-radius:28px;box-shadow:inset 0 1px 1px rgba(255,255,255,.95),inset 0 -1px 1px rgba(69,208,189,.10),0 1px 2px rgba(35,31,32,.04),0 12px 32px -8px rgba(69,208,189,.18),0 24px 56px -20px rgba(35,31,32,.12);overflow:hidden;transform:scale(.98) translateY(10px);opacity:0;transition:transform .3s var(--ease),opacity .3s;margin:auto;-webkit-backdrop-filter:blur(24px) saturate(180%);backdrop-filter:blur(24px) saturate(180%)}
  .tg-ov.in .tg-modal{transform:none;opacity:1}
  .tg-mhead{display:flex;align-items:center;gap:14px;padding:18px 22px;border-bottom:1px solid rgba(35,31,32,.06);flex-shrink:0}
  .tg-mback,.tg-mhead .x{width:36px;height:36px;display:grid;place-items:center;border:0;background:transparent;color:var(--t2);cursor:pointer;border-radius:10px;font-size:22px;line-height:1;transition:background .18s ease,color .18s ease}
  .tg-mback:hover,.tg-mhead .x:hover{background:rgba(35,31,32,.06);color:var(--t1)}
  .tg-mhead .x{margin-left:auto;font-size:24px}
  .tg-mtitle{flex:1;display:flex;align-items:baseline;gap:8px;font-family:var(--font-h);font-size:15px;font-weight:500;color:var(--t2);letter-spacing:-.01em}
  .tg-mhead h3{font-family:var(--font-h);font-size:15px;font-weight:700;color:var(--t1);margin:0}
  .tg-mhead .mtag{font-size:13.5px;color:var(--t3);font-weight:400}
  /* Detail gallery: one centred hero preview followed by a four-column contact
     sheet. Keeping both in one column makes their outer edges identical and
     avoids the old sticky-left / scrolling-right split, which pulled the page
     down to the thumbnail rail whenever an arrow changed slides. */
  /* 大图和缩略图条必须**同时看得见** —— 这一块以前是整体 overflow-y:auto,
     16 张缩略图排成 4 行网格,一屏塞不下,用户看到的是被切掉一半的第一排,
     根本不知道下面还有。(Rainy 2026-08-17:「一定要让用户把大图和下面的小图
     都看到」。)
     现在:自己不滚,舞台吃掉剩余高度,胶片条固定在底部一行。 */
  .tg-mmain{flex:1;min-height:0;overflow:hidden;display:flex;flex-direction:column;gap:14px;padding:20px 70px 22px;align-items:stretch}
  .tg-mleft{position:static;display:flex;flex-direction:column;gap:12px;width:100%;min-width:0}
  .tg-mbig{position:relative;flex:1;min-height:0;display:flex;align-items:center;justify-content:center}
  /* 高度优先:先按可用高度定,再由 16/9 推出宽度。原来只写 aspect-ratio,
     宽度撑满之后高度跟着算出来,矮屏上直接把胶片条顶出可视区。 */
  .tg-mbig .stagewrap{position:relative;border-radius:16px;overflow:hidden;border:1px solid rgba(35,31,32,.08);aspect-ratio:16/9;height:100%;width:auto;max-width:100%;background:#fff;box-shadow:0 10px 28px -12px rgba(35,31,32,.22),0 2px 6px rgba(35,31,32,.05)}
  .tg-mbig .stagewrap img{width:100%;height:100%;object-fit:contain;display:block}
  /* ⚠ 上面那条"高度优先"只在**父级有确定高度**时成立,而独立弹窗(generate-flow
     的 #tgOv,prompt 之后那个)这条链是断的:.tg-mleft 没有 flex:1,于是它按内容
     定高;内容就是 .tg-mbig,而 .tg-mbig 的 flex:1 在一个 auto 高的父级里等于
     没有 —— 舞台实测 2×2,大图整块塌成 2px 高,箭头贴到顶上、"1 / 14"紧跟着,
     用户只看得到下面那排缩略图。(Rainy 2026-08-18:"只能看到 preview 看不到大图"。)

     托管在模板库里的那个弹窗(.tg-preview-host)早就不走高度优先了 —— 它在上面
     用 width:min(100%,…) + height:auto 按**宽度**定尺寸,所以 .tg-mleft 有真实
     内容高度,链子自己接上了。独立弹窗照抄同一招,两个弹窗从此是同一个几何。

     360px = 遮罩内边距 48 + 头部 76 + main 内边距 42 + main 内 gap 14 +
     页码条连同它上面的 gap 26 + 胶片条 80 + 底栏 71,取整留一点余量。 */
  .tg-modal .tg-mbig .stagewrap{
    width:min(100%,calc((min(100vh, 956px) - 240px) * 40 / 27));
    height:auto}
  /* 两组箭头一套样式。原来是两套:一套 36px 半透明带 blur、一套 30px 纯白,
     字号还不一样(18 / 16),而里面是 ‹ › 这两个字符 —— 字符的笔画粗细随字体走、
     基线也不在圆心上,右边那个看着总是偏上。换成 SVG chevron:描边自己说了算,
     两边一模一样,居中靠 grid 不靠字体。 */
  .tg-marr,.tg-farr{
    width:34px;height:34px;border-radius:50%;
    border:1px solid rgba(35,31,32,.06);
    background:rgba(255,255,255,.86);
    -webkit-backdrop-filter:blur(10px) saturate(1.4);backdrop-filter:blur(10px) saturate(1.4);
    color:rgba(35,31,32,.62);cursor:pointer;
    display:grid;place-items:center;padding:0;
    box-shadow:0 6px 16px -8px rgba(35,31,32,.28),0 1px 2px rgba(35,31,32,.05),
               inset 0 1px 1px rgba(255,255,255,.9);
    transition:color .16s var(--ease),background .16s var(--ease),
               box-shadow .16s var(--ease),transform .16s var(--ease);
  }
  .tg-marr svg,.tg-farr svg{width:15px;height:15px;display:block}
  .tg-marr:hover,.tg-farr:hover{
    color:var(--accent-dark);background:#fff;
    box-shadow:0 10px 22px -10px rgba(35,31,32,.34),0 1px 2px rgba(35,31,32,.06),
               inset 0 1px 1px rgba(255,255,255,.95);
  }
  .tg-marr:active,.tg-farr:active{transform:scale(.94)}
  .tg-marr:focus-visible,.tg-farr:focus-visible{
    outline:2px solid rgba(69,208,189,.7);outline-offset:2px}
  @media (prefers-reduced-motion:reduce){
    .tg-marr,.tg-farr{transition:none}
    .tg-marr:active,.tg-farr:active{transform:none}
  }
  .tg-marr{position:absolute;top:50%;z-index:3}
  /* 两组箭头必须同心。大图那对原来贴在 -14px、胶片条那对是 flex 首末项(从 0 起),
     两组差着十几个像素,上下一看就是没对齐。现在两组都从容器边缘 0 起、都是 36px,
     圆心自然都落在离边 18px 的地方。(Rainy 2026-08-18:"上下的这两组切换 btn 要对齐"。) */
  /* -46px 把它推进走廊里,不再压在幻灯片上。圆心落在离内容边 29px 的位置。 */
  .tg-marr.prev{left:-46px}
  .tg-marr.next{right:-46px}
  /* 大图那对靠 translateY(-50%) 垂直居中,而 :active 的 scale 会把 transform
     整个换掉 —— 不写全就会在按下的一瞬间掉到下面去。两个变换必须写在一起。 */
  .tg-marr{transform:translateY(-50%)}
  .tg-marr:active{transform:translateY(-50%) scale(.94)}
  /* 首页只留右箭头、末页只留左箭头。用 visibility 而不是 display:none —— 胶片条
     那对是 flex 项,display:none 会让缩略图区变宽,"一屏正好 5 张"当场破功。 */
  .tg-marr[data-edge="1"],.tg-farr[data-edge="1"]{visibility:hidden}
  .tg-mcap{font-size:11.5px;color:var(--t3);text-align:center;font-weight:600;letter-spacing:.03em}
  /* 胶片条:一行,横向滚。四列网格 × 16 页 = 四行,一屏放不下。
     滚动条藏起来 —— 左右两个箭头和拖动都能动它,一条系统滚动条只会让这一行变矮。 */
  /* 整条伸进走廊(负外边距),两个箭头就落在和大图那对同一条竖线上;gap 12 让
     缩略图轨道的左右边缘正好回到内容宽度 —— 也就是和舞台左右对齐。
     46 = 走廊宽,34 = 按钮直径,12 = 46 - 34。 */
  .tg-mfilm{position:relative;flex:0 0 auto;display:flex;align-items:center;gap:12px;
    margin-inline:-46px}
  .tg-mthumbs{display:flex;flex:1;min-width:0;align-items:flex-start;gap:var(--tg-gap,10px);overflow-x:auto;overflow-y:hidden;
    scrollbar-width:none;padding:3px 2px}
  /* ⚠ 不要在这里写 scroll-behavior:smooth。实测写了之后 JS 设 scrollLeft
     完全不生效(设 300 读回来还是 0,等 700ms 也一样),关掉就正常。
     平滑滚动改在 JS 里用 scrollTo({behavior:'smooth'}) 要。 */
  .tg-mthumbs::-webkit-scrollbar{display:none}
  /* 一屏正好 5 张。原来是定高 74px、宽度随内容,于是一屏露出七八张半 ——
     "半张"没有意义,而且按屏翻页时不知道该翻多少。改成按容器宽度均分:
     宽度定死,高度由 16:9 推出来。(Rainy 2026-08-18:"下面的缩略图有 5 个"。) */
  .tg-mthumbs{--tg-per-view:5;--tg-gap:9px}
  .tg-mthumb{
    flex:0 0 calc((100% - (var(--tg-per-view) - 1) * var(--tg-gap)) / var(--tg-per-view));
    height:auto;aspect-ratio:16/9}
  .tg-farr{flex:0 0 auto}
  .tg-farr[disabled]{cursor:default}
  .tg-mthumb{position:relative;border-radius:10px;overflow:hidden;border:1px solid rgba(35,31,32,.08);cursor:pointer;transition:transform .2s var(--ease),box-shadow .2s var(--ease),border-color .2s var(--ease);aspect-ratio:16/9;background:#fafafa;box-shadow:0 4px 12px -4px rgba(35,31,32,.08)}
  .tg-mthumb img{width:100%;height:100%;object-fit:cover;display:block}
  .tg-mthumb:hover{transform:translateY(-2px);box-shadow:0 8px 20px -4px rgba(35,31,32,.12);border-color:rgba(69,208,189,.35)}
  .tg-mthumb.sel{border-color:transparent;box-shadow:0 0 0 2px var(--accent),0 6px 18px -6px rgba(69,208,189,.35)}
  .tg-mthumb::after{content:attr(data-slide-label);position:absolute;left:10px;bottom:8px;z-index:5;font-size:11px;font-weight:500;color:rgba(255,255,255,.9);background:rgba(0,0,0,.32);padding:3px 8px;border-radius:6px;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);pointer-events:none}
  /* Gradient Bloom reserves the lower-left corner for editorial headlines.
     The modal already exposes the current page count below the large preview,
     so its thumbnail badge must not obscure the authored composition. */
  .tg-mthumb[data-template-id="gradient-bloom"]::after,
  .tg-mthumb.tg-mthumb--unobscured::after{content:none!important;display:none!important}
  /* ── 预览里的 deck 对象 ─────────────────────────────────────────────────────
     大图和缩略图都是 JsonRenderer 现渲 samples/<id>/deck.json,而 renderer 的产物
     依赖 deck-engine.css 里的对象级规则 —— 装载这个弹窗的三个页面(templates /
     dashboard / generate-flow)**都没有引 deck-engine.css**(它有 132 条 html/body/*
     全局规则,引进来会把页面自己的排版重置掉)。

     最直接的后果:少了 `.obj-text-content{white-space:pre-wrap}`,deck 里用 \n 表示
     的多行块被折叠成一行 —— harbor 封面的 "Project\nStatus Report" 连成一行横穿到
     右边的照片底下,E7 六段说明各自连成长条互相压字(2026-08-13 用户报的
     「预览和模板不一样、内页乱了」)。

     这套规则 templates.html 里本来就有一份(那边写着是**测**出来的:渲染五套模板
     的全部页面,逐条问 deck-engine.css 哪条真正命中了 deck 元素,得 16 条,去掉
     `.anim-pending{opacity:0}`(预览没有动画播放器触发,加了整页空白)和
     `cursor:move/text`(预览不可编辑)),但它限定在 `.style-modal-thumb-stage`
     底下 —— 那是**旧的平铺网格**退路的类名。预览换成这个共用弹窗之后,选择器就
     再也没命中过。所以规则搬到弹窗自己的容器上,谁装载都不用记得抄一遍。

     deck-engine.css 那边改了对象级规则时,这里要跟着改。 */
  .tg-mbig .stagewrap *,.tg-mthumb *{box-sizing:border-box;margin:0;padding:0}
  .tg-mbig .stagewrap .deck-object,.tg-mthumb .deck-object{position:absolute;z-index:2}
  /* Image silhouettes belong to deck.json. A preview-only 8px radius here
     made Magnolia (and any asymmetric frame) disagree with the editor. */
  .tg-mbig .stagewrap .obj-image,.tg-mthumb .obj-image{overflow:hidden;position:relative}
  .tg-mbig .stagewrap .obj-image .obj-image-inner,.tg-mthumb .obj-image .obj-image-inner{pointer-events:none}
  .tg-mbig .stagewrap .obj-text-content,.tg-mthumb .obj-text-content{overflow-wrap:break-word;white-space:pre-wrap}
  .tg-mbig .stagewrap .slide,.tg-mthumb .slide{position:relative;width:100%;height:100%;flex-shrink:0;overflow:hidden}
  .tg-mbig .stagewrap .sq-layer,.tg-mthumb .sq-layer{position:absolute;inset:0;pointer-events:none;z-index:0;overflow:hidden}
  .tg-mbig .stagewrap .mesh,.tg-mthumb .mesh{position:absolute;inset:0;pointer-events:none;opacity:.45;background:radial-gradient(60% 50% at 80% 20%,rgba(0,229,204,.08) 0%,transparent 60%),radial-gradient(40% 40% at 15% 80%,rgba(123,97,255,.07) 0%,transparent 50%)}
  .tg-mbig .stagewrap .mesh-lines,.tg-mthumb .mesh-lines{position:absolute;inset:0;pointer-events:none;background-image:linear-gradient(rgba(0,229,204,.03) 1px,transparent 1px),linear-gradient(90deg,rgba(0,229,204,.03) 1px,transparent 1px);background-size:60px 60px;mask-image:radial-gradient(70% 60% at 80% 10%,black 0%,transparent 70%)}
  /* 上面 .stagewrap img / .tg-mthumb img 那两条(object-fit:contain/cover + 100%)
     是给**退路的静态 <img>** 写的;deck 对象里的照片有自己的框,别被撑成整页。 */
  .tg-mbig .stagewrap .obj-image img,.tg-mthumb .obj-image img{width:100%;height:100%;object-fit:cover;display:block}
  .tg-mfoot{flex-shrink:0;display:flex;align-items:center;justify-content:flex-end;gap:12px;padding:14px 22px;border-top:1px solid rgba(35,31,32,.06)}
  .tg-mfoot-count{font-size:12.5px;color:var(--t3);font-weight:500;margin-right:auto}
  .tg-mfoot .btn{height:42px;border-radius:12px;padding:0 22px;font-family:var(--font-h);font-size:13px;font-weight:650}
  @media(max-width:760px){.tg-mmain{gap:12px;padding:16px}.tg-mthumb{height:56px}.tg-ov{padding:16px}}
  @media(max-width:900px){.tg-grid{grid-template-columns:1fr 1fr}}

/* ── deck-engine.css 背景层的镜像 ──────────────────────────────────────────
   Clean Light / Midnight Purple / Warm Earth / Bold Mono 的封面背景是一摞
   `.midnight-orb` `.warm-wrap` 这样的层,**样式只存在于 deck-engine.css**。
   而这个页面故意不引 deck-engine.css(templates.html:2366 写了原因:它有 132
   条 html/body/* 的全局规则,引进来会毁掉整页)。所以这四套的 deck.json 渲进
   弹窗时,这些层拿不到任何样式 —— 高度 0、全透明,midnight 会变成纯黑一片。
   实测确认过:.midnight-bg/.orb1-3/.overlay/.grid 全部 height:0。

   这就是它们当初被指到 galleries/<id>.html(手工 1:1 复刻,不依赖
   deck-engine.css)的原因。现在改回 deck.json 作单一数据源,缺的这一半必须
   在这里补上。

   规则是**脚本从 deck-engine.css 原样抽出来再加作用域前缀的**,没有手抄;
   deck-engine.css 那边改了这些类,重跑一次抽取即可。几乎全是百分比,
   与渲染尺寸无关;.midnight-bg 的 url() 是相对 code/engine/ 的,本文件同目录,
   照样解析得到。动画 keyframes 没有一起搬 —— 预览是静态的,少了 keyframes
   动画声明会被忽略,元素停在基准样式上,不影响可见性。 */
.tg-mbig .stagewrap .midnight-bg, .tg-mthumb .midnight-bg { position: absolute; inset: 0; z-index: 0; pointer-events: none; background: url('midnight-bg.svg') center/cover no-repeat; }
.tg-mbig .stagewrap .midnight-orb, .tg-mthumb .midnight-orb { position: absolute; border-radius: 50%; pointer-events: none; mix-blend-mode: screen; z-index: 1; animation: midnightBreathe var(--dur, 4s) ease-in-out infinite; animation-delay: var(--delay, 0s); }
.tg-mbig .stagewrap .midnight-orb1, .tg-mthumb .midnight-orb1 { width: 20%; height: 67%; top: 8%; left: -5%; background: radial-gradient(ellipse at 60% 30%, #a0eeff 0%, #4488ff 30%, #6633cc 65%, transparent 100%); filter: blur(28px); opacity: 0.75; --dur: 5.2s; --delay: 0s; }
.tg-mbig .stagewrap .midnight-orb2, .tg-mthumb .midnight-orb2 { width: 48%; height: 86%; top: 4%; right: -6%; background: radial-gradient(ellipse at 40% 35%, #88aaff 0%, #cc44ff 35%, #6622cc 60%, #330088 80%, transparent 100%); filter: blur(32px); opacity: 0.8; --dur: 6.8s; --delay: -2.1s; }
.tg-mbig .stagewrap .midnight-orb3, .tg-mthumb .midnight-orb3 { width: 30%; height: 50%; top: 28%; left: 23%; background: radial-gradient(ellipse at 50% 50%, rgba(255,102,187,0.33) 0%, rgba(136,68,255,0.27) 50%, transparent 80%); filter: blur(50px); opacity: 0.6; --dur: 8s; --delay: -3.5s; }
.tg-mbig .stagewrap .midnight-overlay, .tg-mthumb .midnight-overlay { position: absolute; inset: 0; z-index: 2; pointer-events: none; background: linear-gradient(135deg, rgba(5,3,20,0.55) 0%, rgba(5,3,20,0.2) 60%, rgba(5,3,20,0.45) 100%); }
.tg-mbig .stagewrap .midnight-grid, .tg-mthumb .midnight-grid { position: absolute; inset: 0; z-index: 1; pointer-events: none; opacity: 0.07; background-image: repeating-linear-gradient(90deg, white 0px, white 2px, transparent 2px, transparent 12.5%); }
.tg-mbig .stagewrap .fluid-wrap, .tg-mthumb .fluid-wrap { position: absolute; inset: 0; z-index: 0; pointer-events: none; overflow: hidden; }
.tg-mbig .stagewrap .fluid-wrap svg, .tg-mthumb .fluid-wrap svg { width: 100%; height: 100%; display: block; position: absolute; inset: 0; }
.tg-mbig .stagewrap .fluid-fade, .tg-mthumb .fluid-fade { position: absolute; left: 0; bottom: 0; width: 100%; height: 20%; background: linear-gradient(to bottom, rgba(255,255,255,0) 0%, rgba(255,255,255,1) 100%); pointer-events: none; z-index: 2; }
.tg-mbig .stagewrap .fluid-blob, .tg-mthumb .fluid-blob { position: absolute; border-radius: 50%; mix-blend-mode: multiply; will-change: transform; pointer-events: none; z-index: 1; }
.tg-mbig .stagewrap .fluid-blob.fb1, .tg-mthumb .fluid-blob.fb1 { width: 48%; height: 64%; background: radial-gradient(ellipse, rgba(68,182,233,0.55) 0%, rgba(49,121,255,0.35) 50%, transparent 80%); top: -11%; left: 14%; filter: blur(60px); animation: fluidBlob1 8s linear infinite; }
.tg-mbig .stagewrap .fluid-blob.fb2, .tg-mthumb .fluid-blob.fb2 { width: 43%; height: 58%; background: radial-gradient(ellipse, rgba(162,245,255,0.50) 0%, rgba(138,191,252,0.30) 55%, transparent 80%); top: -9%; right: 7%; filter: blur(55px); animation: fluidBlob2 11s linear infinite; }
.tg-mbig .stagewrap .warm-wrap, .tg-mthumb .warm-wrap { position: absolute; inset: 0; z-index: 0; pointer-events: none; overflow: hidden; }
.tg-mbig .stagewrap .warm-wrap svg, .tg-mthumb .warm-wrap svg { width: 100%; height: 100%; display: block; position: absolute; inset: 0; }
.tg-mbig .stagewrap .warm-fade, .tg-mthumb .warm-fade { position: absolute; left: 0; bottom: 0; width: 100%; height: 18%; background: linear-gradient(to bottom, rgba(255,248,240,0) 0%, rgba(255,248,240,1) 100%); pointer-events: none; z-index: 1; }
.tg-mbig .stagewrap .warm-wrap #arc0, .tg-mthumb .warm-wrap #arc0 { transform-origin: 400px 450px; animation: warmArc0 10s linear infinite; will-change: transform; }
.tg-mbig .stagewrap .warm-wrap #arc1, .tg-mthumb .warm-wrap #arc1 { transform-origin: 1050px 450px; animation: warmArc1 13s linear infinite; will-change: transform; }
.tg-mbig .stagewrap .bold-wrap, .tg-mthumb .bold-wrap { position: absolute; inset: 0; z-index: 0; pointer-events: none; overflow: hidden; }
.tg-mbig .stagewrap .bold-wrap svg, .tg-mthumb .bold-wrap svg { width: 100%; height: 100%; display: block; position: absolute; inset: 0; }
.tg-mbig .stagewrap .clean-wrap, .tg-mthumb .clean-wrap { position: absolute; inset: 0; z-index: 0; pointer-events: none; overflow: hidden; }
.tg-mbig .stagewrap .clean-wrap svg, .tg-mthumb .clean-wrap svg { width: 100%; height: 100%; display: block; position: absolute; inset: 0; }
