/* Sable Designs — sable (near-black) and or (gold). Heraldic pairing, on-name,
   and deliberately nothing like the purple SaaS default. */
:root{
  --bg:#0a0a0c; --panel:#141417; --panel-2:#1c1c21; --line:#26262c;
  --text:#ece9e4; --muted:#8b867e;
  --gold:#d4a24c; --gold-lt:#efd09a; --gold-dk:#a97722; --gold-rgb:212,162,76; --on-gold:#241a08;
  --ok:#5ec98a; --warn:#e0a03c; --err:#e0685f;
  --r:14px;
  --shadow:0 12px 36px -20px rgba(0,0,0,.75);
  --serif:"Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif;
}
*{box-sizing:border-box}
html,body{margin:0;height:100%}
body{background:var(--bg);color:var(--text);
  font:15px/1.55 ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
  -webkit-font-smoothing:antialiased}
.hidden{display:none !important}
.muted{color:var(--muted)}
.small{font-size:12.5px}
.err{color:var(--err);min-height:16px;font-size:12.5px;margin:6px 0 0}
/* Account settings feedback. A confirmation and a refusal sit in the same
   slot under each form, so the message carries the colour rather than the
   layout moving. Styled here and not inline: the prod CSP is style-src
   'self' with no unsafe-inline, so a style= attribute is silently dead. */
.acct-msg{min-height:16px;font-size:12.5px;margin:8px 0 0}
.acct-msg.ok{color:var(--ok,#7bc47f)}

/* A real switch for two-factor, because on/off is a state and a button that
   says "Turn on" is an action - the owner asked to SEE which it is. The
   checkbox stays in the DOM for the keyboard and for screen readers and is
   only visually replaced by the track. */
label.switch{display:flex;align-items:center;gap:10px;cursor:pointer;
  margin:14px 0 0;text-transform:none;letter-spacing:0}
label.switch input{position:absolute;opacity:0;width:0;height:0}
label.switch .track{position:relative;flex:0 0 auto;width:40px;height:22px;
  border-radius:999px;background:var(--panel-2);border:1px solid var(--line);
  transition:background .15s,border-color .15s}
label.switch .knob{position:absolute;top:2px;left:2px;width:16px;height:16px;
  border-radius:50%;background:var(--muted);transition:transform .15s,background .15s}
label.switch input:checked + .track{background:var(--gold);border-color:var(--gold)}
label.switch input:checked + .track .knob{transform:translateX(18px);background:#12120f}
label.switch input:focus-visible + .track{border-color:var(--gold);
  box-shadow:0 0 0 2px rgba(200,168,90,.35)}
.switch-text{font-size:14px;color:var(--text)}
/* The VA panel's characters: one switch per character, under each VA. */
.va-row{flex-wrap:wrap}
.va-row .va-chars{flex-basis:100%;margin-top:4px}
.va-chars-head{margin:0 0 6px}
.va-chars-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:6px 14px}
label.switch.va-char{flex-direction:row;margin:0;padding:6px 8px;border:1px solid var(--line);border-radius:10px;background:var(--panel-2)}
.va-char-face{width:30px;height:30px;flex:none;border-radius:50%;overflow:hidden;background:var(--panel);
  display:inline-flex;align-items:center;justify-content:center;font-size:12px;color:var(--muted)}
.va-char-face img{width:100%;height:100%;object-fit:cover;display:block}
.va-char-name{flex:1;min-width:0;overflow-wrap:anywhere;font-size:13.5px}
/* A character the owner shared with a VA: no editing, so no actions row. */
.char-shared-note{color:var(--gold-lt);margin-top:4px}
#tf-cancel{margin-left:8px}

/* one account per line on the admin page */
.acc-row{display:flex;align-items:center;gap:12px;padding:9px 0;
  border-bottom:1px solid var(--line)}
.acc-row:last-child{border-bottom:none}
.acc-who{flex:1 1 auto;font-size:14px;word-break:break-all}
h2{margin:0;font-size:19px;font-weight:600;letter-spacing:-.01em}
h3{margin:0;font-size:15px;font-weight:600}
h4{margin:20px 0 4px;font-size:14px}

/* brand ---------------------------------------------------------------- */
.brand{display:flex;align-items:center;gap:9px;font-weight:600;font-size:15px;
  letter-spacing:.01em;white-space:nowrap}
.brand .thin{font-weight:400;color:var(--muted)}
.brand-lg{font-size:20px;font-family:var(--serif);letter-spacing:.02em}
.mark{width:22px;height:22px;display:block;flex:none}
.mark .lg-1{stop-color:var(--gold-lt)} .mark .lg-2{stop-color:var(--gold)} .mark .lg-3{stop-color:var(--gold-dk)}
/* Which site you are on, under the name (two front doors). Shown only when the
   server has marked the page with its side. */
.brand-sub{display:none}
/* The row is tight (logo, name, site switch, collapse in 216px), so the name
   never shrinks, and the line under it is exactly as wide as its own text: the
   gradient only paints inside its box, so a squeezed box cut the last letters
   off ("SAFE FOR WO", owner 2026-10-11). */
:root[data-side] .rail-brand{gap:6px}
:root[data-side] .rail-brand .brand-name{display:flex;flex-direction:column;justify-content:center;
  gap:2px;line-height:1.1;flex:none}
:root[data-side="com"] .sub-com,:root[data-side="red"] .sub-red{display:block;width:max-content;font-size:9.5px;
  font-weight:700;letter-spacing:.07em;text-transform:uppercase;white-space:nowrap;
  background:linear-gradient(90deg,var(--gold-lt),var(--gold) 60%,var(--gold-dk));
  -webkit-background-clip:text;background-clip:text;color:transparent}
.brand-lg .mark{width:30px;height:30px}

/* overlays ------------------------------------------------------------- */
.overlay{position:fixed;inset:0;display:grid;place-items:center;z-index:50;padding:20px;
  background:radial-gradient(80% 55% at 50% 0%,#17161a 0%,var(--bg) 70%)}
.card{background:var(--panel);border:1px solid var(--line);border-radius:14px;
  padding:30px;width:100%;max-width:390px;display:flex;flex-direction:column;gap:13px;
  box-shadow:0 24px 60px -20px #000}
.card h3{font-family:var(--serif);font-size:17px;margin-top:4px}

/* controls ------------------------------------------------------------- */
label{display:flex;flex-direction:column;gap:5px;font-size:12px;color:var(--muted);
  text-transform:uppercase;letter-spacing:.05em}
label.check{flex-direction:row;align-items:center;gap:9px;text-transform:none;
  letter-spacing:0;font-size:14px;color:var(--text)}
input,textarea{background:var(--panel-2);border:1px solid var(--line);color:var(--text);
  border-radius:8px;padding:9px 11px;font:inherit;font-size:14px;width:100%;
  transition:border-color .15s}
input:focus,textarea:focus{outline:none;border-color:var(--gold)}
textarea{resize:vertical;line-height:1.5}
input[type=checkbox]{width:auto;accent-color:var(--gold)}
button{border:0;border-radius:8px;padding:10px 16px;font:inherit;font-size:14px;
  font-weight:600;cursor:pointer;transition:filter .15s,background .15s}
button.primary{background:linear-gradient(180deg,var(--gold-lt),var(--gold));color:var(--on-gold);
  border:1px solid transparent}  /* same height as a ghost button beside it */
button.primary:hover{filter:brightness(1.07)}
button.primary:disabled{opacity:.4;cursor:not-allowed;filter:none}
button.block{width:100%}
/* Anchors are included deliberately: the viewer's Download is an <a download>,
   and a button-only rule left it falling back to the browser's default link
   blue - the one control on the bar that was not our colour. */
button.ghost,a.ghost{background:transparent;color:var(--muted);
  border:1px solid var(--line)}
button.ghost:hover,a.ghost:hover{color:var(--text);border-color:#3a3a41}

/* topbar --------------------------------------------------------------- */
/* The rail runs the full window height, with the brand in it, so the header
   only spans the content area. Previously the topbar cut across the top and
   left the sidebar visibly short. */
/* Detached surfaces on a dark canvas (the Freepik read): rail, composer
   panel and topbar are floating rounded cards separated by gutters, and the
   results canvas is the page itself rather than another box. Flex, not grid:
   a display:none column takes its gap with it, which is what makes the
   collapse states clean. */
#app{display:flex;gap:12px;padding:12px;height:100%;background:var(--bg)}
.panel{width:324px;flex:none;min-width:0;overflow:auto;
  background:var(--panel);border:1px solid var(--line);border-radius:var(--r);
  display:flex;flex-direction:column}
/* Collapsed surfaces on desktop are icon strips, not gone - the way nearly
   every app does it: you can still see where you are and reopen from the
   strip itself. Mobile ignores all of this; drawers slide instead. */
@media (min-width:901px){
  .topbar #rail-toggle,.topbar .panel-toggle{display:none}
  #app.rail-closed .sidebar{width:60px;padding:0 9px 12px}
  #app.rail-closed .rail-brand{justify-content:center;padding:0;margin:0 -9px 10px}
  #app.rail-closed .rail-brand .logo-slot,
  #app.rail-closed .rail-brand .brand-name{display:none}
  #app.rail-closed .sidebar a{justify-content:center;padding:10px 0}
  #app.rail-closed .sidebar a > span:not(.row-icon){display:none}
  #app.rail-closed .sidebar .row-icon,#app.rail-closed .sidebar .row-icon svg{
    width:18px;height:18px}
  #app.rail-closed .side-head{justify-content:center;padding:10px 0}
  #app.rail-closed .side-head .sec-label,
  #app.rail-closed .side-head .chev{display:none}
  #app.rail-closed .side-foot .who{display:none}
  #app.panel-closed .panel{width:60px}
  /* Off the studio the composer is not collapsed, it is GONE. The 60px strip
     it used to leave behind stood in every view and its only control jumped
     you to Image from wherever you were. */
  #app.panel-gone .panel{display:none}
  #app.panel-closed .panel .composer{display:none}
  #app.panel-closed .panel-head{justify-content:center;padding:0;border-bottom:0}
  #app.panel-closed .panel-head > span{display:none}
  #app.panel-closed #panel-collapse svg{transform:rotate(180deg)}
  /* the expanded state parks these buttons on the right with an auto margin,
     and an auto margin beats justify-content - zero it or nothing centers */
  #app.rail-closed #rail-collapse{margin-left:0}
  #app.panel-closed #panel-collapse{margin-left:0}
  /* account strip: only the icon survives collapsing - the role pill and the
     dev "view as" picker have no 60px form */
  #app.rail-closed .sidebar .role-pill,
  #app.rail-closed .sidebar .viewas{display:none}
}
/* On a phone the drawers cover the topbar, so each carries its own way out.
   The chevrons point back the way the drawer slid in. */
@media (max-width:900px){
  #rail-collapse svg,#panel-collapse svg{transform:none}
  #rail-collapse{margin-left:auto}
}
.rail-brand #rail-collapse{margin-left:auto}
.panel-head #panel-collapse{margin-left:auto}
.content{flex:1;min-width:0;display:flex;flex-direction:column;gap:12px}
.panel-head{height:52px;flex:none;display:flex;align-items:center;padding:0 16px;
  border-bottom:1px solid var(--line);font-size:11px;font-weight:600;
  color:var(--muted);text-transform:uppercase;letter-spacing:.1em}
.panel-blurb{display:block;margin:-6px 0 12px;line-height:1.45}
/* One tip per tool, from its own guide; and the first-visit welcome. */
.tool-tip{margin:-4px 0 12px;padding:8px 10px;line-height:1.45;color:var(--muted);
  background:var(--panel-2);border:1px solid var(--line);border-radius:8px}
.card.welcome{max-width:560px;padding:20px;margin:0 0 16px;gap:10px;box-shadow:none}
.welcome-steps{margin:0;padding-left:20px;line-height:1.6}
.card.welcome .primary{align-self:flex-start}
/* inside the panel the composer is the column itself, not a card in a page */
.panel .composer{border:0;border-radius:0;background:none;max-width:none;
  margin:0;padding:14px 16px 20px;flex:1}
.topbar{height:52px;flex:none;display:flex;align-items:center;gap:16px;
  padding:0 16px;background:var(--panel);border:1px solid var(--line);
  border-radius:var(--r)}
.topbar-right{margin-left:auto;display:flex;align-items:center;gap:12px}
.status{display:flex;align-items:center;gap:8px;font-size:12.5px;
  padding:5px 12px 5px 10px;border-radius:99px;background:var(--panel-2);
  border:1px solid var(--line)}
.status .dot{width:7px;height:7px;border-radius:50%;background:var(--muted);flex:none}
.state-idle .dot{background:#5a5a62}
.state-generating{border-color:rgba(94,201,138,.4)}
.state-generating .dot{background:var(--ok);animation:pulse 1.3s ease-in-out infinite}
.state-cold_start,.state-queued{border-color:rgba(224,160,60,.4)}
.state-cold_start .dot,.state-queued .dot{background:var(--warn);
  animation:pulse 1.3s ease-in-out infinite}
.state-unreachable .dot,.state-not_configured .dot{background:var(--err)}
@keyframes pulse{50%{opacity:.35}}

/* shell ---------------------------------------------------------------- */
.shell{display:contents}
.rail-btn{display:grid;place-items:center;width:30px;height:30px;flex:none;
  background:none;border:1px solid var(--line);border-radius:7px;
  color:var(--muted);padding:0;cursor:pointer}
.rail-btn:hover{color:var(--text);border-color:#3a3a41}
.rail-btn svg{width:16px;height:16px}
.sidebar{width:218px;flex:none;background:var(--panel);
  border:1px solid var(--line);border-radius:var(--r);
  padding:0 10px 12px;overflow:auto;display:flex;flex-direction:column;gap:2px}
.rail-brand{height:52px;padding:0 6px;flex:none;
  border-bottom:1px solid var(--line);margin:0 -10px 10px;padding-left:16px}
.sidebar nav{display:flex;flex-direction:column;gap:1px}

/* collapsible groups keep the rail short */
.side-head{width:100%;display:flex;align-items:center;gap:9px;
  background:none;border:0;color:var(--muted);font-size:11px;font-weight:600;
  text-transform:uppercase;letter-spacing:.1em;padding:10px;cursor:pointer;
  border-radius:7px}
.side-head:hover{color:var(--text);background:var(--panel-2)}
.side-head .sec-label{flex:1;text-align:left}
.sec-icon{display:flex;width:17px;height:17px;flex:none;opacity:.85}
.sec-icon svg{width:17px;height:17px}
.side-head .chev{width:15px;height:15px;flex:none;transition:transform .18s ease;
  opacity:.85}
.side-head.closed .chev{transform:rotate(-90deg)}
.side-body{display:flex;flex-direction:column;gap:1px;overflow:hidden}
.side-body.closed{display:none}
.row-icon{display:flex;width:15px;height:15px;flex:none;opacity:.6}
.row-icon svg{width:15px;height:15px}
.sidebar a:hover .row-icon,.sidebar a.active .row-icon{opacity:1}
.sidebar a{align-items:center;gap:9px}
.side-foot{margin-top:auto;border-top:1px solid var(--line);padding-top:8px;
  display:flex;flex-direction:column;gap:2px}
/* Centred: the signed-in address sits under the Account row rather than in a
   list, so left-aligning it made it look like an orphaned nav item. */
.side-foot .who{padding:2px 10px 4px;font-size:11px;text-align:center;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.acct-row{cursor:pointer}
/* icon, then label hard-left. The tag (if any) is pushed right on its own. */
.sidebar a{color:#c9c5be;text-decoration:none;padding:7px 10px;border-radius:7px;
  font-size:13.5px;cursor:pointer;display:flex;justify-content:flex-start;gap:9px;
  text-align:left}
.sidebar a > span:not(.row-icon):not(.tag):not(.role-pill){flex:1;min-width:0;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.sidebar a .tag{margin-left:auto;flex:none}
.sidebar a:hover{background:var(--panel-2);color:var(--text)}
.sidebar a.active{background:linear-gradient(90deg,rgba(var(--gold-rgb),.16),transparent);
  color:var(--gold-lt);box-shadow:inset 2px 0 0 var(--gold)}
.sidebar a.soon{color:#5f5c57;cursor:default}
.sidebar a.soon:hover{background:none;color:#5f5c57}
.tag{font-size:10px;color:var(--muted);border:1px solid var(--line);
  border-radius:99px;padding:1px 6px;align-self:center}
/* the canvas is the page, not another box - tiles float on the dark bg */
main{flex:1;min-height:0;overflow:auto;padding:0 6px 26px}
.view{max-width:1500px;padding-top:14px}
/* the studio's library header is sticky and must line up with the top of the
   sidebar and the composer - padding above it would show as a dark band */
#view-studio{padding-top:0}

/* composer ------------------------------------------------------------- */
.composer{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);
  padding:16px 18px;margin-bottom:24px}
.composer-head{display:flex;align-items:center;gap:10px;margin-bottom:12px}
.composer-head h2{margin-right:2px}
.tool-icon{display:flex;width:22px;height:22px;color:var(--gold);flex:none}
.tool-icon svg{width:22px;height:22px}

/* per-tool settings: only what THIS tool exposes, each with a ? */
.tool-settings{display:flex;flex-wrap:wrap;gap:12px;margin-top:12px}
.setting{display:flex;flex-direction:column;gap:5px;min-width:120px}
/* Settings that must occupy a whole row rather than share one. The container
   is a WRAPPING FLEX ROW, so a field sizes to its content by default - which
   is why the negative prompt came out narrower than the positive one no matter
   what width the textarea itself was given. The constraint was the parent. */
.setting.wide{flex:1 0 100%}
.setting input{width:100%}
/* ANCHORS -------------------------------------------------------------
   Character and reference photo: the two things chosen before anything is
   typed, so they sit above the prompt together. Freepik marks these with grey
   squares; ours are marked with the Sable diamond, which we already own and
   which reads as "this is the thing the picture is built on". */
/* ONE horizontal-scroll strip (owner's call 2026-08-23): character, reference
   and any further references sit on a single row and slide left/right instead
   of wrapping to new rows. Bigger tiles so the thumbnail is actually readable. */
.anchors{display:flex;flex-direction:row;flex-wrap:nowrap;gap:12px;
  margin-bottom:12px;overflow-x:auto;overflow-y:hidden;padding:2px 2px 8px;
  scrollbar-width:thin;scrollbar-color:var(--line) transparent}
.anchors::-webkit-scrollbar{height:6px}
.anchors::-webkit-scrollbar-thumb{background:var(--line);border-radius:3px}
.anchors::-webkit-scrollbar-thumb:hover{background:#3a3a42}
.anchors::-webkit-scrollbar-track{background:transparent}
.anchors:empty{display:none}
/* A TILE, not a panel. Freepik uses a grey square; ours is the Sable mark -
   a square turned 90 degrees - with the control's icon inside it. The native
   select / file input is laid transparently over the whole tile, so tapping
   anywhere opens it and two controls fit in one short row. */
/* No box. The gold diamond IS the control - a panel around it just eats the
   width the sidebar does not have. */
.anchor{position:relative;flex:0 0 auto;width:66px;display:flex;
  flex-direction:column;align-items:center;gap:5px;padding:2px 2px 0;
  scroll-snap-align:start;background:none;border:0;border-radius:10px;cursor:pointer}
/* A plain rounded-rectangle tile (owner's call 2026-08-23: no more diamond) -
   it just shows the picture, cropped to a square, with a gold edge when set. */
.dia{position:relative;width:54px;height:54px;flex:0 0 auto;display:grid;
  place-items:center;overflow:hidden;border-radius:10px;border:1px solid var(--line);
  background:var(--panel-2);transition:border-color .12s}
.anchor:hover .dia{border-color:#3a3a42}
.anchor.on .dia{border-color:var(--gold)}
.dia svg{position:relative;width:22px;height:22px;color:var(--gold-lt)}
/* When something IS chosen the tile becomes its picture - character cover or
   the uploaded reference - filling the whole rounded rectangle. */
.dia img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  border-radius:9px}
.anchor.on .dia svg{display:none}
/* Clipped to its own tile. Without this the text simply overflowed into the
   neighbour - the tile is 100px, "BACKGROUNDS" was not. */
.anchor-lbl{font-size:9px;letter-spacing:.06em;text-transform:uppercase;
  color:var(--muted);line-height:1;max-width:100%;overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
.anchor-val{font-size:10.5px;color:var(--text);max-width:100%;overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap;line-height:1.3}
/* the real control, invisible but covering the tile */
.anchor-hit{position:absolute;inset:0;width:100%;height:100%;opacity:0;
  cursor:pointer;border:0;padding:0}
/* Clear the selection. Sits ABOVE the invisible control, or the control would
   swallow the click - the whole tile is a hit area by design. */
.anchor-x{position:absolute;top:-4px;right:2px;z-index:3;width:18px;height:18px;
  display:none;place-items:center;border-radius:50%;cursor:pointer;
  background:rgba(18,18,20,.9);border:1px solid var(--line);
  color:var(--muted);font-size:11px;line-height:1;padding:0}
.anchor.on .anchor-x{display:grid}
.anchor-x:hover{color:var(--err);border-color:var(--err)}

/* COMPACT ROW ---------------------------------------------------------
   Count and shape on one strip. Five labelled chips plus a number field was
   three rows of a narrow sidebar for two small numbers. */
/* flex-direction:row is NOT optional here: `.setting` is a flex COLUMN, so
   without it the count and the ratio stack instead of sitting side by side.
   Third time this rule has caught me - .setting and label are both columns. */
.compact-row{display:flex;flex-direction:row;align-items:center;gap:8px;
  flex-wrap:nowrap}
.cstep{display:inline-flex;align-items:center;background:var(--panel-2);
  border:1px solid var(--line);border-radius:9px;overflow:hidden}
.cstep-b{background:none;border:0;color:var(--muted);cursor:pointer;
  width:26px;height:30px;font-size:15px;line-height:1;padding:0}
.cstep-b:hover{color:var(--gold-lt);background:rgba(var(--gold-rgb),.10)}
.cstep input{width:26px;border:0;background:none;text-align:center;padding:0;
  font-size:13px;color:var(--text);-moz-appearance:textfield}
.cstep input::-webkit-outer-spin-button,
.cstep input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}
/* FOURTH attempt, and the first that does not fight a native <select>.
   Earlier tries gave the select its own width, colour and text-align-last;
   it still rendered as a sliver, or with the value shoved off-centre by the
   arrow the closed control draws inside its own box. No combination of
   text-align, text-align-last or appearance fixed it across browsers, so
   stop trying: draw the face ourselves - icon plus value, centred - and
   lay the real select over it at zero opacity. Same trick as the anchor
   tiles, and every pixel is ours. */
.cratio{position:relative;display:inline-flex}
.cratio-face{display:inline-flex;align-items:center;justify-content:center;
  gap:7px;height:32px;padding:0 12px;border-radius:9px;
  background:var(--panel-2);border:1px solid var(--line);pointer-events:none}
.cratio-face svg{width:15px;height:15px;color:var(--muted);flex:0 0 auto}
/* a pill with no caret reads as a label, not a control - it has to look like
   it opens something, because it does */
.cratio-face .cratio-caret{width:13px;height:13px;color:var(--gold);
  margin-left:-2px}
.cratio-face b{font-size:13px;font-weight:600;color:var(--text);line-height:1}
.cratio:hover .cratio-face{border-color:#3a3a41}
/* ID-qualified deliberately: `#tool-settings select` below is specificity
   1-0-1 and would otherwise win the padding, border and background of the
   invisible hit layer. Same class of trap that broke this control before. */
#tool-settings select.cratio-hit,.cratio-hit{position:absolute;inset:0;
  width:100%;height:100%;opacity:0;cursor:pointer;border:0;padding:0;
  background:none;appearance:none}

/* the plain-language consequence of a choice, under the control that makes it */
.setting-note{display:block;margin-top:5px;font-size:11px;line-height:1.45;
  color:var(--dim);text-transform:none;letter-spacing:0}
.setting-label{display:flex;align-items:center;gap:5px;font-size:11px;
  color:var(--muted);text-transform:uppercase;letter-spacing:.06em}
.hint{display:inline-grid;place-items:center;width:14px;height:14px;border-radius:50%;
  border:1px solid var(--line);color:var(--muted);font-size:9.5px;font-weight:700;
  cursor:help;position:relative;text-transform:none;letter-spacing:0;flex:none}
.hint:hover,.hint:focus{border-color:var(--gold);color:var(--gold);outline:none}
/* The bubble is NOT a child of the hint any more - see showTip() in app.js.
   Anchoring it inside the control meant the composer column (324px wide,
   overflow:auto) clipped it, so the text was there and unreadable. A fixed
   node parented to <body> is outside every ancestor's overflow, so it can be
   as wide as it needs and sit beside the column instead of inside it. */
.tip{position:fixed;z-index:200;max-width:300px;background:#08080a;
  color:var(--text);border:1px solid #35353d;border-radius:8px;padding:9px 11px;
  font-size:12px;font-weight:400;line-height:1.45;text-align:left;
  pointer-events:none;opacity:0;transition:opacity .12s;
  box-shadow:0 14px 34px -8px #000}
.tip.on{opacity:1}
.hint-demo{display:inline-grid;place-items:center;width:15px;height:15px;
  border-radius:50%;border:1px solid var(--gold);color:var(--gold);font-size:10px;
  font-weight:700;vertical-align:middle}
.ratio-setting{min-width:100%}
.ratios{display:flex;gap:6px;flex-wrap:wrap}
.ratio{background:var(--panel-2);border:1px solid var(--line);color:var(--muted);
  border-radius:7px;padding:6px 11px;font-size:12px;font-weight:600;cursor:pointer}
.ratio:hover{color:var(--text);border-color:#3a3a41}
.ratio.on{background:linear-gradient(180deg,var(--gold-lt),var(--gold));
  color:var(--on-gold);border-color:transparent}
.char-row{display:flex;gap:8px;align-items:center}
/* min-width:0 is the whole fix: a flex child defaults to min-content width, so
   a long character name pushed the Manage button outside the panel instead of
   letting the select shrink. The chosen value ellipsises; the open dropdown is
   drawn by the OS and still shows the full name. */
.char-row select{flex:1 1 auto;min-width:0;text-overflow:ellipsis;
  background:var(--panel-2);border:1px solid var(--line);
  color:var(--text);border-radius:8px;padding:8px 10px;font:inherit;font-size:14px}
.char-row button{flex:0 0 auto;white-space:nowrap}

/* Voice attached to a character, in her editor. Brands the select exactly like
   the composer's (gold caret, panel background) - it sat outside #tool-settings
   so it was rendering as a bare OS control - and aligns the preview button to
   the select's row (align-items:flex-end, since the label stacks above it). */
.ce-voice{display:flex;align-items:flex-end;gap:8px;margin-top:10px}
.ce-voice label{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;
  gap:4px;font-size:12px;color:var(--muted)}
.ce-voice select{width:100%;background:var(--panel-2);border:1px solid var(--line);
  color:var(--text);border-radius:8px;padding:8px 10px;font:inherit;font-size:14px;
  appearance:none;
  background-image:linear-gradient(45deg,transparent 50%,var(--gold) 50%),
                   linear-gradient(135deg,var(--gold) 50%,transparent 50%);
  background-position:calc(100% - 18px) calc(50% + 2px),
                      calc(100% - 13px) calc(50% + 2px);
  background-size:5px 5px,5px 5px;background-repeat:no-repeat}
.ce-voice select:focus{outline:none;border-color:var(--gold)}
.ce-voice select option{background:var(--panel-2);color:var(--text)}
.ce-voice button{flex:0 0 auto;white-space:nowrap}

/* strength slider */
.str-ctl{display:flex;align-items:center;gap:10px}
.str-ctl input[type=range]{flex:1;min-width:0;accent-color:var(--gold)}
.str-ctl b{min-width:42px;text-align:right;color:var(--gold-lt);
  font-variant-numeric:tabular-nums}

/* Every select in the composer wears the brand, not the OS default. The scene
   dropdown was rendering as a grey system control next to gold chips. */
#tool-settings select,#p-scene{width:100%;background:var(--panel-2);
  border:1px solid var(--line);color:var(--text);border-radius:8px;
  padding:8px 10px;font:inherit;font-size:14px;appearance:none;
  background-image:linear-gradient(45deg,transparent 50%,var(--gold) 50%),
                   linear-gradient(135deg,var(--gold) 50%,transparent 50%);
  background-position:calc(100% - 18px) calc(50% + 2px),
                      calc(100% - 13px) calc(50% + 2px);
  background-size:5px 5px,5px 5px;background-repeat:no-repeat}
#tool-settings select:focus,#p-scene:focus{outline:none;border-color:var(--gold)}
#tool-settings select option,#p-scene option{background:var(--panel-2);color:var(--text)}

/* ---- Branded dropdown (replaces the OS-native <select> popup) -----------
   The native popup is drawn by the OS and cannot be themed - on Windows it is
   the grey/blue system list. Every single <select> is progressively enhanced
   in app.js: the real control is hidden and this is drawn over it and synced,
   so all form logic keeps working. Minimalist, Apple-like: rounded, soft
   shadow, a gold check on the selected row. */
/* A select inside a <label> inherited the label's capitals and spacing, so
   Batch's model list read SEEDREAM 5.0 PRO (BY... (owner, 2026-10-11). */
.sable-dd{position:relative;display:block;width:100%;font-size:14px;
  text-transform:none;letter-spacing:normal;font-weight:400}
.sable-dd>select{display:none!important}
.sable-dd-btn{width:100%;display:flex;align-items:center;gap:10px;
  background:var(--panel-2);border:1px solid var(--line);color:var(--text);
  border-radius:9px;padding:9px 12px;font:inherit;font-size:14px;cursor:pointer;
  text-align:left;transition:border-color .12s ease,background .12s ease}
.sable-dd-btn:hover{border-color:#3a3a42}
.sable-dd.open .sable-dd-btn,.sable-dd-btn:focus-visible{outline:none;border-color:var(--gold)}
.sable-dd-btn:disabled{opacity:.5;cursor:default}
.sable-dd-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.sable-dd-chevron{flex:none;width:8px;height:8px;border-right:1.6px solid var(--muted);
  border-bottom:1.6px solid var(--muted);transform:rotate(45deg) translateY(-2px);
  transition:transform .18s ease;margin-left:2px}
.sable-dd.open .sable-dd-chevron{transform:rotate(-135deg) translateY(1px);border-color:var(--gold)}
/* The open list is as wide as its longest option (never narrower than the
   control, never wider than the screen), and an option still too long
   wraps instead of being cut: "16:9 · Wide..." told nobody anything. */
.sable-dd-menu{position:absolute;top:calc(100% + 6px);left:0;right:auto;z-index:60;
  min-width:100%;width:max-content;max-width:min(440px,calc(100vw - 24px));box-sizing:border-box;
  background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:5px;
  box-shadow:0 12px 34px rgba(0,0,0,.5);max-height:300px;overflow-y:auto;display:none;
  animation:sable-dd-in .13s ease}
.sable-dd.open .sable-dd-menu{display:block}
.sable-dd.up .sable-dd-menu{top:auto;bottom:calc(100% + 6px)}
.sable-dd.right .sable-dd-menu{left:auto;right:0}
@keyframes sable-dd-in{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}
.sable-dd-opt{position:relative;padding:8px 12px 8px 30px;border-radius:7px;color:var(--text);
  cursor:pointer;white-space:normal;overflow-wrap:anywhere;line-height:1.3}
.sable-dd-opt:hover,.sable-dd-opt.active{background:rgba(var(--gold-rgb),.14)}
.sable-dd-opt.sel{color:var(--gold-lt)}
.sable-dd-opt.sel::before{content:'';position:absolute;left:12px;top:12px;width:4px;height:8px;
  border-right:1.7px solid var(--gold);border-bottom:1.7px solid var(--gold);
  transform:rotate(45deg)}
.sable-dd-opt.disabled{opacity:.4;cursor:default}
.sable-dd-opt.disabled:hover{background:none}
/* compact toolbar dropdowns keep a small footprint instead of full width */
.sable-dd[data-for="feed-limit"],.sable-dd[data-for="fold-limit"],
.sable-dd[data-for="viewas-sel"],.sable-dd[data-for="viewer-fmt"],
.sable-dd[data-for="feed-by"],
.sable-dd[data-for="sel-folder"],.sable-dd[data-for="edit-pick-src"],
.sable-dd[data-for="bt-sel-folder"],.sable-dd[data-for="bv-sel-folder"]{
  display:inline-block;width:auto;min-width:130px;vertical-align:middle}
/* the aspect-ratio dropdown sits in the compact row next to the count stepper.
   It never shrinks below its own label: "21:9 · Ultrawide" was cut to
   "21:9 · Ult..." once the model chips shared the row (owner's list,
   2026-10-03). The chips wrap to the next line instead. */
.compact-row .sable-dd{flex:1 0 auto;min-width:max-content}
.compact-row .sable-dd .sable-dd-label{overflow:visible;text-overflow:clip}
/* characters (LoRA) ------------------------------------------------------ */
.char-add{max-width:none}
.char-add .filebtn{justify-content:center;margin-bottom:10px}
.detected{background:var(--panel-2);border:1px solid var(--line);
  border-radius:8px;padding:9px 12px;font-size:12.5px;color:var(--muted);
  margin-bottom:10px;line-height:1.5}
.detected b{color:var(--text);font-weight:600}
.char-fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));
  gap:12px}
.up-wrap{margin-top:12px}
.up-wrap .bar{width:100%}

.char-grid{display:grid;gap:14px;
  grid-template-columns:repeat(auto-fill,minmax(210px,240px));
  justify-content:start}
.char-card{background:var(--panel);border:1px solid var(--line);
  border-radius:var(--r);overflow:hidden;display:flex;flex-direction:column}
.char-thumb{position:relative;display:block;aspect-ratio:1;cursor:pointer;
  background:var(--panel-2);overflow:hidden}
.char-thumb img{width:100%;height:100%;object-fit:cover;display:block}
.char-blank{position:absolute;inset:0;display:grid;place-items:center;
  color:#43434d}
.char-blank svg{width:44px;height:44px}
.char-thumb-hint{position:absolute;left:0;right:0;bottom:0;padding:7px 10px;
  font-size:11.5px;color:#fff;background:linear-gradient(180deg,transparent,rgba(0,0,0,.75));
  opacity:0;transition:opacity .15s;text-align:center}
.char-thumb:hover .char-thumb-hint{opacity:1}
/* read-only placeholder card for a character that is mid-training */
.char-card-training{opacity:.92}
.char-thumb-training{display:grid;place-items:center;aspect-ratio:1;
  background:var(--panel-2);color:var(--muted);animation:pulse 1.6s ease-in-out infinite}
.char-training-spin{font-size:13px;color:var(--muted)}
.char-training-badge{flex:1;text-align:center;padding:7px 10px;border-radius:8px;
  background:var(--panel-2);border:1px solid var(--line);color:var(--gold);
  font-size:13px;font-weight:600}
.char-body{padding:11px 12px;display:flex;flex-direction:column;gap:8px;flex:1}
.char-name-in{width:100%;background:transparent;border:1px solid transparent;
  color:var(--text);font:inherit;font-size:14.5px;font-weight:600;
  border-radius:7px;padding:4px 6px;margin-left:-6px}
.char-name-in:hover{border-color:var(--line)}
.char-name-in:focus{border-color:var(--gold);outline:none;
  background:var(--panel-2)}
.char-min{display:flex;align-items:center;gap:8px;font-size:12px;
  color:var(--muted)}
.char-min input[type=text],.char-min input:not([type]){flex:1;min-width:0;
  background:var(--panel-2);border:1px solid var(--line);color:var(--text);
  border-radius:6px;padding:4px 7px;font:inherit;font-size:12.5px}
.char-min input[type=range]{flex:1;min-width:0;accent-color:var(--gold)}
.char-min .str-val{color:var(--text);font-variant-numeric:tabular-nums;
  min-width:34px}
.warn-txt{color:var(--warn)}
/* What the LoRA's own training captions say it covers - the thing that decides
   whether asking it for a full body will work. */
.trained-for{background:var(--panel-2);border:1px solid var(--line);
  border-radius:7px;padding:6px 9px;font-size:12px;color:var(--text);
  line-height:1.45}
.trained-for .tf-label{display:block;font-size:10px;letter-spacing:.07em;
  text-transform:uppercase;color:var(--muted);margin-bottom:2px}
.trained-for.unknown{color:var(--muted)}
.char-actions{display:flex;gap:8px;padding:0 12px 12px}
.char-actions button{flex:1}
.ghost.danger{color:var(--err);border-color:rgba(224,104,95,.35)}
.ghost.danger:hover{background:rgba(224,104,95,.12)}
/* A nav row that stands alone between collapsible groups, so it needs the
   breathing room a group header would otherwise have given it. */
.solo-row{margin:4px 0}
.row .grow{flex:1}
.composer textarea{min-height:88px}
.composer-foot{display:flex;align-items:center;gap:14px;margin-top:12px}
.composer-foot .primary{margin-left:auto;min-width:130px}
.refs{display:flex;gap:8px;flex-wrap:wrap}
.ref{display:flex;align-items:center;gap:8px}
.filebtn{position:relative;overflow:hidden;display:inline-flex;align-items:center;
  background:var(--panel-2);border:1px dashed var(--line);border-radius:7px;
  padding:7px 13px;font-size:12px;color:var(--muted);cursor:pointer;
  text-transform:uppercase;letter-spacing:.05em}
.filebtn:hover{border-color:var(--gold);color:var(--text)}
.filebtn input{position:absolute;inset:0;opacity:0;cursor:pointer}
.controls{display:flex;flex-direction:column;gap:10px}
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:8px}

/* results -------------------------------------------------------------- */
.results-head{display:flex;align-items:center;gap:10px;margin-bottom:12px}
/* margin-left:auto on EVERY button distributes the slack between them, which
   is what left Select stranded mid-row. Push once, from the filters. */
.results-head .lib-filters{margin-right:auto}
.tile a{display:block;text-decoration:none}
.badge.soon{color:var(--warn)}
.tile{position:relative}
.tile-open{display:block;width:100%;padding:0;border:0;background:none;cursor:zoom-in;position:relative}
.tile-open img{width:100%;aspect-ratio:1;object-fit:cover;display:block;
  background:var(--panel-2);transition:filter .15s}
.tile-open:hover img{filter:brightness(1.06)}
.tile-zoom{position:absolute;right:8px;bottom:8px;width:26px;height:26px;
  display:grid;place-items:center;border-radius:6px;background:rgba(0,0,0,.55);
  color:#fff;font-size:14px;opacity:0;transition:opacity .15s}
.tile-open:hover .tile-zoom{opacity:1}
/* Dismiss control. `place-self` is the load-bearing bit: .tile.pending is a
   centring grid, and without it the grid's place-items:center overrides the
   top/right insets and parks the button in the middle. */
.tile-del{position:absolute;top:6px;right:6px;place-self:start end;z-index:3;
  width:24px;height:24px;border:1px solid var(--line);border-radius:7px;
  background:rgba(12,12,14,.82);color:var(--muted);font-size:12px;
  cursor:pointer;display:grid;place-items:center;line-height:1;padding:0;
  transition:background .15s,color .15s,border-color .15s}
.tile-del:hover{background:var(--err);color:#fff;border-color:var(--err)}
.tile-pending-body{width:100%}
.tile-err{margin-top:6px;color:var(--muted);font-size:12px;line-height:1.4}

/* image viewer / lightbox */
.viewer{position:fixed;inset:0;z-index:80;background:rgba(6,6,8,.96);
  display:flex;flex-direction:column;backdrop-filter:blur(4px)}
.viewer-bar{display:flex;align-items:center;gap:12px;padding:12px 16px;
  border-bottom:1px solid var(--line);flex:none}
.viewer-title{font-weight:600}
.viewer-count{margin-left:2px}
.viewer-spacer{flex:1}
.viewer-bar a.ghost{text-decoration:none;display:inline-flex;align-items:center}
/* Download is what people opened the image to do, so it carries the brand
   colour and the format sits next to it as one control. */
.viewer-dlgroup{display:inline-flex;align-items:stretch;gap:0}
.viewer-fmt{width:auto;padding:5px 8px;font-size:12px;border-radius:8px 0 0 8px;
  border-right:0;background:var(--panel-2);color:var(--muted)}
.viewer-dl{border-radius:0 8px 8px 0;padding:5px 13px;cursor:pointer;
  background:rgba(var(--gold-rgb),.14);color:var(--gold-lt);
  border:1px solid rgba(var(--gold-rgb),.5)}
.viewer-dl:hover:not(:disabled){background:rgba(var(--gold-rgb),.24);
  border-color:var(--gold)}
.viewer-dl:disabled{opacity:.6;cursor:default}

/* transient confirmation, bottom-centre, never blocks anything */
.toast{position:fixed;left:50%;bottom:26px;transform:translate(-50%,12px);
  background:var(--panel);border:1px solid var(--line);color:var(--text);
  padding:10px 16px;border-radius:10px;font-size:13px;z-index:120;
  box-shadow:0 8px 26px rgba(0,0,0,.45);opacity:0;pointer-events:none;
  transition:opacity .16s ease,transform .16s ease;max-width:min(90vw,460px)}
.toast.on{opacity:1;transform:translate(-50%,0)}
.toast.on.has-act{pointer-events:auto}
.toast-act{margin-left:10px;background:none;border:1px solid rgba(var(--gold-rgb),.55);
  color:var(--gold);border-radius:7px;padding:3px 10px;font-size:12px;cursor:pointer}
.toast-act:hover{background:rgba(var(--gold-rgb),.14)}
.trash-note{grid-column:1/-1;display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin:2px 0 8px}
.trash-cell{cursor:default}
.trash-foot{display:flex;align-items:center;gap:6px;padding:6px 2px;flex-wrap:wrap}
.viewer-del{color:var(--err) !important;border-color:rgba(224,104,95,.4) !important}
/* overflow:hidden, not auto - panning is a transform now. With overflow:auto
   the centred image could not be scrolled back above its start edge, so the
   top of a zoomed picture was permanently unreachable. */
.viewer-stage{flex:1;display:flex;align-items:center;justify-content:center;
  overflow:hidden;padding:20px 64px;position:relative;touch-action:none}
/* No zoom-in cursor: clicking the picture does nothing. A lens cursor promises
   a click-to-zoom that no longer exists. */
.viewer-stage video{max-width:100%;max-height:100%;object-fit:contain;display:block}
.viewer-stage img{max-width:100%;max-height:100%;object-fit:contain;
  border-radius:8px;box-shadow:0 20px 60px -10px #000;cursor:default;
  transform-origin:center center;will-change:transform;user-select:none;
  -webkit-user-drag:none}
.viewer-stage img.pannable{cursor:grab;border-radius:0}
.viewer-stage img.pannable.dragging{cursor:grabbing}
.viewer-nav{position:absolute;top:50%;transform:translateY(-50%);z-index:2;
  width:44px;height:64px;border:0;border-radius:10px;background:rgba(20,20,24,.7);
  color:var(--text);font-size:28px;cursor:pointer;display:grid;place-items:center}
.viewer-nav:hover{background:rgba(40,40,48,.9)}
.viewer-nav.prev{left:12px}
.viewer-nav.next{right:12px}
.bar{margin-top:10px;height:5px;border-radius:99px;background:var(--panel-2);
  overflow:hidden;width:100%}
.bar span{display:block;height:100%;width:0;border-radius:99px;
  background:linear-gradient(90deg,var(--gold-dk),var(--gold-lt));
  transition:width .4s ease}
/* Spacing that used to travel as style="" in generated markup. Our CSP is
   style-src 'self' with no unsafe-inline, so the browser dropped every one of
   them - the margins simply never applied. */
.tile-tool{margin-top:8px}
.tile-pct{margin-top:5px}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(190px,1fr));gap:12px}
.tile{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);
  overflow:hidden}
.tile img{width:100%;aspect-ratio:1;object-fit:cover;display:block;background:var(--panel-2)}
.tile .meta{padding:8px 10px;font-size:11.5px;color:var(--muted);
  display:flex;justify-content:space-between;gap:8px;align-items:center}
.tile.pending{display:grid;place-items:center;aspect-ratio:1;text-align:center;padding:14px}
.tile.failed{border-color:rgba(224,104,95,.35)}
.badge{font-size:10.5px;padding:2px 8px;border-radius:99px;background:var(--panel-2);
  text-transform:uppercase;letter-spacing:.06em}
.badge.done{color:var(--ok)}
.badge.failed,.badge.blocked{color:var(--err)}
.badge.running,.badge.queued{color:var(--warn)}

/* lists ---------------------------------------------------------------- */
.card-flat{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);
  padding:18px;display:flex;flex-direction:column;gap:12px;max-width:560px;margin-top:14px}
.item{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);
  padding:13px 15px;margin-top:10px;max-width:760px}
.item .t{font-weight:600;font-size:14px}
.vote{display:flex;align-items:center;gap:14px}
.vote button{background:var(--panel-2);color:var(--gold-lt);padding:7px 13px;
  border:1px solid var(--line)}
  text-transform:uppercase;letter-spacing:.1em;color:var(--gold);
  border-bottom:1px solid var(--line)}

/* account */
.acct-grid{display:grid;grid-template-columns:1fr 1.4fr;gap:16px;align-items:start}
.acct-grid .card-flat{max-width:none;margin-top:0}
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(110px,1fr));gap:10px}
.stat{background:var(--panel-2);border:1px solid var(--line);border-radius:9px;
  padding:11px 12px}
.stat-v{font-size:19px;font-weight:600;letter-spacing:-.01em}
.stat-l{font-size:11px;color:var(--muted);text-transform:uppercase;
  letter-spacing:.06em;margin-top:3px}
.stat-n{font-size:10.5px;color:#6d6a64;margin-top:3px}
.wf-row{display:grid;grid-template-columns:1.6fr .7fr .8fr .7fr;gap:8px;
  padding:7px 0;border-bottom:1px solid var(--line)}
.wf-row:last-child{border-bottom:0}
.attach{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
@media(max-width:900px){.acct-grid{grid-template-columns:1fr}}

@media(max-width:1050px){
  .composer-body{grid-template-columns:1fr}
  .shell{grid-template-columns:1fr}
  .sidebar{display:none}
}

/* create-a-character ---------------------------------------------------- */
/* CTA bar + button — moved off inline style= (prod CSP style-src 'self' blocks
   inline styles, so the button rendered default-sized in production). */
.cc-cta{margin-top:24px;padding-top:20px;border-top:1px solid var(--line)}
#cc-create{width:100%;padding:15px;font-size:16px;font-weight:700}
.cc-traits{display:grid;gap:10px;margin-top:10px;
  grid-template-columns:repeat(auto-fit,minmax(170px,1fr))}
.cc-field{display:flex;flex-direction:column;gap:4px}
.cc-wide{grid-column:1/-1}
.cc-label{font-size:11.5px;color:var(--muted)}
.cc-label em{font-style:normal;opacity:.7}
.cc-field select{background:var(--panel-2);border:1px solid var(--line);
  color:var(--text);border-radius:8px;padding:8px 10px;font:inherit;font-size:13.5px}
.cc-chips{display:flex;flex-wrap:wrap;gap:6px}
.cc-chip{background:var(--panel-2);border:1px solid var(--line);color:var(--muted);
  border-radius:99px;padding:5px 11px;font:inherit;font-size:12px;cursor:pointer;
  transition:all .15s}
.cc-chip:hover{color:var(--text);border-color:#3a3a41}
.cc-chip.on{background:linear-gradient(180deg,var(--gold-lt),var(--gold));
  color:var(--on-gold);border-color:transparent}
.cc-stage{margin-top:16px;border-top:1px solid var(--line);padding-top:16px}
.cc-preview{display:flex;gap:16px;align-items:flex-start;flex-wrap:wrap}
.cc-imgwrap{position:relative;width:260px;aspect-ratio:4/5;flex:none;
  background:var(--panel-2);border:1px solid var(--line);border-radius:var(--r);
  overflow:hidden;display:grid;place-items:center}
.cc-imgwrap img{width:100%;height:100%;object-fit:cover;display:block}
.cc-spin{color:var(--muted);font-size:13px}
.cc-actions{flex:1;min-width:220px;display:flex;flex-direction:column;gap:10px}
/* the training-set window: a feed-like grid of candidate cards */
.cc-dataset{margin-top:16px;border-top:1px solid var(--line);padding-top:16px}
.cc-ds-grid{display:grid;gap:10px;margin-top:14px;
  grid-template-columns:repeat(auto-fill,minmax(160px,1fr))}
.cc-ds-card{position:relative;border-radius:10px;overflow:hidden;
  background:var(--panel-2);border:1px solid var(--line);aspect-ratio:3/4}
.cc-ds-card img{width:100%;height:100%;object-fit:cover;display:block}
.cc-ds-card.kept{border-color:var(--gold)}
.cc-ds-badge{position:absolute;top:6px;left:6px;background:var(--gold);
  color:var(--on-gold);font-size:11px;font-weight:700;padding:3px 8px;border-radius:99px}
.cc-ds-acts{position:absolute;left:0;right:0;bottom:0;display:flex;gap:6px;
  padding:8px;background:linear-gradient(transparent,rgba(0,0,0,.8))}
.cc-ds-acts button{flex:1;min-width:0;padding:5px 4px;font-size:10.5px;
  white-space:nowrap;border-radius:6px}
/* intent tag: was this shot meant to be clothed or nude */
.cc-ds-tag{position:absolute;top:6px;right:6px;font-size:9.5px;font-weight:700;
  padding:2px 6px;border-radius:99px;letter-spacing:.03em;z-index:1}
.cc-ds-tag.sfw{background:rgba(46,230,166,.16);color:#2ee6a6;
  border:1px solid rgba(46,230,166,.45)}
.cc-ds-tag.nsfw{background:rgba(224,104,95,.2);color:#ff8f84;
  border:1px solid rgba(224,104,95,.55)}
/* full-size viewer navigation chevrons */
.cc-lightbox .lb-nav{position:absolute;top:50%;transform:translateY(-50%);
  width:46px;height:72px;border:none;background:rgba(0,0,0,.45);color:#fff;
  font-size:34px;line-height:1;cursor:pointer;border-radius:10px;z-index:1}
.cc-lightbox .lb-prev{left:14px}
.cc-lightbox .lb-next{right:14px}
.cc-lightbox .lb-nav:hover{background:rgba(0,0,0,.78)}
.cc-ds-foot{display:flex;align-items:center;justify-content:space-between;
  gap:12px;margin-top:16px;padding-top:16px;border-top:1px solid var(--line)}
/* The other site's version on the same press (two front doors): its own line
   across the row, never squeezed beside the button. */
.cc-ds-foot{flex-wrap:wrap}
label.cc-also{display:flex;flex-direction:row;align-items:flex-start;gap:8px;flex:1 1 100%;
  order:5;margin:0;font-size:13px;line-height:1.4;color:var(--text);text-transform:none;
  letter-spacing:normal;font-weight:400;cursor:pointer}
label.cc-also input{margin-top:2px;flex:none}
#cc-also-note{flex:1 1 100%;order:5}
label.cc-also.hidden,#cc-also-note.hidden{display:none}
.cc-ds-img{cursor:zoom-in}
.cc-ds-wait{display:grid;place-items:center}
.cc-ds-wait .cc-spin{font-size:12px}
/* full-size look at one candidate; click anywhere to close */
.cc-lightbox{position:fixed;inset:0;z-index:80;display:grid;place-items:center;
  padding:24px;background:rgba(0,0,0,.9);cursor:zoom-out}
.cc-lightbox.hidden{display:none}
.cc-lightbox img{max-width:96vw;max-height:92vh;border-radius:10px;
  object-fit:contain}

/* role badge ------------------------------------------------------------ */
.role-pill{margin-left:auto;flex:none;font-size:9.5px;font-weight:700;
  letter-spacing:.08em;padding:2px 7px;border-radius:99px;line-height:1.4}
/* purple = dev, gold = owner. Gold is the brand colour, so the dev badge is
   deliberately OFF-brand: it should read as a mode you are in, not a rank. */
.role-dev{background:rgba(150,110,230,.18);color:#b79cf0;
  border:1px solid rgba(150,110,230,.45)}
.role-owner{background:rgba(var(--gold-rgb),.15);color:var(--gold-lt);
  border:1px solid rgba(var(--gold-rgb),.45)}
.f-tools{display:flex;gap:6px;align-items:center;flex:none}
.item.f-done{opacity:.55}
.item.f-done .t{text-decoration:line-through}
.badge.done{color:var(--ok)}
.badge.planned{color:var(--warn)}

/* The Account row is icon + label + role pill. The label span carries flex:1
   from the generic .sidebar a rule, so it fills the gap and its text sat hard
   left against the icon while the pill floated right - reading as two
   unrelated things rather than one row. Centre the label in that gap. */


/* Account row: icon left, label CENTRED, role pill right.
   flex cannot centre the middle item when the flankers differ in width - the
   label only ever centres inside its own leftover space, which is why two
   text-align attempts looked wrong. A 1fr/auto/1fr grid centres the middle
   column against the ROW, whatever the icon and pill measure. */
.sidebar a.acct-row{display:grid;grid-template-columns:1fr auto 1fr;
  align-items:center;gap:9px}
.sidebar a.acct-row .row-icon{justify-self:start}
.sidebar a.acct-row .role-pill{justify-self:end;margin-left:0;flex:none}
.sidebar a.acct-row > span:not(.row-icon):not(.role-pill){flex:none;
  text-align:center;white-space:nowrap}

/* dev-only role preview */
.viewas{display:flex;align-items:center;gap:7px;padding:4px 10px 2px;
  font-size:10.5px;color:var(--muted);text-transform:uppercase;
  letter-spacing:.07em}
.viewas select{flex:1;min-width:0;background:var(--panel-2);
  border:1px solid var(--line);color:var(--text);border-radius:6px;
  padding:3px 6px;font:inherit;font-size:11px;text-transform:capitalize}
.role-standard{background:rgba(139,134,126,.15);color:var(--muted);
  border:1px solid rgba(139,134,126,.4)}

/* review queue (dev only) ----------------------------------------------- */
/* Thumbnails are blurred until the reviewer asks for them. This queue holds
   images the age filter thinks may show a minor - they must never appear
   just because someone clicked the wrong row in the rail. */
.sidebar a.has-pending .tag{color:var(--warn);border-color:rgba(224,160,60,.5)}
.review-tools{display:flex;align-items:center;gap:16px;margin:14px 0 4px;max-width:760px}
.review-tools button{margin-left:auto}
.rv-h3{margin-top:30px;font-size:14px;color:var(--muted);font-weight:600}
.rv-item{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);
  padding:13px 15px;margin-top:10px;max-width:760px;
  display:flex;align-items:center;gap:14px}
.rv-item.done{opacity:.6}
/* The thumbnail is the way in to the full-size viewer, so it has to read as a
   control. Blurred until asked for, but always openable - you cannot judge a
   face at 110px and the whole queue is pointless if the reviewer can't look. */
.rv-thumb{flex:0 0 110px;height:110px;border-radius:8px;overflow:hidden;
  background:var(--panel-2);position:relative;padding:0;border:1px solid var(--line);
  cursor:zoom-in;display:block}
.rv-thumb:hover{border-color:var(--gold)}
.rv-thumb img{width:100%;height:100%;object-fit:cover;display:block;
  filter:blur(16px);transform:scale(1.15);transition:filter .12s}
#rv-list.revealed .rv-thumb img{filter:none;transform:none}
.rv-thumb-open{position:absolute;inset:auto 0 0 0;font-size:9.5px;
  text-align:center;padding:3px 0;letter-spacing:.07em;text-transform:uppercase;
  color:var(--gold-lt);background:rgba(0,0,0,.62)}
.rv-thumb:hover .rv-thumb-open{background:rgba(0,0,0,.8);color:var(--text)}
.rv-body{flex:1;min-width:0}
.rv-prompt{margin-top:5px;color:var(--muted);line-height:1.4;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.rv-acts{display:flex;gap:8px;flex-shrink:0;flex-wrap:wrap;justify-content:flex-end}
.rv-acts button.danger{color:var(--err);border-color:rgba(224,104,95,.35)}
.rv-verdict{font-size:10.5px;text-transform:uppercase;letter-spacing:.06em;
  padding:2px 8px;border-radius:99px;background:var(--panel-2);flex-shrink:0}
.rv-released{color:var(--ok)}
.rv-deleted{color:var(--err)}

/* what this will cost, beside Generate. Brought back 2026-09-13 when credits
   went live; removed 2026-08-15 because a price in a currency nobody was being
   charged in is noise. No inline styles: the prod CSP is style-src 'self'. */
.quote{display:flex;flex-direction:column;align-items:flex-end;gap:1px;
  margin-right:10px;line-height:1.25;min-width:0}
/* The edit workspace stacks its controls, so the tag sits ABOVE Apply
   and reads left-aligned with them rather than hanging off the right. */
.edit-quote{align-items:flex-start;margin:0 0 8px}
.quote-cost{font-size:12px;color:var(--text);font-variant-numeric:tabular-nums}
.quote-bal{font-size:10.5px;color:var(--muted);font-variant-numeric:tabular-nums}
.quote.short .quote-cost{color:var(--err)}
.quote.short .quote-bal{color:var(--err)}
/* The rail's running balance. Same number, always visible, so nobody has to
   open Account to find out where they stand. */
.bal-pill{display:flex;align-items:baseline;gap:6px;padding:6px 10px;margin:2px 0 8px;
  border:1px solid var(--line);border-radius:8px;background:var(--panel-2)}
.bal-pill b{font-size:12px;font-variant-numeric:tabular-nums}
.bal-pill span{font-size:10px;text-transform:uppercase;letter-spacing:.06em;
  color:var(--muted)}
.bal-pill.low b{color:var(--err)}
a.bal-pill{color:inherit;text-decoration:none}
a.bal-pill:hover{border-color:var(--gold)}
.err-topup{color:var(--gold);font-weight:600;white-space:nowrap}

/* Spacing utilities, because the production CSP is style-src 'self' and a
   style="" attribute in generated markup is DROPPED by the browser with no
   error. Seventeen of them were sitting in app.js innerHTML strings, so that
   spacing had never once been applied on the live site - and one comment in
   the file already knew, having worked out the same thing about a progress bar.
   Classes are the only way to style markup this app builds at runtime.

   CSSOM assignment (el.style.width = ...) is NOT inline style and stays legal;
   that is what the progress bar uses. */
.mt6{margin-top:6px}
.mt8{margin-top:8px}
.mt10{margin-top:10px}
.mt14{margin-top:14px}
.mb10{margin-bottom:10px}
.grow{flex:1}
.full-w{width:100%}
.centre-pad{display:flex;align-items:center;justify-content:center;padding:10px}

/* A heading or paragraph that opens a card, so its own top margin would push
   the card's padding open. Was nine inline style="margin-top:0" attributes,
   every one of them silently dropped by the production CSP (style-src 'self')
   and confirmed blocked in the browser console on 2026-09-14. The spacing had
   therefore been wrong on the live site for as long as the CSP has been on. */
.flush{margin-top:0}

/* credits (admin). No inline styles anywhere: the production CSP is
   style-src 'self', so a style= attribute is silently dropped. */
.cr-form input:disabled,.cr-form button:disabled{opacity:.45;cursor:not-allowed}
.cr-env{margin:0 0 14px;padding:9px 13px;border-radius:8px;font-size:12.5px;
  font-weight:600;letter-spacing:.01em}
.cr-env-dev{background:rgba(224,160,60,.12);border:1px solid rgba(224,160,60,.5);
  color:#e0a03c}
.cr-env-prod{background:rgba(90,180,120,.12);border:1px solid rgba(90,180,120,.45);
  color:var(--ok)}
.cr-accounts{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0 18px}
.cr-acct{display:flex;flex-direction:column;align-items:flex-start;gap:2px;
  padding:8px 12px;border:1px solid var(--line);border-radius:8px;
  background:var(--panel-2);cursor:pointer;text-align:left;color:inherit;
  font:inherit}
.cr-acct:hover{border-color:var(--gold)}
.cr-acct-em{font-size:12px}
.cr-acct-bal{font-size:11px;color:var(--muted)}
.cr-form{display:flex;gap:10px;align-items:flex-end;flex-wrap:wrap;
  margin-bottom:8px}
.cr-field{display:flex;flex-direction:column;gap:4px;flex:1;min-width:160px}
.cr-field span{font-size:10.5px;text-transform:uppercase;letter-spacing:.06em;
  color:var(--muted)}
.cr-field input{width:100%}
.cr-narrow{flex:0 0 120px;min-width:120px}
.cr-note{margin-top:6px;font-size:12px;color:var(--gold-lt);min-height:1.2em}
.cr-row{display:grid;grid-template-columns:72px 96px 1fr 80px;gap:10px;
  padding:7px 0;border-bottom:1px solid var(--line);font-size:12px;
  align-items:baseline}
.cr-delta{font-variant-numeric:tabular-nums}
.cr-pos{color:var(--ok)}
.cr-neg{color:var(--err)}
.cr-reason{color:var(--muted);text-transform:uppercase;font-size:10.5px;
  letter-spacing:.06em}
.cr-detail{color:var(--muted);overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap}
.cr-bal{text-align:right;font-variant-numeric:tabular-nums;color:var(--muted)}
@media (max-width:640px){
  .cr-row{grid-template-columns:64px 1fr 72px}
  .cr-reason{display:none}
}

/* the owner's side of the same event */
.tile.held{border-color:rgba(224,160,60,.3)}
.badge.held{color:var(--warn)}
.tile-held{margin-top:8px;color:var(--muted);font-size:11.5px;line-height:1.45}

/* roadmap (dev only) ----------------------------------------------------- */
.rm-filters{display:flex;gap:6px;flex-wrap:wrap}
.rm-f{background:var(--panel-2);color:var(--muted);border:1px solid var(--line);
  border-radius:99px;padding:4px 12px;font-size:12px}
.rm-f.on{color:var(--gold-lt);border-color:var(--gold)}
.rm-cat{margin:26px 0 6px;font-size:13px;color:var(--muted);font-weight:600;
  text-transform:uppercase;letter-spacing:.06em;display:flex;gap:8px;align-items:baseline}
.rm-item{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);
  padding:11px 14px;margin-top:8px;max-width:860px;display:flex;gap:12px;align-items:flex-start}
.rm-body{flex:1;min-width:0}
.rm-item .t{font-weight:600;font-size:14px;display:flex;align-items:center;gap:8px}
.rm-dot{width:8px;height:8px;border-radius:99px;background:var(--muted);
  margin-top:6px;flex-shrink:0}
.rm-todo .rm-dot{background:var(--warn)}
.rm-doing .rm-dot{background:var(--ok);animation:pulse 1.3s ease-in-out infinite}
.rm-done .rm-dot{background:var(--ok)}
.rm-blocked .rm-dot{background:var(--err)}
.rm-ruled-out .rm-dot,.rm-findings .rm-dot{background:#5f5c57}
.rm-done .t,.rm-ruled-out .t{opacity:.6}
.rm-done .t{text-decoration:line-through}
.rm-status{font-size:10px;text-transform:uppercase;letter-spacing:.06em;
  color:var(--muted);flex-shrink:0;padding-top:3px}
.rm-more{background:none;border:1px solid var(--line);color:var(--muted);
  border-radius:99px;padding:0 8px;font-size:10px;text-transform:uppercase;
  letter-spacing:.05em;line-height:17px}
.rm-more.on{color:var(--gold-lt);border-color:var(--gold)}
.rm-detail{margin-top:10px;border-top:1px solid var(--line);padding-top:10px}
.rm-detail-h{font-size:12px;color:var(--gold-lt);margin-bottom:6px}
.rm-detail pre{white-space:pre-wrap;word-break:break-word;font-size:12px;
  line-height:1.55;color:var(--muted);margin:0;max-height:420px;overflow:auto;
  font-family:inherit}
.rm-note.is-done{opacity:.55}
.rm-note.is-done .t{text-decoration:line-through}
.rm-note{display:flex;align-items:center;gap:12px}

/* legal links on the sign-in card */
.login-legal{margin:4px 0 0;line-height:1.5;font-size:11.5px}
.login-legal a{color:var(--gold-lt);text-decoration:none;border-bottom:1px solid rgba(var(--gold-rgb),.35)}
.login-legal a:hover{border-bottom-color:var(--gold)}

/* library filter bar ---------------------------------------------------- */
.lib-filters{display:flex;gap:6px;flex-wrap:wrap}
.lib-f{background:var(--panel-2);color:var(--muted);border:1px solid var(--line);
  border-radius:99px;padding:4px 12px;font-size:12px;font-weight:500}
.lib-f.on{color:var(--gold-lt);border-color:var(--gold)}

/* mobile: rail and panel both slide OVER the canvas instead of owning
   columns (owner's decision, 2026-08-06). The rail reuses its existing
   rail-closed class as the slide state; the panel gets panel-open. JS adds
   rail-closed on boot for small screens so neither starts covering the
   canvas. */
.panel-toggle{display:none}
@media (max-width:900px){
  #app{padding:8px;gap:8px}
  .sidebar{position:fixed;top:0;bottom:0;left:0;width:min(268px,80vw);z-index:45;
    border-radius:0;transform:translateX(-100%);transition:transform .2s ease;
    box-shadow:14px 0 40px -18px #000}
  #app:not(.rail-closed) .sidebar{transform:none}
  .panel{position:fixed;top:0;bottom:0;left:0;width:min(340px,88vw);z-index:44;
    border-radius:0;transform:translateX(-100%);transition:transform .2s ease;
    box-shadow:14px 0 40px -18px #000}
  #app.panel-open .panel{transform:none}
  .panel-toggle{display:grid}
}

/* pipeline picker in the composer (variant_of families) ------------------ */
#wf-variants{margin:0 0 12px}
#wf-variants select{width:100%;background:var(--panel-2);color:var(--text);
  border:1px solid var(--line);border-radius:8px;padding:8px 11px;
  font:inherit;font-size:13.5px}
#wf-variants select:focus{outline:none;border-color:var(--gold)}

/* polish pass (2026-08-06): the detached-surface look ------------------- */
/* every floating card carries the same soft depth */
.sidebar,.panel,.topbar{box-shadow:var(--shadow)}

/* quiet, thin scrollbars - the stock ones read as "table" instantly */
*{scrollbar-width:thin;scrollbar-color:#2c2c33 transparent}
::-webkit-scrollbar{width:9px;height:9px}
::-webkit-scrollbar-thumb{background:#2c2c33;border-radius:99px;
  border:2px solid var(--bg)}
::-webkit-scrollbar-track{background:transparent}

/* rail rows: chunkier now that there are few of them */
.sidebar a{padding:9px 12px;border-radius:9px;font-size:14px}
.sidebar a.active{background:linear-gradient(90deg,rgba(var(--gold-rgb),.18),rgba(var(--gold-rgb),.03));
  box-shadow:inset 2px 0 0 var(--gold)}
.sidebar .solo-row{margin:1px 0}

/* library tiles: rounded, and they answer the cursor */
.grid{gap:14px}
.tile-open img{border-radius:12px;border:1px solid var(--line);
  transition:border-color .15s,transform .15s}
.tile:hover .tile-open img{border-color:rgba(var(--gold-rgb),.45)}
.tile.pending{border:1px solid var(--line);border-radius:12px;
  background:var(--panel)}
.tile .meta{padding:7px 3px 0}

/* controls soften to match */
button{border-radius:10px}
.composer textarea{min-height:110px}
input,textarea,#wf-variants select{border-radius:9px}

/* the picker reads as the tool switcher it now is, not a form field */
.pick-lbl{display:block;font-size:11px;font-weight:600;color:var(--muted);
  text-transform:uppercase;letter-spacing:.08em;margin:0 0 5px}
#wf-variants{margin:0 0 14px}
#wf-variants select{font-weight:600;font-size:14px;padding:9px 11px;
  cursor:pointer}

/* bulk selection --------------------------------------------------------- */
.selbar{display:flex;align-items:center;gap:8px;margin:0 0 12px;padding:9px 12px;
  background:var(--panel);border:1px solid var(--gold-dk);border-radius:10px}
.selbar-spacer{flex:1}
.selbar #sel-count{font-size:12.5px;color:var(--gold-lt);font-weight:600}
.selbar button.danger{color:var(--err);border-color:rgba(224,104,95,.35)}
.selbar button:disabled{opacity:.35;cursor:not-allowed}
.grid.selecting .tile-open{cursor:pointer}
.tile-check{position:absolute;top:8px;left:8px;width:20px;height:20px;
  border-radius:6px;border:1.5px solid rgba(255,255,255,.55);
  background:rgba(0,0,0,.42);display:none}
.grid.selecting .tile-check{display:block}
.tile.picked .tile-check{background:var(--gold);border-color:var(--gold-lt)}
.tile.picked .tile-check::after{content:"";position:absolute;left:6px;top:2px;
  width:5px;height:10px;border:solid var(--on-gold);border-width:0 2px 2px 0;
  transform:rotate(45deg)}
.tile.picked .tile-open img{border-color:var(--gold);
  outline:2px solid var(--gold);outline-offset:-2px}

/* viewer details panel (prompt + settings, Freepik/TJP pattern) ---------- */
.viewer-body{flex:1;min-height:0;display:flex}
.viewer-info{width:310px;flex:none;overflow:auto;padding:18px 18px 26px;
  border-left:1px solid var(--line);background:var(--panel)}
.viewer-info.hidden{display:none}
.vi-tool{font-size:15px;font-weight:600}
.vi-when{margin-top:2px}
/* Filename shown in the viewer's details panel, so the image on screen and
   the file in Downloads are obviously the same one. Breaks anywhere because
   the panel is narrow on a phone. */
.vi-file{margin-top:2px;word-break:break-all}
.mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px}
/* Seed: mode select drives a value box. Styled to match
   .char-row select exactly - it sits two fields below it in the same panel,
   so anything else reads as a bug. */
.seed-row{display:flex;gap:8px;align-items:center}
.seed-row select,.seed-row input{background:var(--panel-2);
  border:1px solid var(--line);color:var(--text);border-radius:8px;
  padding:8px 10px;font:inherit;font-size:14px}
.seed-row select{flex:0 0 auto}
.seed-row input{flex:1 1 auto;min-width:0}
.seed-row input:disabled{opacity:.45}

.vi-sec{margin-top:20px}
.vi-h{display:flex;align-items:center;gap:8px;font-size:11px;font-weight:600;
  color:var(--muted);text-transform:uppercase;letter-spacing:.08em;
  margin-bottom:7px}
.vi-copy{margin-left:auto;background:none;border:1px solid var(--line);
  color:var(--muted);border-radius:99px;padding:2px 9px;font-size:10px;
  text-transform:uppercase;letter-spacing:.05em}
.vi-copy:hover{color:var(--gold-lt);border-color:var(--gold)}
.vi-prompt{margin:0;font-size:13.5px;line-height:1.6;color:var(--text);
  white-space:pre-wrap;word-break:break-word}
.vi-neg{color:var(--muted);font-size:12.5px}
.vi-chips{display:flex;flex-wrap:wrap;gap:6px}
.vi-chip{font-size:11.5px;color:var(--muted);background:var(--panel-2);
  border:1px solid var(--line);border-radius:99px;padding:3px 10px}
/* Square (1:1) preview tiles in a grid that wraps to a new row as more
   references are used. padding-bottom:100% keeps each preview square whatever
   the source aspect (the aspect-ratio prop is unreliable inside the panel). */
.vi-refs{display:grid;grid-template-columns:repeat(auto-fill,minmax(84px,1fr));gap:10px}
.vi-ref{display:block;min-width:0}
.vi-ref-img{display:block;position:relative;height:0;padding-bottom:100%;
  border-radius:10px;overflow:hidden;background:var(--panel-2);border:1px solid var(--line)}
.vi-ref-img img,.vi-ref-img .vi-ref-dot{position:absolute;inset:0;width:100%;
  height:100%;object-fit:cover;display:block}
.vi-ref-img .vi-ref-dot{background:var(--line)}
.vi-ref-t{margin-top:5px;font-size:11px;color:var(--muted);text-align:center;
  line-height:1.25;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.vi-ref-t b{display:block;font-weight:600;color:var(--text,inherit)}
@media (max-width:900px){
  .viewer-body{flex-direction:column}
  .viewer-info{width:auto;max-height:42vh;border-left:0;
    border-top:1px solid var(--line)}
}
/* the stage is now a flex child of .viewer-body, so it needs an explicit
   min-width or a wide image pushes the details panel off-screen */
.viewer-stage{min-width:0}

/* Feed size picker. `button.ghost` cannot style a <select>, so it needs the
   same treatment spelled out or it renders as a raw OS dropdown next to
   two styled buttons. */
/* Every select that sits in a results header, not just the library's own -
   the edit picker's source list is the same control doing the same job, and
   naming one id left it rendering as a raw white OS dropdown. */
#feed-limit,#feed-by,.results-head select{background:transparent;color:var(--muted);
  border:1px solid var(--line);border-radius:8px;padding:9px 10px;
  font:inherit;font-size:12.5px;font-weight:600;cursor:pointer}
#feed-limit:hover,#feed-by:hover,.results-head select:hover{color:var(--text);
  border-color:#3a3a41}
/* The dropdown ITSELF is drawn by the OS and inherits nothing, so its options
   need the colours spelled out or the list opens white on a black page. */
#feed-limit option,#feed-by option,.results-head select option{background:var(--panel-2);
  color:var(--text)}

/* One job = one card. Multiple frames tile inside it; the grid is square so a
   4-frame generation reads as a single result with four options, not as four
   separate generations. */
.tile .mosaic { display: grid; gap: 3px; width: 100%; aspect-ratio: 1 / 1;
                border-radius: inherit; overflow: hidden; }
.tile .mosaic.n1 { grid-template-columns: 1fr; }
.tile .mosaic.n2 { grid-template-columns: 1fr 1fr; }
.tile .mosaic.n4 { grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; }
/* Three images in a 2x2 grid leaves an empty quarter, which reads as a broken
   card rather than a card with three pictures. Give three its own shape: one
   tall on the left, two stacked on the right, so the card is always full
   however many images survive a delete. */
.tile .mosaic.n3 { grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; }
.tile .mosaic.n3 > *:first-child { grid-row: span 2; }
.tile .mosaic .cell { position: relative; padding: 0; border: 0; cursor: pointer;
                      background: var(--tile-bg, #111); overflow: hidden; }
.tile .mosaic .cell img { width: 100%; height: 100%; object-fit: cover; display: block; }
.tile .mosaic .cell video { width: 100%; height: 100%; object-fit: cover; display: block; }
.tile .mosaic .cell.more { display: flex; flex-direction: column; align-items: center;
                           justify-content: center; gap: 2px;
                           border: 1px dashed var(--line, #3a3a3a); }
.tile .mosaic .cell.more .more-n { font-size: 1.4rem; font-weight: 600;
                                    color: var(--gold-lt); }
.tile .mosaic .cell.more .more-l { font-size: .75rem; opacity: .85;
                                    color: var(--gold); }
.tile .mosaic .cell.picked { outline: 3px solid var(--gold); outline-offset: -3px; }

/* Quick actions, revealed on hover over a thumbnail. Hidden from touch
   devices via the hover media query - there is no hover on a phone, so they
   would either sit there permanently or be unreachable. */
.cell-acts{position:absolute;top:6px;right:6px;display:flex;gap:4px;opacity:0;
  transition:opacity .12s ease;pointer-events:none;z-index:2}
.cell-act{display:inline-flex;align-items:center;justify-content:center;
  width:26px;height:26px;border-radius:7px;color:#e8e8ea;cursor:pointer;
  background:rgba(18,18,20,.72);border:1px solid rgba(255,255,255,.14);
  backdrop-filter:blur(3px)}
.cell-act:hover{background:rgba(18,18,20,.92);border-color:var(--gold);
  color:var(--gold-lt)}
.cell-act[data-act="del"]:hover{border-color:var(--err);color:var(--err)}
/* A favourited image keeps its heart visible with the pointer nowhere near it -
   the state IS the information, and hiding it until hover would mean you can
   only see what you saved by hunting for it.

   Written as "hide the others while NOT hovering" rather than "show them on
   hover": the reverse lost on specificity, so hovering a favourited image
   showed only the heart and the download and delete buttons were unreachable
   without un-favouriting first. */
.cell-act.heart.on{color:var(--gold);border-color:var(--gold)}
@media (hover:hover){
  .tile .mosaic .cell:hover .cell-acts{opacity:1;pointer-events:auto}
  .cell .cell-acts:has(.heart.on){opacity:1;pointer-events:auto}
  .cell:not(:hover) .cell-acts:has(.heart.on) .cell-act:not(.heart){
    opacity:0;pointer-events:none}
}

/* folders ---------------------------------------------------------------- */
.fold-grid{display:grid;gap:14px;
  grid-template-columns:repeat(auto-fill,minmax(170px,200px));
  justify-content:start;margin-top:16px}
.fold-card{display:flex;flex-direction:column;gap:2px;padding:0 0 10px;
  background:var(--panel);border:1px solid var(--line);border-radius:var(--r);
  overflow:hidden;cursor:pointer;text-align:left;color:var(--text)}
.fold-card:hover{border-color:var(--gold)}
.fold-cover{position:relative;display:block;aspect-ratio:1;background:var(--panel-2);
  overflow:hidden}
.fold-cover img{width:100%;height:100%;object-fit:cover;display:block}
.fold-empty{position:absolute;inset:0;display:grid;place-items:center;color:#43434d}
.fold-empty svg{width:38px;height:38px}
.fold-name{padding:9px 11px 0;font-size:13.5px;font-weight:600}
.fold-n{padding:0 11px}
.fold-open-head{display:flex;align-items:center;gap:10px;margin:14px 0 12px}
.fold-open-head h3{margin:0}
.fold-open-head .spacer{flex:1}
/* No grid of its own: an open folder renders through the feed's renderer into
   #fold-images, which carries the feed's own .grid class. A folder that looked
   different from the library was a second thing to learn for no gain. */

/* The folder pickers wear the brand, like every other select in the app. They
   shipped as bare OS controls - grey, system arrow, wrong font - sitting next
   to gold-bordered buttons. The composer's rule is keyed to #tool-settings and
   could not reach them, so the look is a class both can share. */
.sel-brand{width:auto;max-width:180px;background:var(--panel-2);
  border:1px solid var(--line);color:var(--text);border-radius:8px;
  padding:6px 30px 6px 11px;font:inherit;font-size:12.5px;appearance:none;
  cursor:pointer;
  background-image:linear-gradient(45deg,transparent 50%,var(--gold) 50%),
                   linear-gradient(135deg,var(--gold) 50%,transparent 50%);
  background-position:calc(100% - 16px) calc(50% + 1px),
                      calc(100% - 11px) calc(50% + 1px);
  background-size:5px 5px,5px 5px;background-repeat:no-repeat}
.sel-brand:hover{border-color:#3a3a41}
.sel-brand:focus{outline:none;border-color:var(--gold)}
.sel-brand:disabled{opacity:.45;cursor:default}
.sel-brand option{background:var(--panel-2);color:var(--text)}
.tile.tile-group .meta .badge { font-variant-numeric: tabular-nums; }

/* A cell whose image has not arrived yet. One per pending frame, so the card
   shows what is coming instead of a single spinner for the whole job. Only the
   frame actually being worked on animates - the rest read as queued. */
.tile .mosaic .cell.waiting { display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 10px;
  background: var(--tile-bg, #111); cursor: default;
  border: 1px solid var(--line, #2a2a2a); }
.tile .mosaic .cell.waiting .cell-lbl { font-size: 11px; letter-spacing: .08em;
  text-transform: uppercase; color: var(--muted, #8a8a8a); }
.tile .mosaic .cell.waiting.active .cell-lbl { color: var(--gold-lt); }
.tile .mosaic .cell.waiting .cell-bar { width: 60%; height: 3px;
  background: var(--line, #2a2a2a); border-radius: 2px; overflow: hidden; }
.tile .mosaic .cell.waiting .cell-bar span { display: block; height: 100%;
  width: 30%; border-radius: 99px; opacity: .35;
  background: linear-gradient(90deg, var(--gold-dk), var(--gold-lt)); }
.tile .mosaic .cell.waiting.active .cell-bar span { opacity: 1;
  animation: cellslide 1.4s ease-in-out infinite; }
@keyframes cellslide {
  0%   { margin-left: -30%; }
  100% { margin-left: 100%; }
}
@media (prefers-reduced-motion: reduce) {
  .tile .mosaic .cell.waiting.active .cell-bar span { animation: none; width: 60%; }
}

/* Stop control on a running generation. Same corner affordance as the dismiss
   button on a failed one - it sits in its own layer rather than inside the
   mosaic grid, which is what pushed the old dismiss button into the middle. */
.tile-stop{position:absolute;top:6px;right:6px;z-index:3;width:26px;height:26px;
  border-radius:50%;border:1px solid var(--line);background:rgba(0,0,0,.62);
  color:var(--gold-lt);font-size:13px;line-height:1;cursor:pointer;padding:0}
.tile-stop:hover{background:rgba(0,0,0,.85);border-color:var(--gold)}
.tile{position:relative}

/* character card: a name, what it is for, and the actions. Everything else
   (trigger word, strength, "trained for") was detail the customer cannot act
   on and should not have to understand. */
.char-name{font-weight:600;font-size:15px;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap}
.char-use{margin-top:4px}
.char-name-in{width:100%}

/* The branded-select rule above is `#tool-settings select`, which outranks
   `.seed-row select` on specificity - so width:100% swallowed the seed row and
   pushed the number box out of sight. Restore the intended sizing with rules
   that can actually win. */
#tool-settings .seed-row select{width:auto;flex:0 0 auto;padding-right:34px}
#tool-settings .seed-row input{flex:1 1 auto;min-width:0}
#tool-settings .char-row select{width:auto;flex:1 1 auto;min-width:0}

/* The negative prompt gets NO rules of its own. It is the same kind of input as
   the positive one, so it must inherit the same `.composer textarea` sizing -
   an id-selector override here is exactly what made it permanently shorter no
   matter what min-height it was given. */
#p-negative{width:100%}

/* breathing room above the character list */
#char-grid{margin-top:18px}
.char-list-head{margin-top:26px}

/* Create-a-character modal. Inline expansion left no way back to the list. */
.cc-modal{position:fixed;inset:0;z-index:60;display:flex;
  justify-content:center;padding:40px 16px;overflow:auto;
  background:rgba(0,0,0,.72);backdrop-filter:blur(3px)}
/* margin:auto centres the dialog BOTH ways when it fits, so a short form no
   longer hugs the top with dead space dumped below the button. Safe for the
   tall training-set window too: the inner is capped at 100vh-80px and scrolls
   its OWN content, so it never grows past the flex container and centring can't
   clip its top (the classic centred-modal-overflow trap). Measured: short form
   centres 68/68; tall window top stays reachable. */
.cc-modal-inner{position:relative;width:min(560px,100%);margin:auto;
  max-height:calc(100vh - 80px);overflow:auto}
/* the training-set window wants room for a big grid of cards. Must override
   .card-flat's max-width:560px (which capped width:1180px before). */
.cc-modal-inner.ds-wide{width:min(1180px,96vw);max-width:min(1180px,96vw)}
.cc-close{position:absolute;top:10px;right:10px;width:28px;height:28px;
  border-radius:50%;border:1px solid var(--line);background:var(--panel-2);
  color:var(--gold-lt);cursor:pointer;font-size:14px;line-height:1;padding:0}
.cc-close:hover{border-color:var(--gold)}

/* character editor: cover + name, then the reference sets ---------------- */
.ce-top{display:flex;gap:16px;align-items:flex-start;margin:14px 0 6px}
.ce-cover{position:relative;flex:0 0 132px;width:132px;aspect-ratio:1;
  border-radius:12px;overflow:hidden;background:var(--panel-2);
  border:1px solid var(--line);cursor:pointer;display:block}
.ce-cover img{width:100%;height:100%;object-fit:cover;display:block}
.ce-cover:hover{border-color:var(--gold)}
.ce-fields{flex:1;min-width:0;display:flex;flex-direction:column;gap:9px;
  align-items:flex-start}
.ce-fields label{display:block;width:100%}
.ce-fields input{width:100%}
/* Not full width: renaming is a small, occasional act, and a gold bar across
   the dialog reads as the primary thing to do in here. It is not - adding
   images is. */
.ce-fields .primary{width:auto;padding:7px 16px}

/* One block per set. The heading carries the plain-English reason the set
   exists - "Outfits" alone does not say what happens to them. */
.ce-set{margin-top:20px;padding-top:16px;border-top:1px solid var(--line)}
.ce-set-head{display:flex;align-items:baseline;gap:9px;flex-wrap:wrap;
  margin-bottom:10px}
.ce-set-name{font-size:13.5px;font-weight:600}
.ce-set-n{margin-left:auto;font-variant-numeric:tabular-nums}
/* Capped rather than stretched: with 1fr a set of two produced two enormous
   squares, which made three images look like a gallery instead of a set. */
.ce-tiles{display:grid;gap:9px;justify-content:start;
  grid-template-columns:repeat(auto-fill,minmax(84px,96px))}
.ce-tile{position:relative;aspect-ratio:1;border-radius:10px;overflow:hidden;
  background:var(--panel-2);border:1px solid var(--line)}
.ce-tile img{width:100%;height:100%;object-fit:cover;display:block}
/* The remove control sits ON the tile and is always visible: these are small
   and there is no second way to reach one, so hiding it until hover would
   make removal a guess on a touchscreen. */
.ce-tile-x{position:absolute;top:4px;right:4px;width:20px;height:20px;
  border-radius:6px;border:1px solid rgba(255,255,255,.16);
  background:rgba(18,18,20,.78);color:#e8e8ea;font-size:10px;line-height:1;
  padding:0;cursor:pointer}
.ce-tile-x:hover{border-color:var(--err);color:var(--err)}
.ce-add{display:grid;place-items:center;cursor:pointer;border-style:dashed}
.ce-add:hover{border-color:var(--gold)}
.ce-add span{font-size:20px;color:var(--muted)}
.ce-add:hover span{color:var(--gold-lt)}
.ce-versions{display:flex;flex-direction:column;gap:7px}
.ce-ver{display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  padding:9px 11px;border:1px solid var(--line);border-radius:9px;
  background:var(--panel-2)}
.ce-ver.live{border-color:rgba(var(--gold-rgb),.42)}
.ce-ver-n{font-size:12.5px;font-weight:600}
.ce-ver .muted{flex:1;min-width:0}
.ce-ver-live{font-size:10px;font-weight:700;letter-spacing:.06em;
  text-transform:uppercase;color:var(--gold-lt);border:1px solid var(--gold);
  border-radius:99px;padding:2px 9px}
.char-files{margin-top:2px}

.ce-foot{display:flex;align-items:center;gap:12px;margin-top:22px;
  padding-top:16px;border-top:1px solid var(--line)}
.ce-foot .primary{width:auto;padding:9px 20px;margin-left:auto}

/* sub-folders and the breadcrumb ----------------------------------------- */
.fold-subs{grid-column:1/-1;display:grid;gap:12px;margin-bottom:6px;
  grid-template-columns:repeat(auto-fill,minmax(150px,180px));
  justify-content:start}
.fold-card.sub .fold-cover{aspect-ratio:4/3}
.fold-plus{font-size:26px;color:var(--muted)}
.fold-new:hover .fold-plus{color:var(--gold-lt)}
.crumb{background:none;border:0;padding:0;color:var(--muted);cursor:pointer;
  font:inherit;font-size:inherit}
.crumb:hover{color:var(--gold-lt)}
.crumb-sep{color:var(--dim);margin:0 7px}

/* the add tile inside Uploads, sized like a feed card so the grid stays even */
.up-add-tile .up-add{display:grid;place-items:center;aspect-ratio:1;
  border:1px dashed var(--line);border-radius:12px;cursor:pointer;
  text-align:center;color:var(--muted);font-size:22px;line-height:1.2}
.up-add-tile .up-add:hover{border-color:var(--gold);color:var(--gold-lt)}

/* tabs across the top of a picker: Uploads, then each folder */
.gp-tabs{display:flex;gap:7px;flex-wrap:wrap;margin:12px 0 2px}
.gp-tabs.hidden{display:none}
.gp-tab{background:var(--panel-2);border:1px solid var(--line);color:var(--muted);
  border-radius:99px;padding:5px 12px;font-size:12px;cursor:pointer}
.gp-tab:hover{border-color:#3a3a41;color:var(--text)}
.gp-tab.on{border-color:var(--gold);color:var(--gold-lt)}
.rp-none{display:grid;place-items:center}
.rp-none-l{font-size:12px;color:var(--muted)}

/* reference picker: the uploads folder, as a chooser ---------------------- */
.rp-grid{display:grid;gap:10px;margin-top:14px;
  grid-template-columns:repeat(auto-fill,minmax(96px,1fr))}
.rp-tile{position:relative;aspect-ratio:1;padding:0;border-radius:10px;
  overflow:hidden;background:var(--panel-2);border:1px solid var(--line);
  cursor:pointer;display:block}
.rp-tile img{width:100%;height:100%;object-fit:cover;display:block}
.rp-tile:hover{border-color:var(--gold)}
/* Upload sits FIRST and is dashed, so the way to add is the obvious thing in
   an empty folder rather than something to hunt for once the grid fills. */
.rp-add{display:grid;place-items:center;border-style:dashed;text-align:center;
  color:var(--muted);font-size:19px;line-height:1.25}
.rp-add:hover{color:var(--gold-lt)}
.rp-add-l{font-size:11px;letter-spacing:.04em}
.char-sets{margin-top:3px}

/* Build line: which release channel, and which exact build is on screen. */
.build-line{display:flex;align-items:center;gap:7px;padding:8px 12px 2px;
  font-size:11px;letter-spacing:.04em}
.build-ch{text-transform:uppercase;font-weight:700;padding:1px 6px;
  border-radius:5px;border:1px solid var(--line)}
.build-dev{color:#c9a6ff;border-color:#5a3f8a}
.build-owner{color:var(--gold-lt);border-color:var(--gold-dk)}
.build-standard{color:var(--muted)}
.build-id{color:var(--muted);font-family:ui-monospace,Menlo,Consolas,monospace;
  cursor:pointer}
.build-id:hover{color:var(--gold-lt);text-decoration:underline}

/* --- pinned chrome ----------------------------------------------------- */
/* The brand stays put while the tool list scrolls under it, and the account
   block stays reachable at the bottom. Only the middle moves. */
/* The sidebar is ALREADY the scroller (overflow:auto above). Sticky children
   pin inside it; adding a second scroller on #nav gave that element no
   resolvable height and clipped the list instead of scrolling it. */
.sidebar .rail-brand{position:sticky;top:0;z-index:5;background:var(--panel);
  padding-top:12px}
#nav{flex:1 0 auto}
/* The bottom gap lives INSIDE the footer, like the brand row's top gap: as
   the sidebar's own padding it was a 12 px strip under the pinned footer
   where the scrolling menu showed through (found 2026-10-11). */
.sidebar{padding-bottom:0}
.side-foot{position:sticky;bottom:0;z-index:5;background:var(--panel);
  border-top:1px solid var(--line);margin-top:auto;padding-bottom:12px}
.build-line{justify-content:center}

/* The library controls stay on screen, so "Select" then scrolling still
   leaves Delete reachable without scrolling back up. */
/* One sticky strip, not two floating boxes. The container carries an OPAQUE
   page-coloured background so nothing shows through the gaps between and
   around the boxes as the feed scrolls under it; the boxes inside keep the
   panel look. Hardcoded per-element `top` offsets are gone too - they would
   have overlapped as soon as the header wrapped on a narrow window. */
/* main has 14px top / 6px side padding and IS the scroller. A sticky child
   pins below that padding, so the feed scrolled through the band above it.
   Negative margins pull the strip out to the container's padding edge and the
   matching padding puts the content back - so its background covers the gap. */
.feed-sticky{position:sticky;top:0;z-index:8;background:var(--bg);
  margin:0 -6px 8px;padding:0 6px 0}
.feed-sticky .results-head,
.feed-sticky .selbar{position:static;background:var(--panel);
  border:1px solid var(--line);border-radius:var(--r);padding:7px 12px;
  margin:0 0 8px;font-size:12.5px}
/* The header is a flex ROW and did not wrap, so a full-width notes row simply
   squeezed the controls into columns instead of dropping below them. Wrapping
   is what makes "underneath" possible at all. */
.feed-sticky .results-head{display:flex;flex-wrap:wrap;align-items:center;
  gap:8px;row-gap:0}
.feed-sticky .lib-filters{display:flex;flex-wrap:nowrap;gap:6px}
.feed-sticky .selbar.hidden{display:none}
/* compact: this strip is chrome, not content - it should not compete with the
   images for attention */
.feed-sticky .results-head h3{font-size:13px;margin:0}
.feed-sticky .results-head select,
.feed-sticky button{font-size:12px;padding:5px 10px}
.feed-sticky .lib-filters button{font-size:12px;padding:4px 11px}
/* Retention now lives INSIDE the library header rather than in the topbar.
   Two separate strips meant a gap between them that the feed scrolled through
   no matter how the sticky backgrounds were arranged - joining them removes
   the problem rather than working around it. */
/* The notes row spans the full width beneath the controls. flex-basis:100%
   forces the wrap, so "Your library", the filters and the buttons keep one
   line to themselves however narrow the window gets. */
.lib-notes{flex:0 0 100%;width:100%;display:flex;gap:14px;align-items:baseline;
  margin-top:5px;font-size:10.5px;line-height:1.3;opacity:.7}
.lib-notes:empty{display:none}
#feed-more{font-size:10.5px}

/* On desktop the topbar's two buttons are already hidden (they duplicate the
   sidebar and composer collapse controls), and its remaining text has moved
   into the library header - so it was an empty box. Mobile still needs it for
   the drawer toggles. */
@media (min-width:901px){ .topbar{display:none} }

/* help assistant --------------------------------------------------------
   Deliberately conventional: launcher bottom-right, panel above it, branded
   header, scrolling transcript, composer pinned at the bottom. People already
   know how these work - novelty here costs comprehension and buys nothing. */
.ask-fab{position:fixed;right:20px;bottom:20px;z-index:70;width:54px;height:54px;
  border-radius:50%;border:1px solid var(--gold-dk);cursor:pointer;padding:0;
  background:var(--panel);box-shadow:0 8px 24px rgba(0,0,0,.45);
  display:grid;place-items:center;transition:transform .15s ease}
.ask-fab:hover{transform:translateY(-2px);border-color:var(--gold)}
.ask-fab svg{width:26px;height:26px;display:block}

.ask-panel{position:fixed;right:20px;bottom:86px;z-index:71;
  width:min(384px,calc(100vw - 40px));height:min(580px,calc(100vh - 130px));
  display:flex;flex-direction:column;background:var(--panel);
  border:1px solid var(--line);border-radius:16px;
  box-shadow:0 20px 60px rgba(0,0,0,.55);overflow:hidden}

/* branded header, like every support widget people have already used */
.ask-head{flex:none;padding:18px 18px 16px;
  background:linear-gradient(160deg,#2a2118 0%,var(--panel) 100%);
  border-bottom:1px solid var(--line)}
.ask-head-top{display:flex;align-items:center;gap:9px}
.ask-head-top svg{width:22px;height:22px}
.ask-brand{font-weight:600;font-size:14px;color:var(--text)}
.ask-x{margin-left:auto;background:none;border:0;color:var(--muted);
  cursor:pointer;font-size:14px;padding:2px 4px;line-height:1}
.ask-x:hover{color:var(--text)}
.ask-hi{margin-top:12px;font-size:19px;font-weight:600;line-height:1.3;
  color:var(--gold-lt)}
.ask-sub{margin-top:4px;font-size:12.5px;color:var(--muted);line-height:1.45}

.ask-log{flex:1;min-height:0;overflow-y:auto;padding:14px;display:flex;
  flex-direction:column;gap:10px}
.ask-msg{font-size:13.5px;line-height:1.5;padding:9px 12px;border-radius:12px;
  max-width:86%;white-space:pre-wrap;word-break:break-word}
.ask-you{align-self:flex-end;background:var(--gold);color:var(--on-gold);
  border-bottom-right-radius:4px}
.ask-bot{align-self:flex-start;background:var(--panel-2);
  border:1px solid var(--line);border-bottom-left-radius:4px}

.ask-chips{flex:none;display:flex;flex-wrap:wrap;gap:6px;padding:0 14px 12px}
.ask-chips:empty{display:none}
/* Preset chips inside an edit field. The options panel is narrow, so they
   wrap; the row exists so each preset GROUP wraps as its own block rather
   than the second group's chips trailing off the first group's last line. */
.chip-row{display:flex;flex-wrap:wrap;gap:6px;margin:4px 0 10px}

.ask-chip{background:var(--panel-2);border:1px solid var(--line);
  color:var(--gold-lt);border-radius:99px;padding:6px 12px;font-size:12px;
  cursor:pointer;transition:border-color .15s}
.ask-chip:hover{border-color:var(--gold)}
.ask-form{flex:none;display:flex;gap:8px;padding:12px;
  border-top:1px solid var(--line);background:var(--panel)}
.ask-form input{flex:1;min-width:0;border-radius:10px}
.ask-form button{flex:0 0 auto;padding:9px 16px;border-radius:10px}
/* Sits under the composer, quiet - a stated choice, not a nag. The direction
   and casing are set explicitly because the global `label` rule is a uppercased
   column, which stacked the box above the sentence. */
.ask-privacy{flex:none;display:flex;flex-direction:row;align-items:center;
  gap:8px;padding:0 12px 11px;font-size:11px;color:var(--dim);cursor:pointer;
  text-transform:none;letter-spacing:0;background:var(--panel)}
.ask-privacy span{flex:0 1 auto}
.ask-privacy input{width:13px;height:13px;margin:0;flex:0 0 auto;cursor:pointer}
@media (max-width:900px){
  .ask-panel{right:12px;left:12px;width:auto;height:min(70vh,520px);bottom:80px}
}

/* .cost-tag and .bal-pill lived here. Removed with the markup 2026-08-15 -
   no pricing appears in this UI until the rates and the processor are
   settled. See the note in app.js above the generate handler. */

/* edit workspace ---------------------------------------------------------
   Two cards side by side: the tools, then the picture. Both wear the same
   panel + hairline + radius the library header does, so entering Edit does not
   feel like leaving the app.

   Accent is GOLD throughout. The reference this was drawn from uses a violet
   accent on near-black, which is not us. */
/* Exactly as tall as the sidebar beside it. main is already a full-height
   flex sibling of .sidebar, so the only thing that made these cards sit lower
   and finish higher was the document padding that every OTHER view wants:
   14px above (.view) and 26px below (main). A workspace is not a scrolling
   document, so on this view both go. */
#app.on-edit main{padding:0 6px 0}
#app.on-edit #view-edit{padding-top:0;height:100%}
.edit-work{display:flex;gap:10px;align-items:stretch;height:100%;
  min-height:440px}

/* the tools card: icon rail + the lit tool's options, as ONE object */
.edit-tools{flex:0 0 292px;display:flex;min-width:0;overflow:hidden;
  background:var(--panel);border:1px solid var(--line);border-radius:var(--r)}
.edit-rail{flex:0 0 50px;display:flex;flex-direction:column;gap:3px;
  padding:8px 5px;border-right:1px solid var(--line);background:var(--panel-2)}
.edit-rail button{width:40px;height:40px;display:grid;place-items:center;
  border:1px solid transparent;border-radius:10px;background:none;
  color:var(--muted);cursor:pointer;padding:0}
.edit-rail button svg{width:19px;height:19px}
.edit-rail button:hover{color:var(--text);background:#00000055}
.edit-rail button.on{color:var(--on-gold);background:var(--gold);
  border-color:var(--gold-dk)}

.edit-opts{flex:1;min-width:0;display:flex;flex-direction:column;gap:11px;
  padding:13px 14px;overflow:auto}
.edit-opts-head h3{font-size:14px}
.edit-opts-head p{margin:3px 0 0}
.edit-opts label{display:block;font-size:10.5px;letter-spacing:.06em;
  text-transform:uppercase;color:var(--muted);margin:0 0 5px}
.edit-opts .field{margin:0 0 12px}
.edit-opts input[type=range]{width:100%;accent-color:var(--gold)}
.edit-opts textarea{width:100%;min-height:72px;resize:vertical}
.edit-opts .ghost{width:100%}
.edit-opts #edit-run{margin-top:auto}
/* exclusive choices as one control (fast/best, replace/erase) */
.seg{display:flex;gap:4px}
/* Two per row. Four labels like "Black and white" squeezed into one line
   of a narrow options panel each get a couple of characters' width and
   stop being readable, which is the whole point of naming a look. */
.seg.grid2{display:grid;grid-template-columns:1fr 1fr;gap:4px}
.seg.grid2 button{padding:7px 6px;white-space:nowrap}

.seg button{flex:1;padding:6px 4px;font-size:12px;border-radius:8px;
  border:1px solid var(--line);background:var(--panel-2);color:var(--muted);
  cursor:pointer}
.seg button.on{border-color:var(--gold);color:var(--gold-lt);
  background:rgba(var(--gold-rgb),.10)}
.opt-val{float:right;text-transform:none;letter-spacing:0;color:var(--text)}

/* the picture card */
.edit-canvas{flex:1;min-width:0;display:flex;flex-direction:column;
  background:var(--panel);border:1px solid var(--line);border-radius:var(--r);
  overflow:hidden}
.edit-canvas-bar{flex:none;display:flex;align-items:center;gap:8px;
  padding:7px 12px;font-size:12.5px;border-bottom:1px solid var(--line);
  background:var(--panel)}
.edit-canvas-bar button{font-size:12px;padding:5px 10px}
.edit-canvas-bar .spacer{margin-left:auto}

/* The stage OWNS its height and the image is contained inside it. Without
   min-height:0 a flex child refuses to shrink below its content, so a 1920px
   tall picture simply pushed the whole column past the bottom of the window -
   which is exactly what it did. */
/* Same behaviour as the library's viewer: the picture is laid out to FIT and
   zoom is a transform on top, so the stage never scrolls - it clips, and you
   pan. touch-action:none hands the wheel and the drag to us instead of the
   page. */
.edit-stage{flex:1;min-height:0;display:flex;align-items:center;
  justify-content:center;padding:14px;overflow:hidden;position:relative;
  touch-action:none;background:var(--bg)}
.edit-stage img{display:block;max-width:100%;max-height:100%;
  object-fit:contain;border-radius:8px;box-shadow:var(--shadow);
  transform-origin:center center;will-change:transform;user-select:none;
  -webkit-user-drag:none}
.edit-stage img.pannable{cursor:grab;border-radius:0}
.edit-stage img.pannable.dragging{cursor:grabbing}
.edit-stage.busy img{opacity:.45;filter:saturate(.5)}

/* the chain. Only interesting once there is more than one step, so it hides
   itself when there is not. */
.edit-strip{flex:none;display:flex;gap:6px;padding:7px 12px;overflow-x:auto;
  border-top:1px solid var(--line);background:var(--panel)}
.edit-strip:empty{display:none}
.edit-strip img{height:44px;width:auto;border-radius:6px;cursor:pointer;
  border:2px solid transparent;opacity:.6}
.edit-strip img:hover{opacity:1}
.edit-strip img.on{border-color:var(--gold);opacity:1}

/* a file input that looks like our buttons rather than the browser's */
.btn-file{display:inline-flex;align-items:center;cursor:pointer}

@media (max-width:900px){
  .edit-work{flex-wrap:wrap;height:auto}
  .edit-tools{flex:0 0 100%}
  .edit-rail{flex:0 0 46px}
  .edit-stage{min-height:44vh}
}

/* Applies at every width, not only desktop: on a phone the composer is a
   drawer, and a drawer for a view that has no composer is still wrong. */
#app.panel-gone .panel{display:none}
/* The one-line explanation under a setting. Without it a segmented pair is
   two words with no stated consequence - which is exactly what "Fast / Best"
   was on a tool that had no tiers at all. */
.opt-help{margin:5px 0 0;line-height:1.35}

/* A tile whose image could not be fetched. Quiet and legible, rather than the
   browser's torn-page glyph - "not loaded" is a temporary state (a backfill in
   progress, a storage cap) and should not read as "your work is gone". */
.cell-dark{background:var(--panel-2);display:grid;place-items:center}
.cell-dark-l{position:absolute;inset:0;display:grid;place-items:center;
  font-size:11px;color:var(--muted);pointer-events:none}

/* the retouch mask, laid exactly over the picture ------------------------
   Positioned from script each paint, because the image's on-screen rect
   depends on the stage size, the window and the zoom - and a mask that is one
   pixel out is worse than no mask. */
#edit-mask{position:absolute;z-index:3;cursor:crosshair;touch-action:none;
  border-radius:8px}
#edit-mask.hidden{display:none}
/* while painting, the stage must not also try to pan */
.edit-stage:has(#edit-mask:not(.hidden)) img{cursor:crosshair}

/* progress over the edit canvas ------------------------------------------
   The stage dimmed and gave no other signal for the length of a whole
   generation, which reads as a frozen app rather than a working one. */
#edit-prog{position:absolute;left:50%;bottom:18px;transform:translateX(-50%);
  width:min(300px,70%);background:rgba(10,10,12,.82);border:1px solid var(--line);
  border-radius:10px;padding:8px 12px;z-index:3;text-align:center}
#edit-prog.hidden{display:none}
.edit-prog-l{font-size:11px;color:var(--muted);display:block;margin-bottom:6px}
.edit-prog-bar{height:4px;border-radius:2px;background:var(--panel-2);
  overflow:hidden}
.edit-prog-bar span{display:block;height:100%;background:var(--gold);
  transition:width .3s}

/* before / after wipe ----------------------------------------------------
   The previous step sits UNDER the current one and is revealed by clipping
   the current image, so the two are always the same size and place. */
/* Stacking order matters and this had it wrong: `before` carried z-index 1
   while the current image had none, so the BEFORE painted on top and the wipe
   appeared to do nothing - you were looking at the original on both sides of
   the divider. before 1, after 2, mask 3, divider 4. */
#edit-before{position:absolute;z-index:1;object-fit:contain;border-radius:8px;
  pointer-events:none}
.edit-stage img#edit-img{position:relative;z-index:2}
#edit-before.hidden,#edit-wipe.hidden{display:none}
#edit-wipe{position:absolute;z-index:4;cursor:ew-resize;touch-action:none}
.wipe-line{position:absolute;top:0;bottom:0;width:2px;margin-left:-1px;
  background:var(--gold);box-shadow:0 0 0 1px rgba(0,0,0,.45)}
.wipe-grip{position:absolute;top:50%;transform:translate(-50%,-50%);
  width:30px;height:30px;margin-left:0;border-radius:50%;background:var(--gold);
  color:var(--on-gold);font-size:13px;line-height:30px;text-align:center;
  letter-spacing:-1px;box-shadow:0 2px 8px rgba(0,0,0,.5)}
/* the brush owns the pointer when a mask tool is active */
.edit-stage:has(#edit-mask:not(.hidden)) #edit-wipe{pointer-events:none;
  opacity:.35}
/* brush released: the divider takes the pointer instead */
#edit-mask.brush-off{pointer-events:none;cursor:default}
.edit-stage:has(#edit-mask.brush-off) #edit-wipe{pointer-events:auto;opacity:1}

/* ---- General picker: one card -> modal -> categories -> pick cards ------
   Owner's design 2026-08-23: instead of a separate card per attachment type,
   ONE "General" card opens a modal (left sidebar of categories) and each pick
   drops a card into the anchors strip. No limit. */
/* the old per-type tiles are replaced by the General flow ONLY when the picker
   is active (body.has-picker, added by the picker JS behind the labs gate); keep
   #p-lora in the DOM (hidden) as the character engine the modal drives. */
.has-picker #anchor-char,.has-picker #anchor-ref{display:none!important}
.gen-add .dia{border-style:dashed;border-color:#3a3a42}
.gen-add:hover .dia{border-color:var(--gold)}
.gp-overlay{position:fixed;inset:0;z-index:200;background:rgba(0,0,0,.62);
  display:none;align-items:center;justify-content:center}
.gp-overlay.open{display:flex}
.gp-modal{width:min(900px,95vw);height:min(640px,88vh);background:var(--panel);
  border:1px solid var(--line);border-radius:16px;display:flex;overflow:hidden;
  box-shadow:0 24px 64px rgba(0,0,0,.6)}
.gp-side{width:186px;flex:0 0 auto;border-right:1px solid var(--line);
  padding:14px 8px;display:flex;flex-direction:column;gap:2px;background:var(--bg)}
.gp-side h4{font-size:11px;letter-spacing:.06em;text-transform:uppercase;
  color:var(--muted);padding:6px 12px 10px;margin:0}
.gp-cat{display:flex;align-items:center;gap:10px;padding:10px 12px;border-radius:9px;
  color:var(--text);cursor:pointer;font-size:14px;border:0;background:none;
  text-align:left;width:100%}
.gp-cat:hover{background:var(--panel-2)}
.gp-cat.on{background:rgba(var(--gold-rgb),.14);color:var(--gold-lt)}
.gp-main{flex:1;display:flex;flex-direction:column;min-width:0}
.gp-head{display:flex;align-items:center;justify-content:space-between;
  padding:15px 18px;border-bottom:1px solid var(--line)}
.gp-title{font-size:16px;font-weight:600}
.gp-close{width:30px;height:30px;border-radius:8px;border:1px solid var(--line);
  background:var(--panel-2);color:var(--muted);cursor:pointer;font-size:13px;
  display:grid;place-items:center;line-height:1;padding:0}
.gp-close:hover{color:var(--text);border-color:var(--gold)}
.gp-tabs{display:flex;gap:6px;flex-wrap:wrap;padding:12px 18px 0}
.gp-tab{padding:6px 12px;border-radius:20px;border:1px solid var(--line);
  background:var(--panel-2);color:var(--muted);cursor:pointer;font-size:12.5px}
.gp-tab.on{border-color:var(--gold);color:var(--gold-lt)}
.gp-grid{flex:1;overflow-y:auto;padding:16px 18px;display:grid;
  grid-template-columns:repeat(auto-fill,minmax(112px,1fr));gap:12px;align-content:start}
.gp-item{position:relative;border:1px solid var(--line);border-radius:10px;
  background:var(--panel-2);cursor:pointer;overflow:hidden;min-height:82px;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:8px;padding:10px;transition:border-color .12s,background .12s;text-align:center}
.gp-item.has-img{aspect-ratio:1;min-height:0;padding:0}
.gp-item:hover{border-color:var(--gold)}
.gp-item.on{border-color:var(--gold);background:rgba(var(--gold-rgb),.12)}
.gp-item img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.gp-item .gp-name{position:relative;font-size:12px;color:var(--text);
  line-height:1.25;max-width:100%;word-break:break-word}
.gp-item.has-img .gp-name{position:absolute;left:0;right:0;bottom:0;padding:5px 6px;
  background:linear-gradient(180deg,transparent,rgba(0,0,0,.8));color:#fff;font-size:11px}
.gp-item .gp-ico{position:relative;width:22px;height:22px;color:var(--gold-lt)}
.gp-empty{color:var(--muted);font-size:13px;padding:30px 18px;grid-column:1/-1}

/* ---- inline tag pills in the prompt (contentEditable editor) ------------ */
.p-prompt-rich{width:100%;min-height:74px;background:var(--panel-2);border:1px solid var(--line);
  color:var(--text);border-radius:10px;padding:10px 12px;font:inherit;font-size:14px;
  line-height:1.95;outline:none;overflow-y:auto;max-height:220px}
.p-prompt-rich:focus{border-color:var(--gold)}
.p-prompt-rich:empty::before{content:attr(data-ph);color:var(--muted)}
.ptag{display:inline-flex;align-items:center;gap:1px;background:rgba(var(--gold-rgb),.16);
  border:1px solid var(--gold-dk);border-radius:20px;padding:0 3px 0 9px;margin:0 1px;
  font-size:12.5px;line-height:1.4;color:var(--gold-lt);white-space:nowrap;
  vertical-align:baseline;user-select:none}
.ptag-name{font-weight:500}
.ptag-dd{display:inline-flex;align-items:center;justify-content:center;width:15px;height:15px;
  border:0;background:none;color:var(--gold-lt);cursor:pointer;padding:0}
.ptag-dd svg{width:12px;height:12px}
.ptag-menu{position:fixed;z-index:210;min-width:180px;max-width:230px;max-height:280px;overflow-y:auto;
  background:var(--panel);border:1px solid var(--line);border-radius:11px;padding:5px;
  box-shadow:0 12px 34px rgba(0,0,0,.55)}
.ptag-mi{display:flex;align-items:center;gap:9px;padding:7px 9px;border-radius:8px;cursor:pointer;
  font-size:13px;color:var(--text)}
.ptag-mi:hover{background:rgba(var(--gold-rgb),.14)}
.ptag-mi img{width:26px;height:26px;border-radius:6px;object-fit:cover;flex:none}
.ptag-mi-dot{width:8px;height:8px;border-radius:50%;background:var(--muted);flex:none;margin:0 9px}
.ptag-mi-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ptag-empty{color:var(--muted);cursor:default}
.ptag-empty:hover{background:none}

/* ---- floating quick-pick menu (pill swap + "@" insert), its own popup ---- */
.gp-fly{position:fixed;z-index:240;min-width:190px;max-width:250px;
  background:var(--panel);border:1px solid var(--line);border-radius:12px;
  box-shadow:0 16px 40px rgba(0,0,0,.6);overflow:hidden;
  animation:gpFly .11s ease-out}
@keyframes gpFly{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}
.gp-fly-h{font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--muted);
  padding:9px 12px 6px;border-bottom:1px solid var(--line)}
.gp-fly-list{max-height:260px;overflow-y:auto;padding:5px}
.gp-fly-i{display:flex;align-items:center;gap:9px;padding:7px 9px;border-radius:8px;
  cursor:pointer;font-size:13px;color:var(--text)}
.gp-fly-i:hover{background:rgba(var(--gold-rgb),.14)}
.gp-fly-i img{width:26px;height:26px;border-radius:6px;object-fit:cover;flex:none}
.gp-fly-dot{width:8px;height:8px;border-radius:50%;background:var(--gold);flex:none;margin:0 9px}
.gp-fly-n{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.gp-fly-hint{color:var(--muted);font-size:11px}
.gp-fly-empty{color:var(--muted);cursor:default}
.gp-fly-empty:hover{background:none}

/* ---- move-to-folder picker (uploads) ------------------------------------ */
.fp-modal{max-width:440px}
.fp-title{margin:0 0 12px}
.fp-list{display:flex;flex-direction:column;gap:6px;max-height:60vh;overflow-y:auto}
.fp-row{display:flex;align-items:center;gap:11px;width:100%;text-align:left;
  padding:8px 10px;border:1px solid var(--line);border-radius:10px;
  background:var(--panel-2);color:var(--text);cursor:pointer;font:inherit}
.fp-row:hover{border-color:var(--gold);background:rgba(var(--gold-rgb),.10)}
.fp-cover,.fp-ic{width:38px;height:38px;border-radius:8px;flex:none;
  display:grid;place-items:center;background:var(--panel);overflow:hidden;color:var(--muted)}
.fp-cover img{width:100%;height:100%;object-fit:cover}
.fp-ic svg{width:20px;height:20px}
.fp-plus{font-size:22px;line-height:1;color:var(--gold)}
.fp-name{font-weight:500;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.fp-sub{flex:none}
.fp-new{border-style:dashed}
.fold-uploads.grid,.fold-empty-note{width:100%}

/* ---- 2026-08-25 scale pass (owner's TO-FIX list) ------------------------
   One control height across mixed rows; branded dropdowns size from their
   longest option (inline min-width set by the enhancer) instead of the
   current selection; viewer bar controls unified. */
.sable-dd-btn{min-height:32px}                     /* empty label can't collapse */
.viewer-bar{flex-wrap:wrap}
.viewer-bar .viewer-title{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.viewer-bar button,.viewer-bar .sable-dd-btn{
  padding:5px 12px;font-size:12.5px;min-height:30px;border-radius:8px}
.viewer-bar .sable-dd{display:inline-block;width:auto;vertical-align:middle}
.sable-dd[data-for="viewer-folder"]{display:inline-block;width:auto;vertical-align:middle}
/* ratio pill: stop flex-growing, match the batch stepper's 32px height */
/* ratio pill: fixed width sized for the longest label ("21:9 · Ultrawide") so
   it never resizes when the selection changes, and matches the batch stepper's
   height. min-width, NOT content-width, so short labels don't shrink it. */
.compact-row .sable-dd{flex:0 0 auto;width:auto;min-width:150px}
.compact-row .sable-dd-btn{min-height:32px;padding:0 10px;border-radius:9px;font-size:13px}
/* voice rows: the branded wrapper is the flex child now, not the hidden select */
.char-row .sable-dd{flex:1 1 auto;min-width:0}
.char-row .sable-dd-btn{width:100%}
/* character-editor voice dropdown at the card's own field scale */
/* .ce-fields is align-items:flex-start, so the voice DIV shrink-wraps to its
   content (~147px) and truncates voice names - unlike the labels/inputs which
   escape via width:100%. Force it full-width like the other fields, and let the
   label+dropdown grow into it. (owner: "out of scale" = too narrow, v2) */
.ce-voice{width:100%}
.ce-voice label{flex:1 1 auto;min-width:0;width:auto}
.ce-voice .sable-dd{flex:1 1 auto;width:100%;min-width:0}
.ce-voice .sable-dd-btn{font-size:13.5px;padding:8px 10px;border-radius:8px}
/* picker category icons (Add sidebar + @ fly-menu) */
.gp-cat svg{width:18px;height:18px;flex:none;color:var(--muted)}
.gp-cat.on svg{color:var(--gold-lt)}
.gp-fly-i svg{width:16px;height:16px;flex:none;margin-right:2px;color:var(--gold-lt)}
/* reference browser inside the Add window: folder tiles + breadcrumb */
.gp-crumb{display:flex;align-items:center;gap:8px;padding:10px 18px 0;font-size:12.5px;color:var(--muted)}
.gp-crumb button{padding:2px 10px;font-size:12px}
/* reference picker section headers span the whole grid row */
.gp-sec{grid-column:1/-1;font-size:11px;font-weight:600;letter-spacing:.04em;
  text-transform:uppercase;color:var(--muted);padding:4px 2px 0;margin-top:2px}
.gp-sec:first-child{margin-top:0}
/* folder card: square like an image tile, but unmistakably a folder - a solid
   bottom bar with folder glyph + name + count, and a big glyph when empty */
.gp-item.gp-folder{aspect-ratio:1;overflow:hidden}
.gp-item.gp-folder-empty{background:var(--panel-2);border:1px solid var(--line)}
.gp-fbig{position:absolute;inset:0;display:grid;place-items:center;color:var(--gold-lt)}
.gp-fbig svg{width:34px;height:34px}
.gp-fbar{position:absolute;left:0;right:0;bottom:0;display:flex;align-items:center;
  gap:5px;padding:6px 8px;font-size:12px;line-height:1.2;
  background:linear-gradient(transparent,rgba(0,0,0,.55) 40%,rgba(0,0,0,.86))}
.gp-fbar svg{width:14px;height:14px;flex:none;color:var(--gold-lt)}
.gp-fname{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  color:#fff;font-weight:500}
.gp-fn{flex:none;color:rgba(255,255,255,.72);font-size:11px}
/* account "Buy more credits" link, styled as the control it acts like */
a.acct-topup{display:inline-block;padding:10px 16px;border-radius:10px;
  font-size:14px;text-decoration:none;line-height:1.2}
/* ticket / feature attachment previews + expandable ticket body */
.t-atts{display:flex;flex-wrap:wrap;gap:8px;margin-top:8px}
.t-att{width:96px;height:96px;object-fit:cover;border-radius:8px;border:1px solid var(--line);display:block}
video.t-att{width:160px;height:96px;object-fit:cover;background:#000}
.t-item{cursor:pointer}
.t-body{margin-top:6px;white-space:pre-wrap;color:var(--text)}

/* SQUARE TILES via the PADDING-BOX technique, not aspect-ratio.
   Owner's Brave rendered portrait uploads TALL while folder covers stayed
   square - the signature of a portrait image landing in normal flow so its
   natural height overrides `aspect-ratio` (aspect-ratio yields to min-content).
   The padding trick makes the square come from padding (height:0 + padding-
   bottom:100% of the width), which NO content can override, and every child is
   absolute so the image cannot add height. !important so no cascade undoes it.
   Verified square in-app too, so it can't regress the working case. */
.gp-grid .gp-item.has-img,
.gp-grid .gp-item.gp-folder{
  display:block !important;
  aspect-ratio:auto !important;
  height:0 !important;
  min-height:0 !important;
  padding:0 0 100% 0 !important;
  overflow:hidden;
}
.gp-grid .gp-item.has-img>img,
.gp-grid .gp-item.gp-folder>img{
  position:absolute !important;inset:0 !important;
  width:100% !important;height:100% !important;object-fit:cover !important;max-width:none;
}
/* keep the overlaid labels/icons pinned inside the padded square */
.gp-grid .gp-item.has-img>.gp-name,
.gp-grid .gp-item.gp-folder>.gp-fbar{position:absolute;left:0;right:0;bottom:0}
.gp-grid .gp-item.gp-folder>.gp-fbig{position:absolute;inset:0}


/* ---- model picker -------------------------------------------------------
   Inline style= attributes are dead under the prod CSP (style-src 'self'), so
   everything here is a class. */
#wf-model { display: block; margin-top: .6rem; }
/* The MODEL control reuses .sable-dd / .sable-dd-btn so it is literally
   the same widget as TOOL. Nothing bespoke here on purpose - a lookalike
   drifts, and it measurably had: 14.72px vs 14px, and a different fill. */

.mp-inner { max-width: 660px; width: 94vw; }
.mp-search-wrap { margin: .2rem 0 .5rem; }
.mp-search { width: 100%; padding: .55rem .8rem; border-radius: 9px;
             background: var(--bg); color: inherit; font-size: .95rem;
             border: 1px solid var(--line); }
.mp-filters { display: flex; gap: .4rem; margin-bottom: .7rem; flex-wrap: wrap; }
.mp-filters .mp-sel { width: auto; min-width: 11rem; padding: .4rem .6rem;
          border-radius: 8px; font-size: .82rem; background: var(--panel-2);
          color: inherit; border: 1px solid var(--line); cursor: pointer; }

.mp-list { max-height: 60vh; overflow-y: auto; }
.mp-head { font-size: .68rem; text-transform: uppercase; letter-spacing: .09em;
           opacity: .45; margin: .8rem 0 .35rem; }
.mp-head:first-child { margin-top: 0; }

.mp-acc { border-radius: 9px; margin-bottom: .25rem; }
.mp-acc.open { background: rgba(255,255,255,.02); }
.mp-prov { display: flex; align-items: center; gap: .5rem; width: 100%;
           padding: .5rem .6rem; border-radius: 9px; background: transparent;
           border: 0; color: inherit; cursor: pointer; text-align: left; }
.mp-prov:hover { background: var(--panel-2); }
.mp-prov-n { flex: 1; font-size: .9rem; font-weight: 600; }
.mp-prov-c { font-size: .75rem; opacity: .5; }
.mp-caret { opacity: .5; font-size: .8rem; width: 1rem; text-align: center; }
.mp-models { display: none; padding: 0 .35rem .35rem; }
.mp-acc.open .mp-models { display: block; }

.mp-row { display: flex; align-items: center; gap: .5rem; width: 100%;
          padding: .42rem .6rem; border-radius: 8px; background: transparent;
          border: 1px solid transparent; color: inherit; cursor: pointer;
          text-align: left; }
.mp-row:hover { background: var(--panel-2); }
.mp-row.on { border-color: var(--gold); background: var(--panel-2); }
.mp-name { flex: 1; font-size: .86rem; }
.mp-chips { display: flex; gap: .25rem; flex-wrap: wrap; }
.mp-chip { font-size: .63rem; padding: .12rem .42rem; border-radius: 999px;
           background: transparent; color: var(--muted); white-space: nowrap;
           border: 1px solid var(--line); font-weight: 500; }
/* A constraint, not a capability - amber so it reads as "mind this", not
   "avoid this". */
.mp-chip.mp-need { background: rgba(224,160,60,.14); color: var(--warn);
                   border-color: rgba(224,160,60,.4); }
/* An absence. Dim, and deliberately the quietest thing on the row. */
.mp-chip.mp-off { background: transparent; color: var(--muted);
                  border-color: transparent; opacity: .65; }
/* The one capability this product is sold on, so it is the only chip in the
   brand colour. Everything else stays quiet around it. */
.mp-chip.mp-ok { background: rgba(var(--gold-rgb),.15); color: var(--gold-lt);
                 border-color: rgba(var(--gold-rgb),.42); font-weight: 600; }
/* The picker inherited a blue that is not in this palette: its rules asked for
   var(--accent) and var(--card), neither of which this stylesheet defines, so
   they fell through to a generic grey-blue. Sable is sable and or. */
.mp-head { color: var(--gold-dk); opacity: 1; }
.mp-prov-n { color: var(--text); }
.mp-row.on .mp-name { color: var(--gold-lt); font-weight: 600; }
.mp-search:focus { outline: none; border-color: var(--gold);
                   box-shadow: 0 0 0 3px rgba(var(--gold-rgb),.12); }
.mp-caret { color: var(--gold-dk); opacity: .8; }
.mp-cost { color: var(--gold-dk); }

/* The reference picker is a labelled setting like every other row, so the
   composer reads as one list rather than one stray control. */
#anchors-wrap { display: block; }
#anchors-wrap .anchors { margin-top: .35rem; }


/* ---- per-model settings -------------------------------------------------
   The model's own controls, as chips under the prompt. Sable's surfaces and
   accent rather than a borrowed look: same radius, same border and same hover
   as the composer's other controls, so they read as part of this app.  */

.model-opts {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  /* Each chip is its own control with its own menu, so they need to look
     separate. At .4rem they touched and read as a segmented control. */
  gap: .55rem;
  /* Pushed to the right of whatever shares its row - in practice the Ratio
     dropdown, which these belong beside rather than under. */
  margin: 0 0 0 auto;
  position: relative;
}
/* The row now carries two controls, so it has to lay them out. */
.setting.compact-row { display: flex; align-items: center; gap: .6rem;
                       flex-wrap: wrap; }
.mo-chip {
  display: inline-flex;
  align-items: center;
  gap: .42rem;
  /* Same height and corners as the Ratio dropdown beside it: these are
     dropdowns too, and a pill with a square mark read as a tick box. */
  min-height: 32px;
  padding: 0 10px;
  border-radius: 9px;
  border: 1px solid var(--line);
  background: var(--panel-2);
  color: var(--text);
  font-size: .78rem;
  line-height: 1;
  cursor: pointer;
  transition: border-color .12s ease, background .12s ease;
}
.mo-chip:hover { border-color: var(--gold); background: var(--panel); }
.mo-val { white-space: nowrap; }
.mo-k { color: var(--muted); white-space: nowrap; }
.mo-chip .sable-dd-chevron { margin-left: .15rem; }

/* Each control gets its own mark so the row is scannable without reading it -
   drawn in CSS because prod CSP forbids inline SVG attributes. */
.mo-ic {
  width: 11px; height: 11px; flex: 0 0 11px;
  border: 1.5px solid currentColor;
  opacity: .75;
}
.mo-ic-res   { border-radius: 2px; }
.mo-ic-dur   { border-radius: 50%; }
.mo-ic-ratio { border-radius: 2px; height: 8px; }
.mo-ic-audio { border-radius: 50% 2px 2px 50%; }

/* The value menu: the model picker's shape, one level down. */
.mo-menu {
  position: absolute;
  top: calc(100% + .35rem);
  left: 0;
  z-index: 40;
  min-width: 9rem;
  padding: .3rem;
  display: flex;
  flex-direction: column;
  gap: .12rem;
  box-shadow: 0 12px 30px rgba(0, 0, 0, .45);
}
.mo-opt {
  text-align: left;
  padding: .42rem .55rem;
  border-radius: 8px;
  border: 0;
  background: transparent;
  color: var(--text);
  font-size: .82rem;
  cursor: pointer;
}
.mo-opt:hover { background: var(--panel); }
.mo-opt.on { background: rgba(var(--gold-rgb),.16); font-weight: 600; }
/* Catches the next click anywhere so the menu closes like every other popup. */
.mo-off { position: fixed; inset: 0; z-index: 39; }

/* ---- named reference slots ---------------------------------------------- */

.ref-slots {
  display: grid;
  grid-template-columns: 1fr 1fr;
  /* Roomier than the default chip gap: these are two large targets, and at
     .45rem they read as one split box rather than two separate slots. */
  gap: .6rem;
  /* Clear of the "Reference" label above and the +Add row below. */
  margin: .45rem 0 .7rem;
}
.ref-slot {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-end;
  gap: .3rem;
  min-height: 4.1rem;
  padding: .55rem;
  border-radius: var(--r);
  border: 1px dashed var(--line);
  background: var(--panel-2);
  color: var(--muted);
  font-size: .76rem;
  cursor: pointer;
  overflow: hidden;
  transition: border-color .12s ease, color .12s ease;
}
.ref-slot:hover { border-color: var(--gold); color: var(--text); }
.ref-slot.filled { border-style: solid; color: var(--text); }
.ref-slot img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.ref-slot-lbl {
  position: relative;
  z-index: 1;
  padding: .1rem .35rem;
  border-radius: 6px;
}
.ref-slot.filled .ref-slot-lbl { background: rgba(0, 0, 0, .55); }
.ref-slot-ic {
  width: 15px; height: 13px;
  border: 1.5px solid currentColor;
  border-radius: 3px;
  opacity: .7;
}

/* Credits sit at the end of a picker row, the way a price does on a menu. */
.mp-cost {
  margin-left: auto;
  padding-left: .6rem;
  white-space: nowrap;
  font-size: .76rem;
  color: var(--muted);
}

/* The model that made it, directly under the tool name - on a third-party
   generation this is the single most useful line in the panel. */
.vi-model {
  margin-top: .15rem;
  font-size: .82rem;
  color: var(--gold-lt);
}
.vi-by { color: var(--muted); }

/* A card for something this model cannot do. Dimmed and inert rather than
   removed, so the control does not jump around as models are compared. */
.anchor.anchor-off { opacity: .38; pointer-events: none; filter: grayscale(1); }
.ref-note { margin: .3rem 0 0; font-size: .72rem; color: var(--muted);
            line-height: 1.35; }

/* ALL GENERATIONS (admin) ------------------------------------------------
   A contact sheet, not a library: the tile is small because the question it
   answers is "what is being made", and the answer is in the shape of fifty of
   them at once rather than in any single one. Full size is a click away. */
.fd-filters { display: flex; gap: 6px; flex-wrap: wrap; }
.fd-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 12px;
  margin-top: 14px;
}
.fd-tile {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r);
  overflow: hidden;
  min-width: 0;
}
.fd-shot {
  display: block;
  position: relative;
  width: 100%;
  aspect-ratio: 1 / 1;
  padding: 0;
  border: 0;
  background: var(--panel-2);
  cursor: pointer;
}
.fd-shot img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Nothing to show: a failed job, a held one, or a video. The tile keeps its
   place in the grid so the sheet does not reflow around the gaps. */
.fd-none {
  display: flex; align-items: center; justify-content: center;
  width: 100%; height: 100%;
  font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted);
}
.fd-badges {
  position: absolute; top: 6px; left: 6px;
  display: flex; gap: 4px; flex-wrap: wrap;
}
.fd-badge {
  background: rgba(0, 0, 0, .62);
  color: var(--muted);
  border-radius: 99px;
  padding: 1px 7px;
  font-size: 10px;
  letter-spacing: .04em;
}
.fd-badge.held { color: var(--warn); }
.fd-badge.failed { color: var(--err); }
.fd-meta { padding: 7px 9px 9px; min-width: 0; }
.fd-tool { font-size: 12px; font-weight: 600; }
/* Three lines of small print under each tile. They must truncate rather than
   wrap: an email that wraps to three lines makes its tile twice the height of
   its neighbours and the sheet stops being a grid. */
.fd-who, .fd-when, .fd-num {
  font-size: 11px;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.fd-num { color: var(--gold-lt); }


/* ---- assistants (VA sub-accounts) ---------------------------------------
   Classes, not inline styles: the production CSP is style-src 'self', so a
   style= attribute is dropped silently and the spacing simply never applies. */
.va-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 0;
  border-bottom: 1px solid var(--line, #2a2a30);
}
.va-row:last-child { border-bottom: 0; }
.va-actions { display: flex; align-items: center; gap: 8px; }
.va-cap-in { width: 5.5rem; text-align: right; }

/* API keys card (api_v1.py). Same rows as the VA card. */
.api-row-off { opacity: 0.55; }
.api-new {
  margin: 12px 0;
  padding: 10px 12px;
  border: 1px solid var(--gold, #c9a45c);
  border-radius: 8px;
}
.api-token { display: flex; align-items: center; gap: 8px; }
.api-token code {
  flex: 1;
  min-width: 0;
  overflow-wrap: anywhere;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
  user-select: all;
}
.api-sub { margin: 18px 0 4px; font-weight: 600; }
.api-check { display: flex; gap: 8px; align-items: flex-start; text-transform: none;
  letter-spacing: 0; font-size: 13px; margin: 4px 0 12px; }
.api-check input { width: auto; margin-top: 3px; accent-color: var(--gold); }
.api-how { margin-top: 14px; }
.api-how summary { cursor: pointer; font-size: 0.9rem; }
.api-how code { overflow-wrap: anywhere; }
.api-snip {
  margin: 4px 0 12px;
  padding: 8px 10px;
  border-radius: 6px;
  background: var(--panel-2, rgba(255, 255, 255, 0.04));
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11.5px;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
button.small { padding: 4px 10px; font-size: 0.85rem; }

.va-how { margin: 6px 0 14px; padding-left: 18px; }
.va-how li { margin: 3px 0; }

/* ---- tool guides (Help > Tool guides) ---------------------------------- */
.linkish{background:none;border:0;padding:0;color:var(--gold-lt);cursor:pointer;
  text-decoration:underline;text-underline-offset:3px;font:inherit}
.linkish.small{font-size:12px}
.linkish:hover{color:var(--gold)}
.guide-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:12px;margin-top:14px}
.guide-card{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);
  padding:16px;text-align:left;color:inherit;cursor:pointer;font:inherit}
.guide-card:hover{border-color:var(--gold)}
.guide-card h3{margin:0 0 6px;font-size:15px}
.guide-card p{margin:0}
.guide{max-width:880px}
.guide h2{margin:6px 0 8px}
.guide h3{margin:28px 0 10px;font-size:15px}
.guide .lead{font-size:15px;line-height:1.55;margin:0 0 14px}
.guide ul,.guide ol{margin:0;padding-left:20px;line-height:1.6}
.guide li{margin:3px 0}
.guide-figs{display:grid;gap:10px;grid-template-columns:repeat(auto-fill,minmax(150px,1fr))}
.guide-figs.two{grid-template-columns:repeat(2,minmax(0,1fr))}
.guide-figs figure{margin:0}
.guide-figs img{width:100%;height:auto;display:block;border-radius:10px;border:1px solid var(--line)}
.guide-figs figcaption{font-size:12px;color:var(--muted);margin-top:6px}
.guide-cap{font-size:13px;color:var(--muted);margin:0 0 10px}
.guide-opts{display:grid;gap:8px}
.guide-opt{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:10px 12px}
.guide-opt b{display:block;margin-bottom:2px}
.guide-prices{display:flex;gap:10px;flex-wrap:wrap}
.guide-price{background:var(--panel-2);border:1px solid var(--line);border-radius:10px;padding:10px 14px}
.guide-price b{display:block;font-size:18px;color:var(--gold-lt)}
.guide-back{margin-bottom:6px}
@media (max-width:560px){.guide-figs{grid-template-columns:repeat(2,minmax(0,1fr))}}
.guide-cat{margin:22px 0 8px;font-size:13px;color:var(--muted);font-weight:600;
  text-transform:uppercase;letter-spacing:.06em}
.guide-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:12px}
.guide-list{display:block}
.guide-figs video{width:100%;height:auto;display:block;border-radius:10px;border:1px solid var(--line);background:#000}
#char-guide{margin-top:4px}
.guide-card{display:flex;flex-direction:column;align-items:flex-start;justify-content:flex-start}

/* ---- feed cards, owner's list 2026-10-03 ---------------------------------
   Failed / stopped: the badge is a label, not a full-width bar; the reason is
   clamped to four lines (all of it is in the tooltip) so it can never run off
   the bottom of the square; and the settings are one click away. */
.tile.pending .tile-pending-body{display:flex;flex-direction:column;align-items:center;
  gap:2px;max-height:100%;overflow:hidden;padding:14px 4px 0}
.tile.pending .tile-pending-body .badge{display:inline-block}
.tile.pending .tile-err{display:-webkit-box;-webkit-line-clamp:4;-webkit-box-orient:vertical;
  overflow:hidden;word-break:break-word;-webkit-line-clamp:5}
.tile-reuse{margin-top:10px;font-size:12px;padding:6px 12px;border-radius:9px;
  background:var(--panel-2);border:1px solid var(--line);color:var(--text);cursor:pointer}
.tile-reuse:hover{border-color:var(--gold)}
.tile.held .tile-held{display:-webkit-box;-webkit-line-clamp:4;-webkit-box-orient:vertical;overflow:hidden}
/* a voice clip's cell: the wave and the words; it plays in the viewer */
.cell.cell-audio{display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:8px;padding:12px;background:var(--panel-2);font:inherit;color:inherit}
.viewer-aud{display:flex;flex-direction:column;align-items:center;gap:18px;
  width:min(560px,100%);padding:24px;text-align:center}
.viewer-aud[hidden]{display:none}
.viewer.no-fmt .viewer-fmt, .viewer.no-fmt .sable-dd[data-for="viewer-fmt"]{display:none !important}
.viewer-aud-ic{color:var(--gold);line-height:0}
.viewer-aud-ic svg{width:64px;height:64px}
.viewer-aud-txt{margin:0;font-size:17px;line-height:1.5;color:var(--text)}
.viewer-aud audio{width:100%;color-scheme:dark}
.cell-audio-ic{color:var(--gold);line-height:0}
.cell-audio-lbl{font-size:12px;color:var(--muted);text-align:center;line-height:1.35;
  display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}
#cc-img.zoomable{cursor:zoom-in}
.cc-extra{display:block;margin-top:10px}
.cc-extra textarea{width:100%;min-height:58px;resize:vertical;margin-top:4px}

/* ---- account page as sections (owner's list, 2026-10-03) ---------------- */
/* Until the sections are built the cards stay out of sight, so the old one
   column page never flashes up first (owner, 2026-10-11). */
#view-account:not(.acct-ready)>[data-acct]{display:none}
.acct-layout{display:grid;grid-template-columns:210px minmax(0,1fr);gap:22px;
  align-items:start;max-width:1000px}
.acct-tabs{display:flex;flex-direction:column;gap:4px;position:sticky;top:12px}
.acct-tab{text-align:left;padding:10px 12px;border-radius:10px;background:transparent;
  border:1px solid transparent;color:var(--muted);font-size:14px;cursor:pointer}
.acct-tab:hover{color:var(--text);background:var(--panel)}
.acct-tab.on{color:var(--text);background:var(--panel-2);border-color:var(--line)}
.acct-panes{display:flex;flex-direction:column;gap:14px;min-width:0}
.acct-panes .card-flat{margin:0}
.acct-panes label input,.acct-panes label textarea{max-width:460px}
.acct-off{display:none !important}
@media (max-width:760px){
  .acct-layout{grid-template-columns:1fr;gap:12px}
  .acct-tabs{flex-direction:row;overflow-x:auto;position:static;padding-bottom:4px}
  .acct-tab{white-space:nowrap}
}

/* ready-made prompts under the prompt box */
.prompt-ideas{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:8px}
.pi-lbl{font-size:12px;color:var(--muted);margin-right:2px}
.pi-chip{font-size:12px;padding:5px 10px;border-radius:99px;background:var(--panel-2);
  border:1px solid var(--line);color:var(--text);cursor:pointer}
.pi-chip:hover{border-color:var(--gold)}
.gp-charswitch p{margin:0 0 10px}
.gp-switch-row{display:flex;flex-wrap:wrap;gap:6px;justify-content:center}

/* ---- layout pass, owner 2026-10-03 ("make sure everything is in the right
   place ... on vid the size, length and sound dropdowns are not aligned") ---- */
/* The settings row is a two-column grid: Ratio, Quality, Length, Sound (and the
   count stepper) each take one equal cell, so they line up in pairs instead of
   wrapping wherever they happen to fit. The chips' wrapper dissolves into it. */
.setting.compact-row{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
  gap:8px;align-items:stretch;width:100%;flex:1 1 100%}
.setting.compact-row .ratios,.setting.compact-row input[type=hidden]{display:none}
.compact-row .sable-dd{width:100%;min-width:0}
.compact-row .sable-dd-btn{width:100%;min-height:34px;justify-content:space-between}
.compact-row .sable-dd .sable-dd-label{overflow:hidden;text-overflow:ellipsis}
.compact-row .model-opts{display:contents}
.compact-row .model-opts.hidden{display:none}
.mo-cell{position:relative;display:flex;min-width:0}
.mo-chip{width:100%;min-height:34px;justify-content:flex-start}
.mo-chip .sable-dd-chevron{margin-left:auto}
.mo-cell .mo-menu{min-width:100%}
.compact-row .cstep{justify-self:start;align-self:center}
/* The Start/End image slots: a plus, not a square that read as a tick box. */
.ref-slot-ic{width:auto;height:auto;border:0;opacity:.8;font-size:18px;line-height:1}
.ref-slot-ic::before{content:"+"}
/* A required upload (Audio Extractor's video) gets its own line above the price
   and Generate, instead of being squeezed in between them. */
.composer-foot{flex-wrap:wrap}
.composer-foot .refs:not(:empty){flex-basis:100%}
.composer-foot .ref .filebtn{min-height:34px}
/* Links in the page were the browser's blue. */
a{color:var(--gold-lt)}
a:hover{color:var(--gold)}
/* "Upload" on the Edit picker read as plain text: it is a button. */
.btn-file{display:inline-flex;align-items:center;gap:6px;min-height:32px;padding:0 12px;
  border:1px solid var(--line);border-radius:9px;background:var(--panel-2);
  color:var(--text);cursor:pointer;text-transform:none;letter-spacing:0}
.btn-file:hover{border-color:var(--gold)}
/* A tool with nothing to anchor to (Audio Extractor) showed a bare "Reference"
   heading over nothing. */
#anchors-wrap:has(#ref-slots.hidden):has(#anchors:empty){display:none}
/* "How it works" sat 10px in from everything else (button.small's padding). */
button.linkish{padding:0}

/* ---- What's new (owner, 2026-10-03) ------------------------------------ */
.sidebar a.wn-unread::after{content:"";width:8px;height:8px;border-radius:50%;
  background:var(--gold);margin-left:auto;flex:none}
.wn-email{flex-direction:row;align-items:center;justify-content:space-between;gap:16px;
  max-width:760px;margin:12px 0 16px}
.wn-email .switch{flex:none;margin:0;flex-direction:row}
.wn-list{display:flex;flex-direction:column;gap:14px;max-width:760px}
.wn-item{padding:0;max-width:none;margin:0;gap:0}
.wn-sum{display:flex;align-items:center;gap:14px;padding:14px 18px;cursor:pointer;list-style:none}
.wn-sum::-webkit-details-marker{display:none}
.wn-sum:hover .wn-title{color:var(--gold-lt)}
.wn-title{font-size:15px;color:var(--text);flex:1;min-width:0}
.wn-when{white-space:nowrap}
.wn-chev{flex:none}
.wn-item[open] .wn-chev{transform:rotate(-135deg) translateY(1px);border-color:var(--gold)}
.wn-body{padding:0 18px 16px}
.wn-img{width:100%;border-radius:10px;border:1px solid var(--line);margin:4px 0 10px}
.wn-lines{margin:0;padding-left:18px;display:flex;flex-direction:column;gap:6px;line-height:1.5}
@media (max-width:760px){.wn-email{flex-direction:column;align-items:flex-start}
  .wn-sum{flex-wrap:wrap;gap:4px 10px}.wn-when{order:3;flex-basis:100%}}
/* How to: step by step recipes (2026-10-05), on the What's new card */
.ht-lead{margin:0 0 12px}
.ht-char{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin:0 0 14px}
.ht-char-l{font-size:12.5px;color:var(--muted)}
.ht-char-sel{max-width:260px}
.ht-steps{margin:0;padding-left:20px;display:flex;flex-direction:column;gap:14px}
.ht-step::marker{color:var(--gold)}
.ht-text{margin:0 0 8px;line-height:1.5}
.ht-acts{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.ht-got{display:inline-flex;align-items:center;gap:8px}
.ht-got img{width:44px;height:44px;object-fit:cover;border-radius:8px;border:1px solid var(--line)}
.ht-tips{margin:16px 0 0;padding-left:18px;display:flex;flex-direction:column;gap:4px}
#ht-list{display:flex;flex-direction:column;gap:28px;max-width:760px}
.ht-group-head{display:flex;flex-direction:column;gap:4px;margin:0 0 12px;padding:0 0 10px;
  border-bottom:1px solid var(--line)}
.ht-group-head h3{font-size:16px;color:var(--gold-lt)}
.ht-group-head .ht-char{margin:8px 0 0}
.ht-group-head select{max-width:280px}
.ht-note{margin:0 0 12px;color:var(--gold-lt)}
.ht-batch{display:flex;align-items:center;gap:10px;margin:0 0 14px}
.ht-batch-n{width:72px}
.hts-card{max-width:none;margin:0;padding:14px 18px}
.hts-head{display:flex;align-items:baseline;gap:12px;margin:0 0 8px}
.hts-title{font-size:15px;flex:1}
.hts-table{width:100%;border-collapse:collapse;font-size:13.5px}
.hts-table th{text-align:left;font-weight:500;color:var(--muted);padding:4px 8px 6px 0}
.hts-table td{padding:5px 8px 5px 0;border-top:1px solid var(--line);vertical-align:middle}
.hts-table meter{width:90px;height:8px;margin-left:8px;vertical-align:middle}
/* the page an email's "stop these emails" link opens */
.unsub-page{display:grid;place-items:center;min-height:100vh;padding:16px}
.unsub-card{max-width:420px;background:var(--panel);border:1px solid var(--line);
  border-radius:14px;padding:22px 24px}

/* My voices (2026-10-03) */
.mv-top{display:flex;flex-wrap:wrap;gap:10px;align-items:center}
.mv-head{margin-top:24px}
.mv-list{display:flex;flex-direction:column;gap:8px;max-width:720px}
.mv-row{display:flex;flex-direction:row;align-items:center;gap:12px;padding:10px 12px;text-align:left;
  background:var(--panel-2);border:1px solid var(--line);border-radius:12px}
.mv-row .mv-play{width:36px;height:36px;padding:0;display:inline-flex;align-items:center;
  justify-content:center;flex:none;border-radius:50%}
.mv-txt{display:flex;flex-direction:column;gap:2px;flex:1;min-width:0}
.mv-txt strong{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.mv-acts{display:flex;gap:6px;align-items:center}
.mv-form{max-width:560px}
.mv-form .cc-field input{width:100%}
.mvd-form{max-width:640px}
.mvd-form .cc-traits{grid-template-columns:repeat(2,minmax(0,1fr))}
.mvd-form .cc-traits .cc-wide{grid-column:1/-1}
.mv-step{display:flex;flex-direction:column;gap:6px;margin:16px 0 0}
.mv-sentence{margin:4px 0 0;padding:12px 14px;border-left:3px solid var(--gold);
  background:var(--panel-2);border-radius:0 10px 10px 0;font-size:16px;line-height:1.5;color:var(--text)}
.mv-file-row{display:flex;flex-direction:row;align-items:center;gap:10px;min-width:0}
.mv-file-row #mv-file-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.mv-form label.btn-file{display:inline-flex;flex-direction:row;align-items:center;justify-content:center;
  margin:0;text-transform:none;letter-spacing:normal;line-height:1}
.mv-form .mv-consent{display:flex;flex-direction:row;gap:10px;align-items:flex-start;margin:18px 0 0;
  font-size:14px;line-height:1.45;text-transform:none;letter-spacing:normal;color:var(--text);font-weight:400}
.mv-form .mv-consent input{width:auto;flex:none;margin-top:3px}
.mv-actions{display:flex;flex-direction:row;align-items:center;gap:12px;margin-top:18px;flex-wrap:wrap}
@media (max-width:600px){.mv-row{flex-wrap:wrap}
  .mv-acts{width:100%;justify-content:flex-start;padding-left:48px}
  .mvd-form .cc-traits{grid-template-columns:1fr}}
.setting .voice-mine-link{justify-self:start;align-self:start;text-align:left;margin-top:2px}
/* a field's label reads the same whether it wraps an input or a picker */
.mv-form label.cc-field .cc-label{text-transform:none;letter-spacing:normal;font-size:11.5px;
  color:var(--muted);font-weight:400}
/* the voice row in the character editor */
.ce-voice{display:flex;flex-direction:column;align-items:stretch;gap:6px;margin-top:4px;width:100%}
.ce-voice[hidden],.ce-voice.hidden{display:none}
.ce-voice-lbl{font-size:12px;color:var(--muted);text-transform:uppercase;letter-spacing:.05em}
.ce-voice-ctl{display:flex;flex-direction:row;align-items:center;gap:8px}
.ce-voice-ctl .sable-dd,.ce-voice-ctl select{flex:1;min-width:0}
.ce-voice-play{width:36px;height:36px;padding:0;display:inline-flex;align-items:center;
  justify-content:center;flex:none}
.ce-voice .linkish{align-self:flex-start;text-align:left}
.mo-menu{max-height:min(320px,50vh);overflow-y:auto}  /* a long list (a face swap's voices) scrolls in place */
/* ---- alignment pass, 2026-10-03 (owner: "buttons and dropdowns a bit off") ---- */
/* a play button beside a dropdown is a square exactly as tall as it */
.char-row{align-items:stretch}
#p-voice-preview,.ce-voice-play{min-height:0;padding:0;display:inline-flex;align-items:center;
  justify-content:center}
#p-voice-preview{width:42px;height:42px;flex:0 0 42px}
.ce-voice-play{width:39px;height:39px;flex:0 0 39px}
.ce-voice-ctl{align-items:center}
/* an option chip keeps its value on one line and cuts a long one short */
.mo-chip{overflow:hidden}
.mo-k,.mo-chip .sable-dd-chevron{flex:none}
.mo-val{min-width:0;overflow:hidden;text-overflow:ellipsis}
/* the Reference video tile reads like the Add tile under it: centred */
.ref-slot{align-items:center;justify-content:center;text-align:center}
/* "How it works" never touches what is under it */
#wf-guide,#char-guide,#edit-tool-guide{margin-bottom:8px}
.mv-making{border-style:dashed}
.mv-spin{width:36px;height:36px;flex:none;border-radius:50%;border:3px solid var(--line);
  border-top-color:var(--gold);animation:mvspin 1s linear infinite}
@keyframes mvspin{to{transform:rotate(360deg)}}

/* the face swap voice tip under the model settings (2026-10-04) */
.mo-hint{flex-basis:100%;grid-column:1/-1;margin-top:2px;line-height:1.4}

/* Batch (2026-10-06) */
.bt-tabs{margin:10px 0 14px;max-width:none;width:max-content}
.bt-tabs button{white-space:nowrap;padding-left:22px;padding-right:22px}
.bt-card{padding:16px;border-radius:14px;max-width:1100px}
.bt-opts{display:flex;flex-wrap:wrap;gap:14px;align-items:flex-end}
.bt-opts .cc-field{min-width:180px;display:flex;flex-direction:column;gap:6px}
.bt-opts .cc-label{text-transform:none;letter-spacing:normal;font-size:11.5px;color:var(--muted)}
/* The SFW/NSFW and versions buttons as tall as the dropdowns beside them, so
   the row lines up instead of stepping (owner, 2026-10-11). */
.bt-opts .seg{min-height:42px}
.bt-drop{margin:14px 0;min-height:220px;border:1.5px dashed rgba(var(--gold-rgb),.45);border-radius:12px;
  padding:14px;display:flex;flex-direction:column;align-items:center;justify-content:center;cursor:pointer}
.bt-drop.over{background:rgba(var(--gold-rgb),.08)}
.bt-drop .bfs-photos{justify-content:center}
.bt-foot{display:flex;flex-wrap:wrap;align-items:center;gap:12px}
.bt-results{display:flex;flex-wrap:wrap;gap:10px}
.bt-res{width:140px;height:180px;border-radius:10px;overflow:hidden;border:1px solid var(--line);
  display:flex;align-items:center;justify-content:center}
.bt-res img{width:100%;height:100%;object-fit:cover;display:block}
/* Batch results: delete in place, pick to file or download (2026-10-12). */
.bt-res{position:relative}
.bt-open{display:block;width:100%;height:100%}
.bt-x{position:absolute;top:6px;right:6px;width:26px;height:26px;padding:0;border-radius:50%;
  border:none;background:rgba(0,0,0,.7);color:#fff;font-size:12px;line-height:26px;cursor:pointer;
  opacity:0;transition:opacity .15s}
.bt-res:hover .bt-x,.bt-x:focus-visible{opacity:1}
@media (hover:none){.bt-x{opacity:1}}
.selecting .bt-x{display:none}
.selecting .bt-res[data-name]{cursor:pointer}
.selecting .bt-res .tile-check{display:block}
.bt-res.picked{border-color:var(--gold);box-shadow:0 0 0 2px var(--gold)}
.bt-res.picked .tile-check{background:var(--gold);border-color:var(--gold-lt)}
.bt-res.picked .tile-check::after{content:"";position:absolute;left:6px;top:2px;
  width:5px;height:10px;border:solid var(--on-gold);border-width:0 2px 2px 0;transform:rotate(45deg)}
.bt-words{display:flex;flex-direction:column;gap:6px;margin:14px 0 6px}
.bt-words .cc-label{text-transform:none;letter-spacing:normal;font-size:11.5px;color:var(--muted)}
.bt-words textarea{width:100%;min-height:54px;resize:vertical;box-sizing:border-box}
.bt-res-head{display:flex;align-items:center;gap:10px;margin:22px 0 8px}
.bt-res-head h4{margin:0}
.bt-res-head .spacer{flex:1}
.bt-selbar{flex-wrap:wrap}
.bt-sel-count{font-size:12.5px;color:var(--gold-lt);font-weight:600}
.bt-row-all{white-space:nowrap}
.bfs-photos{display:flex;flex-wrap:wrap;gap:8px;margin:6px 0}
.bfs-photo{position:relative;width:72px;height:72px;border-radius:8px;overflow:hidden;
  border:1px solid var(--line)}
.bfs-photo img{width:100%;height:100%;object-fit:cover;display:block}
.bfs-x{position:absolute;top:3px;right:3px;width:20px;height:20px;padding:0;border-radius:50%;
  border:none;background:rgba(0,0,0,.65);color:#fff;font-size:11px;line-height:20px;cursor:pointer}
.pi-batch{border-style:dashed}
.bfs-photo video, .bt-res video{width:100%;height:100%;object-fit:cover;display:block}
.bv-len{position:absolute;left:4px;bottom:3px;font-size:10px;background:rgba(0,0,0,.6);color:#fff;
  padding:1px 5px;border-radius:5px}

/* Clean files (2026-10-09): Batch's look, plus Download all on the right. */
#view-clean .bt-card > p.small:empty, #view-batch .bt-card > p.small:empty{display:none}
#view-clean .bt-card > h4{margin:22px 0 4px}
#cl-keep{margin:0 0 6px}
.cl-run{padding:12px 0;border-top:1px solid var(--line)}
.cl-run:first-child{border-top:none;padding-top:4px}
.cl-run-head{margin-bottom:10px}
.cl-run-head .spacer{flex:1}
.cl-res{position:relative}
.cl-name{position:absolute;left:6px;right:6px;bottom:6px;font-size:11px;color:#fff;background:rgba(0,0,0,.6);padding:2px 6px;border-radius:5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
#view-clean .bt-foot:not(.cl-run-head) > span:empty{display:none}
/* Crop to a shape (2026-10-12): each file at its own shape, the window dragged. */
.cl-crop-f{display:flex;flex-direction:column;gap:6px;margin:0 0 12px}
.cl-crop-f .cc-label{text-transform:none;letter-spacing:normal;font-size:11.5px;color:var(--muted)}
#cl-crop{width:max-content;max-width:100%;flex-wrap:wrap}
#cl-crop button{white-space:nowrap;padding-left:16px;padding-right:16px}
.bfs-photos.cl-cropping{gap:12px}
.bfs-photo.cl-frame{width:auto;height:auto;max-width:100%;cursor:default}
.bfs-photo.cl-frame img,.bfs-photo.cl-frame video{display:block;width:auto;height:auto;max-height:170px;
  max-width:100%;object-fit:fill}
.cl-win{position:absolute;box-sizing:border-box;border:2px solid var(--gold);border-radius:3px;
  box-shadow:0 0 0 9999px rgba(0,0,0,.55);cursor:grab;touch-action:none}
.cl-win:active{cursor:grabbing}
.cl-win.fixed{cursor:default;box-shadow:none}
.cl-blind-note{display:none;position:absolute;left:6px;right:6px;bottom:6px;font-size:11px;color:#fff;
  background:rgba(0,0,0,.65);padding:2px 6px;border-radius:5px;text-align:center}
.cl-blind .cl-blind-note{display:block}
.cl-blind .cl-win{display:none}
/* Advanced settings: re-encode videos (2026-10-12). */
.cl-adv-btn{display:block;width:max-content;margin:0 0 12px}
.cl-adv-btn::after{content:" \25B8"}
.cl-adv-btn[aria-expanded="true"]::after{content:" \25BE"}
.cl-adv{padding:12px 14px 2px;margin:0 0 12px;border:1px solid var(--line);border-radius:10px}
.cl-seg{width:max-content;max-width:100%;flex-wrap:wrap}
.cl-seg button{white-space:nowrap;padding-left:16px;padding-right:16px}
.cl-codec{position:absolute;left:4px;bottom:4px;font-size:10px;font-weight:600;letter-spacing:.04em;
  color:#fff;background:rgba(200,60,60,.85);padding:1px 6px;border-radius:5px}
.cl-odd{color:var(--gold-lt);margin:6px 0 0}
.cl-odd:empty{display:none}

/* Discover our products (owner, 2026-10-08): the Chrome extension download. */
/* Discover: a grid of product cards, each opening its own page. */
.dc-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(290px,1fr));gap:14px;max-width:1100px;margin-top:6px}
.dc-grid>*{margin:0}
.dc-prod{display:flex;flex-direction:column;gap:10px;padding:18px}
.dc-prod-top{display:flex;gap:12px;align-items:center}
.dc-prod-icon{width:48px;height:48px;flex:none;border-radius:12px}
.dc-prod-name{display:flex;flex-direction:column;gap:4px;min-width:0}
.dc-tag{align-self:flex-start;font-size:11px;color:var(--gold-lt);border:1px solid rgba(var(--gold-rgb),.45);
  border-radius:999px;padding:2px 8px}
.dc-prod-pitch{margin:0;line-height:1.5}
.dc-prod-meta{margin:0;flex:1}
.dc-prod-actions{display:flex;gap:10px;align-items:center;flex-wrap:wrap}
.dc-prod-actions .ghost{padding:10px 16px;font-size:14px}
.dc-soon{display:flex;flex-direction:column;justify-content:center;gap:6px;min-height:170px;padding:18px;
  border:1px dashed var(--line);border-radius:var(--r);color:var(--muted)}
.dc-soon b{color:var(--text);font-weight:600}
.dc-back{margin:4px 0 12px}
.dc-back::before{content:'';display:inline-block;width:6px;height:6px;margin-right:8px;
  border-left:1.6px solid currentColor;border-bottom:1.6px solid currentColor;transform:rotate(45deg) translateY(-1px)}
.dc-card{max-width:760px;gap:10px}
.dc-head{display:flex;gap:14px;align-items:flex-start}
.dc-logo{width:56px;height:56px;flex:none;border-radius:14px}
.dc-dl-row{display:flex;align-items:center;gap:14px;flex-wrap:wrap;margin-top:4px}
a.dc-dl{display:inline-flex;align-items:center;justify-content:center;border-radius:8px;
  padding:10px 18px;font-size:14px;font-weight:600;text-decoration:none;color:var(--on-gold);
  background:linear-gradient(180deg,var(--gold-lt),var(--gold));border:1px solid transparent}
a.dc-dl:hover{filter:brightness(1.07)}
.dc-card h4{margin:14px 0 0}
.dc-steps{margin:0;padding-left:20px;display:flex;flex-direction:column;gap:7px;font-size:14px;line-height:1.5}
.dc-code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12.5px;
  background:var(--bg);border:1px solid var(--line);border-radius:5px;padding:1px 5px;white-space:nowrap}
.dc-warn{border:1px solid var(--gold);border-radius:var(--r);padding:12px 14px;font-size:13.5px;
  line-height:1.5;background:rgba(232,182,90,.08)}
.dc-warn b{color:var(--gold-lt)}
@media (max-width:600px){.dc-head{flex-direction:column}.dc-code{white-space:normal}.dc-soon{min-height:0}}

/* Two front doors (2026-10-11): the site switcher under the name, top left. */
.rail-brand{position:relative}
.site-switch{display:grid;place-items:center;width:22px;height:22px;flex:none;padding:0;
  background:transparent;border:1px solid transparent;border-radius:6px;color:var(--muted);cursor:pointer}
.site-switch:hover,.site-switch[aria-expanded="true"]{color:var(--text);border-color:#3a3a41}
.site-switch svg{width:14px;height:14px}
.site-menu{position:absolute;top:calc(100% + 6px);left:0;z-index:60;min-width:220px;padding:6px;
  background:var(--panel,#16161a);border:1px solid var(--line);border-radius:10px;
  box-shadow:0 10px 30px rgba(0,0,0,.45)}
.site-opt{display:flex;flex-direction:column;align-items:flex-start;text-align:left;gap:2px;padding:9px 10px;border-radius:7px;
  color:var(--text);text-decoration:none}
.site-opt:hover{background:rgba(255,255,255,.05)}
.site-opt.on{background:rgba(255,255,255,.08)}
.site-opt b{font-weight:600;font-size:14px}
.site-opt span{font-size:12px;color:var(--muted)}
#app.rail-closed .site-switch,#app.rail-closed .site-menu{display:none}
