/* Rheo — Shader Studio
   ────────────────────────────────────────────────────────────────────────
   Layout map
     header ........... brand bar, scrolls away with the page
     main ............. grid [ .workspace | aside 330px ]
       .workspace
         .preview-dock       sticky — stage + transport + preset strip
       aside                 parameter panel, sections 01–05
   The dock carries the preset strip so nothing can scroll underneath it;
   below 900px .workspace uses display:contents so the dock stays sticky
   across the whole parameter panel instead of ending with a short column.

   Colour
     The canvas renders saturated artwork, so the chrome is monochrome:
     any hue here competes with the work. --accent is therefore near-black
     in light mode and near-white in dark mode, and carries every primary
     action, active state and control fill. Themes come from
     prefers-color-scheme and are overridden by [data-theme] on <html>.
     Metadata drawn OVER the canvas is keyed to the SHADER's background
     luminance (.light-canvas, set in app.js), not to the UI theme.
   ──────────────────────────────────────────────────────────────────────── */

/* ── Header ────────────────────────────────────────────────────────────── */
header{height:64px;padding:0 34px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--border)}
.brand{display:flex;align-items:center;gap:11px;color:inherit;text-decoration:none;font-size:25px;letter-spacing:-.01em;font-weight:650}
.brand-mark{width:26px;height:26px;color:var(--text);display:block;flex:none}
.brand-sub{font:11px var(--mono);letter-spacing:2px;margin:6px 0 0 15px;color:var(--text-3)}
.header-right{display:flex;align-items:center;gap:18px}
.local{font-size:12px;color:var(--text-3)}
#theme{padding:0;width:36px;height:36px;display:grid;place-items:center;background:transparent;border-color:var(--border);color:var(--text-2)}
#theme svg{width:17px;height:17px}
#theme:hover{color:var(--text)}
#theme:hover{background:var(--surface-3);border-color:var(--border-control)}

/* ── Shell ─────────────────────────────────────────────────────────────── */
main{display:grid;grid-template-columns:minmax(0,1fr) 330px;max-width:1800px;margin:auto}
/* 340px = 页眉 64 ＋ workspace 上边距 14 ＋ dock 上边距 8 ＋ 播放栏与预设条 ~250 ＋ 余量，
   取这个上限是为了让整个 dock 在未滚动时就装得下 —— 标题移除腾出的空间用在
   「不用滚就能看到预设条」上，比单纯把画面放大更有用。 */
.workspace{padding:14px 34px 18px;min-width:0}

/* ── Preview dock: stage + transport + preset strip travel together ────── */
.preview-dock{position:sticky;top:var(--dock-inset);z-index:10;background:var(--bg);padding-top:8px}
.stage{position:relative;overflow:hidden;border:1px solid var(--border);border-radius:var(--r-panel);aspect-ratio:1.76;background:var(--stage)}
.preview-dock .stage:not(:fullscreen){width:100%;max-height:max(180px, calc(100dvh - 348px))}
.stage canvas{display:block;width:100%;height:100%}
/* Canvas-relative, not theme-relative: defaults suit a dark shader background,
   .light-canvas (app.js, by shader background luminance) flips them. */
.stage-top{position:absolute;top:22px;left:24px;right:24px;display:flex;justify-content:space-between;font:10px var(--mono);letter-spacing:1.5px;color:#d7e2dd;pointer-events:none;opacity:.75}
.live{display:flex;gap:8px;align-items:center}
.live i{width:5px;height:5px;background:#eef4f0;border-radius:50%;box-shadow:0 0 7px #ffffffb0}
.stage-caption{position:absolute;bottom:25px;left:26px;right:24px;display:flex;justify-content:space-between;align-items:end;pointer-events:none}
.stage-caption button{pointer-events:auto}
.stage-caption h2{font-size:18px;font-weight:500;margin:10px 0;color:#fff;text-shadow:0 2px 10px #0009}
.stage-caption .eyebrow{color:#c3d0c8}
.stage-caption #seed-caption{font:10px var(--mono);color:#c3d0c8}
.glass{background:#ffffff14;backdrop-filter:blur(10px);font-size:19px;width:38px;height:38px;padding:0;display:grid;place-items:center;pointer-events:auto;border-color:#ffffff33;color:#fff}
.glass:hover{background:#ffffff26;border-color:#ffffff55}
#error{position:absolute;inset:30%;background:var(--surface);color:var(--text);padding:25px;border:1px solid var(--border-control);border-radius:var(--r-card);z-index:4}
.stage:fullscreen{aspect-ratio:auto;border-radius:0}
.stage:fullscreen .stage-top,.stage:fullscreen .stage-caption{opacity:0;transition:opacity .2s}
.stage:fullscreen:hover .stage-caption{opacity:1}
.stage.light-canvas .stage-top,.stage.light-canvas .stage-caption .eyebrow,.stage.light-canvas .stage-caption #seed-caption{color:#2b3b41}
.stage.light-canvas .stage-caption h2{color:#101d23;text-shadow:none}
.stage.light-canvas .live i{background:#1d3b43;box-shadow:none}
.stage.light-canvas .glass{color:#16343c;border-color:#16343c40;background:#ffffff66}
.stage.light-canvas .glass:hover{background:#ffffff99}

.transport,.transport>div{display:flex;align-items:center;gap:15px}
.transport{justify-content:space-between;margin-top:10px;padding:6px 8px 6px 10px;color:var(--text-2);
  background:var(--surface);border-radius:var(--r-pill)}
.icon-button{width:32px;height:32px;padding:0;display:grid;place-items:center;font-size:16px;background:none;border-color:transparent;color:var(--text)}
.icon-button:hover{background:var(--surface-3);border-color:transparent}
#time,#fps{font:11px var(--mono)}
.divider{height:14px;border-left:1px solid var(--border)}
.quality-label{font-size:12px}
.quality-label select{font-size:12px;border:0;background:transparent;padding-left:6px}
.transport .quiet{font-size:12px;padding:7px 14px;background:var(--surface-2);border-color:var(--border)}

/* ── Preset strip: always reachable, never scrolls under the preview ───── */
.gallery{padding:14px 0 2px}
.presets{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}
.preset{padding:0;background:var(--surface-2);border:1px solid var(--border);overflow:hidden;text-align:left;border-radius:var(--r-card)}
.preset:hover{border-color:var(--border-control);background:var(--surface-2)}
/* Active state is a filled label, not a hue — readable without colour. */
.preset.active{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent)}
.preset.active .preset-label{background:var(--accent);color:var(--accent-fg);font-weight:600}
.preset.active .preset-label small{color:var(--accent-fg);opacity:.6}
/* 高度上限：缩略图按 aspect-ratio 随栏宽长高，宽屏下预设条会越来越厚，
   把上面 340px 那个常数顶破。封顶后 1920 宽下也能一屏装完。 */
.preset img{width:100%;height:auto;aspect-ratio:3;max-height:100px;object-fit:cover;display:block}
.preset-label{display:flex;align-items:center;justify-content:space-between;padding:7px 9px;font-size:12px;color:var(--text-2)}
.preset-label small{color:var(--text-3);font:9px var(--mono)}

/* ── Parameter panel ───────────────────────────────────────────────────── */
aside{background:var(--surface);border-left:1px solid var(--border);padding:27px 23px 20px}
.panel-heading{display:flex;justify-content:space-between;align-items:center;margin-bottom:5px}
.panel-heading h2{font-size:17px;font-weight:600;margin:0;letter-spacing:-.01em}
.text-button{border:0;background:none;color:var(--text-2);font-size:12px;padding:5px;cursor:pointer}
.text-button:hover{background:none;color:var(--text)}
.control-section{padding:22px 0;border-bottom:1px solid var(--border)}
.section-heading{display:flex;justify-content:space-between;align-items:center;margin-bottom:18px}
.section-heading h3{font-size:14px;font-weight:600;margin:0}
.section-heading h3 span{color:var(--text-3);font:10px var(--mono);margin-right:10px;font-weight:400}
.small-tag{font-size:10px;color:var(--text-3);border:1px solid var(--border);border-radius:var(--r-pill);padding:3px 9px}
.control-hint{font-size:11px;line-height:1.7;color:var(--text-3);margin:14px 0 0}
.field-label{display:flex;justify-content:space-between;align-items:baseline;gap:10px}
.muted{color:var(--text-3);font-size:11px}

/* 输入与确认合成一个胶囊：外层画边框，内部两件都去掉自己的边框， */
.seed-row{display:flex;align-items:center;margin-top:8px;padding:3px 3px 3px 14px;gap:6px;
  background:var(--surface-2);border:1px solid var(--border-control);border-radius:var(--r-pill)}
.seed-row:focus-within{border-color:var(--text-3)}
.seed-row input{min-width:0;flex:1;background:none;color:var(--text);border:0;padding:8px 0;font-family:var(--mono)}
.seed-row input:focus-visible{outline:0}
.seed-row button{flex:none;width:34px;height:34px;padding:0;display:grid;place-items:center;font-size:15px;background:var(--surface-3);border-color:transparent}
.lock-row{display:flex;flex-wrap:wrap;gap:8px;margin:12px 0;color:var(--text-2)}
.lock-row label{display:flex;align-items:center;gap:7px;padding:6px 13px 6px 10px;font-size:12px;
  border:1px solid var(--border);border-radius:var(--r-pill);background:var(--surface-2);cursor:pointer;
  transition:border-color .15s,background .15s}
.lock-row label:hover{border-color:var(--border-control)}
.lock-row label:has(input:checked){border-color:var(--accent);color:var(--text)}
.primary{background:var(--accent);color:var(--accent-fg);width:100%;border:1px solid var(--accent);padding:13px 18px;font-weight:600;text-align:left}
.primary:hover{background:var(--accent);border-color:var(--accent);opacity:.88}
.primary span{float:right;border:1px solid currentColor;font-size:11px;padding:1px 7px;border-radius:var(--r-pill);opacity:.55}

.colors{display:flex;gap:12px}
.colors label{flex:1;color:var(--text-2);min-width:0}
.colors input{display:block;width:100%;height:34px;border-radius:var(--r-pill);margin-top:9px;cursor:pointer}
.colors label span{font:10px var(--mono);display:block;text-transform:uppercase;margin-top:6px;color:var(--text-3)}
.background-control label{display:flex;align-items:center;gap:15px}
.background-control input{width:55px;margin:0}
.background-control label span{margin:0}
.color-stops-heading{display:flex;justify-content:space-between;margin:18px 0 10px;color:var(--text-2);font-size:12px}
.color-stop{display:flex;align-items:center;gap:9px;margin:7px 0}
.color-stop label{font:11px var(--mono);color:var(--text-3);width:12px}
.color-stop input[type=color]{height:30px;width:38px;border-radius:var(--r-pill);cursor:pointer;flex:none}
.color-stop button{width:26px;height:26px;padding:0;display:grid;place-items:center;flex:none;background:none;border-color:transparent;color:var(--text-3)}
.color-stop button:hover{background:var(--surface-3);color:var(--text)}
.hex-input{min-width:0;flex:1;border:1px solid var(--border-control);background:var(--surface-2);color:var(--text);padding:6px 12px;border-radius:var(--r-pill);font:12px var(--mono)}
.add-color{width:100%;margin-top:10px;border-style:dashed;border-color:var(--border-control);color:var(--text-2);font-size:12px;background:none}
.add-color:hover{color:var(--text);background:var(--surface-3)}
.palettes{display:flex;gap:8px;margin:15px 0 20px}
.palette{height:18px;flex:1;border-radius:var(--r-pill);padding:0;border:1px solid var(--border-control)}
.palette-random{font-size:12px;padding:6px 9px;white-space:nowrap}

/* Slider and colour-stop rows reflow into columns whenever the panel is
   wide — one column in the 330px rail, several when it spans the page. */
.control-grid,.stop-grid{display:grid;gap:0 26px}
.control-grid{grid-template-columns:repeat(auto-fit,minmax(230px,1fr))}
.stop-grid{grid-template-columns:repeat(auto-fit,minmax(215px,1fr))}
.range-field{margin-top:18px}
.range-head{display:flex;justify-content:space-between;align-items:center}
.range-head output{color:var(--text-2);font:11px var(--mono)}
input[type=range]{width:100%;height:3px;margin:15px 0 2px;display:block;accent-color:var(--accent);cursor:pointer}
.select-row{display:flex;justify-content:space-between;align-items:center;gap:12px}
.select-row select{font-size:12px}
.switch{position:relative;display:block;width:30px;height:17px}
.switch input{position:absolute;opacity:0;width:100%;height:100%;margin:0;cursor:pointer;z-index:2}
.switch span{display:block;border-radius:20px;background:var(--surface-3);border:1px solid var(--border-control);width:30px;height:17px}
.switch span:after{content:"";width:9px;height:9px;border-radius:50%;background:var(--text-3);position:absolute;left:3px;top:3px;transition:transform .2s,background .2s}
.switch input:checked+span{background:var(--accent);border-color:var(--accent)}
.switch input:checked+span:after{transform:translateX(13px);background:var(--accent-fg)}
.switch input:focus-visible+span{outline:2px solid var(--text);outline-offset:3px}
#particle-controls.disabled{opacity:.45}
#ascii-options{margin-top:18px}
#ascii-custom-row{margin-top:14px}
#ascii-custom{display:block;width:100%;margin-top:8px;padding:10px 16px;border:1px solid var(--border-control);border-radius:var(--r-pill);background:var(--surface-2);color:var(--text)}
#ascii-custom:invalid{border-color:var(--danger)}

#export-menu{position:fixed;inset:auto;margin:0;width:250px;max-width:calc(100vw - 24px);padding:6px;border:1px solid var(--border-control);border-radius:var(--r-panel);background:var(--surface);color:var(--text);box-shadow:0 10px 35px #0003}
#export-menu:popover-open{display:grid;gap:3px}
#export-menu button{display:block;width:100%;padding:12px 16px;text-align:left;border:0;border-radius:var(--r-card);background:transparent;color:var(--text)}
#export-menu button:hover,#export-menu button:focus-visible{background:var(--surface-3)}
#export-menu button span{display:flex;align-items:center;justify-content:space-between;font-size:13px}
#export-menu button b{font:10px var(--mono);color:var(--text-3)}
#export-menu button small{display:block;margin-top:5px;font-size:11px;color:var(--text-3)}
aside>.control-section:last-of-type{border-bottom:0}

#toast{position:fixed;bottom:25px;left:50%;transform:translate(-50%,20px);padding:12px 24px;background:var(--accent);color:var(--accent-fg);border-radius:var(--r-pill);opacity:0;transition:.2s;pointer-events:none;z-index:20;box-shadow:0 6px 20px #00000026}
#toast.show{opacity:1;transform:translate(-50%,0)}

/* ── 双栏：左右各自滚动 ─────────────────────────────────────────────────
   整页不再是一个文档滚动。页眉固定，main 占满剩余高度，左右两栏各自成为
   独立的滚动容器：

     .workspace  overflow-y:auto —— 画面装得下就没有滚动，装不下（矮窗口、
                 全屏比例极端时）才滚。这正是「画面显示完全时左侧不滚动」。
     aside       参数一定比一屏长，始终可滚。

   原先共用文档滚动时，即使左侧内容全部可见，滚轮仍会把整页（含页眉）推上去，
   吸附只是把 dock 追回原位，观感上左侧仍在动。
   窄屏（≤900px）不套用本节，仍是单列文档滚动。 */
@media(min-width:901px){
  html,body{height:100%}
  body{display:flex;flex-direction:column;overflow:hidden}
  header{flex:none}
  main{flex:1 1 auto;min-height:0;width:100%}
  .workspace{overflow-y:auto;overscroll-behavior:contain}
  aside{overflow-y:auto;overscroll-behavior:contain}
  /* 左栏自己就是滚动容器，dock 不必再吸附 */
  .preview-dock{position:static;padding-top:0}
}

/* ── Breakpoints ───────────────────────────────────────────────────────── */
@media(min-width:1500px){
  .workspace{padding:14px 50px 22px}
  .presets{gap:15px}
  /* 这个断点自带更大的预设条间距与下边距，比基准多占约 14px，
     画面上限相应再收一点，否则一屏仍装不下。 */
  .preview-dock .stage:not(:fullscreen){max-height:max(180px, calc(100dvh - 362px))}
}
@media(max-width:1100px){
  main{grid-template-columns:minmax(0,1fr) 290px}
  .workspace{padding:12px 22px 16px}
  aside{padding:27px 18px}
  .brand-sub{display:none}
  .transport{gap:8px}
  .transport>div{gap:7px}
}
/* One column: the dock spans the page and stays pinned above the panel. */
@media(max-width:900px){
  html{scroll-padding-top:calc(clamp(120px,30dvh,300px) + 186px)}
  main{display:block}
  .workspace{display:contents}
  .preview-dock{--dock-inset:0;padding:10px 20px 0;box-shadow:0 10px 22px #0000001f}
  .preview-dock .stage:not(:fullscreen){height:clamp(120px,30dvh,300px);max-height:none;aspect-ratio:auto}
  .transport{padding:10px 0;gap:6px}
  .transport>div{gap:6px}
  .gallery{padding:11px 0 10px}
  aside{border-left:0;border-top:1px solid var(--border)}
  .stage-caption{bottom:16px}
  .stage-caption h2{margin:5px 0;font-size:16px}
  .stage-caption .eyebrow{display:none}
}
@media(max-width:740px){
  header{height:56px;padding:0 18px}
  .local{display:none}
  h1{font-size:24px}
  .preview-dock{padding-left:16px;padding-right:16px}
  aside{padding:25px 22px}
  .eyebrow{font-size:9px}
  #fps,.divider{display:none}
  .transport>div{gap:4px}
  .stage-caption #seed-caption{max-width:230px;display:block;overflow:hidden;text-overflow:ellipsis}
  .control-section{padding:23px 0}
  .colors input{height:45px}
  .range-field{margin-top:24px}
  input[type=range]{height:5px;margin-top:18px}
}
/* Too narrow for four readable thumbnails — scroll the strip sideways. */
@media(max-width:620px){
  .gallery{padding:10px 0 8px}
  .presets{display:flex;overflow-x:auto;gap:8px;padding-bottom:4px;scrollbar-width:thin;scroll-snap-type:x proximity}
  .preset{flex:0 0 116px;scroll-snap-align:start}
  .preset img{aspect-ratio:1.9}
  .preset-label{padding:6px 8px;font-size:11px}
}
@media(max-width:480px){
  .preview-dock{padding-left:12px;padding-right:12px}
  .transport .quiet{padding:7px 5px}
}
/* Landscape phones: a pinned dock would leave no room for the panel. */
@media(max-height:560px){
  .preview-dock{position:static}
  html{scroll-padding-top:0}
}
@media(prefers-reduced-motion:reduce){
  *{transition:none!important}
}

#export-progress{position:fixed;left:50%;bottom:24px;transform:translateX(-50%);z-index:25;display:flex;align-items:center;gap:14px;max-width:calc(100vw - 32px);padding:14px 20px;border:1px solid var(--border-control);border-radius:var(--r-panel);background:var(--surface);color:var(--text);box-shadow:0 6px 24px #0003;font-size:12px}
#export-progress[hidden]{display:none}#export-progress progress{width:90px;accent-color:var(--text)}
