
/* ============================================================
   TOKENS
   ============================================================ */
:root{
  --mint:#00D4AA; --em:#10B981; --em-deep:#0B3B2E; --gold:#C9B37E;
  --bg:#F5F8F5; --bg-2:#FFFFFF; --ink:#0B1F19; --muted:#5E6E68;
  --line:rgba(11,31,25,.09); --line-strong:rgba(11,31,25,.16);
  --card:rgba(255,255,255,.78); --card-solid:#FFFFFF;
  --glow:rgba(0,212,170,.22); --shadow:0 30px 80px -30px rgba(11,59,46,.35), 0 8px 24px -12px rgba(11,31,25,.18);
  --r-lg:22px; --r-md:14px; --r-sm:10px;
  --font:'Sora',ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
  --side-w:264px;
}
html[data-theme="dark"]{
  --bg:#050B12; --bg-2:#0A1219; --ink:#EAF2EE; --muted:#8CA09A;
  --line:rgba(234,242,238,.09); --line-strong:rgba(234,242,238,.18);
  --card:rgba(10,18,25,.66); --card-solid:#0C151D;
  --glow:rgba(0,212,170,.32); --shadow:0 40px 100px -30px rgba(0,0,0,.75), 0 10px 30px -10px rgba(0,212,170,.12);
}
*{box-sizing:border-box}
html,body{margin:0;padding:0;background:var(--bg);color:var(--ink);font-family:var(--font);
  -webkit-font-smoothing:antialiased;font-size:15px;line-height:1.5;transition:background .35s ease,color .35s ease}
a{color:inherit;text-decoration:none}
button{font:inherit;color:inherit;cursor:pointer;border:0;background:none}
img,svg{display:block}
.hidden{display:none!important}
::selection{background:var(--mint);color:#04130F}
:focus-visible{outline:2px solid var(--mint);outline-offset:3px;border-radius:6px}

/* Sky (dark mode starfield) */
#sky{position:fixed;inset:0;z-index:0;pointer-events:none;opacity:0;transition:opacity .6s}
html[data-theme="dark"] #sky{opacity:1}
.aurora{position:fixed;inset:auto -20vw -30vh -20vw;height:70vh;z-index:0;pointer-events:none;
  background:radial-gradient(60% 55% at 50% 100%,rgba(0,212,170,.22),transparent 70%);filter:blur(30px);opacity:0;transition:opacity .6s}
html[data-theme="dark"] .aurora{opacity:1}
.daylight{position:fixed;inset:0;z-index:0;pointer-events:none;opacity:1;transition:opacity .6s;
  background:radial-gradient(45% 40% at 18% 8%,rgba(0,212,170,.16),transparent 70%),
             radial-gradient(35% 35% at 90% 30%,rgba(16,185,129,.10),transparent 70%),
             radial-gradient(circle at center,rgba(11,31,25,.06) 1px,transparent 1.2px) 0 0/22px 22px}
html[data-theme="dark"] .daylight{opacity:0}
.view{position:relative;z-index:1}

/* ============================================================
   MAGIC UI STYLE PRIMITIVES
   ============================================================ */
/* Shimmer button */
.shimmer{position:relative;isolation:isolate;display:inline-flex;align-items:center;gap:10px;padding:12px 22px;border-radius:999px;
  color:#04130F;font-weight:600;letter-spacing:.1px;background:linear-gradient(135deg,var(--mint),var(--em));
  box-shadow:0 10px 30px -10px rgba(0,212,170,.7),inset 0 1px 0 rgba(255,255,255,.45);transition:transform .2s ease,box-shadow .2s ease;overflow:hidden}
.shimmer::before{content:"";position:absolute;inset:0;background:linear-gradient(110deg,transparent 30%,rgba(255,255,255,.55) 50%,transparent 70%);
  transform:translateX(-120%);animation:shimmer 3.2s ease-in-out infinite}
.shimmer:hover{transform:translateY(-1px);box-shadow:0 16px 40px -12px rgba(0,212,170,.85),inset 0 1px 0 rgba(255,255,255,.5)}
.shimmer:active{transform:translateY(0) scale(.99)}
@keyframes shimmer{0%{transform:translateX(-120%)}60%,100%{transform:translateX(120%)}}
/* Ghost button */
.ghost{display:inline-flex;align-items:center;gap:8px;padding:11px 18px;border-radius:999px;border:1px solid var(--line-strong);background:var(--card);
  backdrop-filter:blur(10px);font-weight:500;transition:border-color .2s,background .2s,transform .2s}
.ghost:hover{border-color:var(--mint);transform:translateY(-1px)}
/* Border beam */
.beam{position:relative}
.beam::after{content:"";position:absolute;inset:0;border-radius:inherit;padding:1.5px;pointer-events:none;
  background:conic-gradient(from var(--beam-angle,0deg),transparent 0 70%,var(--mint) 82%,#fff 88%,transparent 95%);
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;mask-composite:exclude;
  animation:beam 6s linear infinite}
@property --beam-angle{syntax:'<angle>';inherits:false;initial-value:0deg}
@keyframes beam{to{--beam-angle:360deg}}
/* Spotlight card */
.spot{position:relative;overflow:hidden;background:var(--card);border:1px solid var(--line);border-radius:var(--r-lg);backdrop-filter:blur(14px)}
.spot::before{content:"";position:absolute;inset:0;pointer-events:none;opacity:0;transition:opacity .3s;
  background:radial-gradient(420px circle at var(--mx,50%) var(--my,50%),var(--glow),transparent 45%)}
.spot:hover::before{opacity:1}
/* Glass */
.glass{background:var(--card);border:1px solid var(--line);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px)}
/* Tag */
.tag{display:inline-flex;align-items:center;padding:3px 9px;border-radius:999px;font-size:11.5px;font-weight:600;letter-spacing:.2px;
  background:rgba(0,212,170,.14);color:var(--em);border:1px solid rgba(0,212,170,.28)}
html[data-theme="dark"] .tag{color:var(--mint)}
.tag.gold{background:rgba(201,179,126,.16);color:#9A7F3F;border-color:rgba(201,179,126,.4)}
html[data-theme="dark"] .tag.gold{color:var(--gold)}
.tag.soon{background:transparent;color:var(--muted);border-color:var(--line-strong)}

/* ============================================================
   HUB (landing / library)
   ============================================================ */
.topbar{position:sticky;top:0;z-index:20;display:flex;align-items:center;justify-content:space-between;gap:20px;padding:14px 28px;
  background:color-mix(in srgb,var(--bg) 70%,transparent);backdrop-filter:blur(18px);border-bottom:1px solid var(--line)}
.brand{display:flex;align-items:center;gap:11px;font-weight:600;letter-spacing:-.2px}
.brand .mark{width:34px;height:34px;border-radius:10px;display:grid;place-items:center;background:linear-gradient(135deg,var(--mint),var(--em));box-shadow:0 8px 20px -8px rgba(0,212,170,.8)}
.topnav{display:flex;gap:6px}
.topnav a{padding:8px 14px;border-radius:999px;color:var(--muted);font-weight:500;transition:color .2s,background .2s}
.topnav a:hover{color:var(--ink);background:var(--card)}
.topright{display:flex;align-items:center;gap:10px}
.iconbtn{width:40px;height:40px;border-radius:999px;display:grid;place-items:center;border:1px solid var(--line-strong);background:var(--card);backdrop-filter:blur(10px);transition:border-color .2s,transform .2s}
.iconbtn:hover{border-color:var(--mint);transform:translateY(-1px)}
.iconbtn svg{width:18px;height:18px}
html[data-theme="dark"] .promo a{color:var(--mint)}

.wrap{max-width:1240px;margin:0 auto;padding:0 28px}
.hero{display:grid;grid-template-columns:1.05fr 1fr;gap:56px;align-items:center;padding:84px 0 60px;min-height:calc(100vh - 110px);min-height:calc(100dvh - 110px)}
.hero h1{text-wrap:balance;font-size:clamp(40px,5.2vw,68px);line-height:1.02;letter-spacing:-2px;font-weight:600;margin:0 0 22px}
.hero .lede{font-size:18px;color:var(--muted);max-width:520px;margin:0 0 30px;font-weight:300}
.hero .ctas{display:flex;gap:12px;flex-wrap:wrap;align-items:center}
.hero .trust{margin-top:38px;display:flex;align-items:center;gap:14px;color:var(--muted);font-size:13.5px}
.avatars{display:flex}
.avatars span{width:28px;height:28px;border-radius:999px;border:2px solid var(--bg);margin-left:-8px;background:linear-gradient(135deg,var(--em-deep),var(--em))}
.avatars span:first-child{margin-left:0}
.reveal{animation:rise .9s cubic-bezier(.2,.8,.2,1) both}
.reveal.d1{animation-delay:.08s}.reveal.d2{animation-delay:.16s}.reveal.d3{animation-delay:.28s}
@keyframes rise{from{opacity:0;transform:translateY(18px)}to{opacity:1;transform:none}}

/* 3D console */
.stage3d{perspective:1400px}
.console{transform-style:preserve-3d;transform:rotateX(var(--rx,6deg)) rotateY(var(--ry,-10deg));transition:transform .18s ease-out;
  border-radius:var(--r-lg);box-shadow:var(--shadow);background:var(--card-solid);border:1px solid var(--line)}
.console .layer{transform:translateZ(30px)}
.console-head{display:flex;align-items:center;justify-content:space-between;padding:16px 18px;border-bottom:1px solid var(--line)}
.console-head .title{display:flex;align-items:center;gap:10px;font-weight:600}
.console-head .dot{width:9px;height:9px;border-radius:999px;background:var(--mint);box-shadow:0 0 0 4px var(--glow)}
.composer{padding:18px}
.composer textarea{width:100%;min-height:96px;resize:none;border:0;outline:0;background:transparent;color:var(--ink);font:inherit;font-size:16px;line-height:1.55}
.composer textarea::placeholder{color:var(--muted)}
.chips{display:flex;gap:8px;flex-wrap:wrap;margin:6px 0 14px}
.chip{display:inline-flex;align-items:center;gap:7px;padding:6px 10px 6px 6px;border-radius:999px;border:1px solid var(--line-strong);background:var(--card);font-size:13px}
.chip .av{width:20px;height:20px;border-radius:999px;background:linear-gradient(135deg,#F6D6C2,#C98C6B)}
.chip.add{padding:6px 10px;color:var(--muted);border-style:dashed}
.pills{display:flex;justify-content:space-between;align-items:center;gap:10px;flex-wrap:wrap;padding-top:14px;border-top:1px solid var(--line)}
.pillrow{display:flex;gap:8px;flex-wrap:wrap}
.pill{display:inline-flex;align-items:center;gap:6px;padding:7px 12px;border-radius:999px;border:1px solid var(--line-strong);font-size:13px;color:var(--ink);background:var(--card);transition:border-color .2s}
.pill:hover{border-color:var(--mint)}
.pill b{font-weight:600}
.go{width:44px;height:44px;border-radius:999px;display:grid;place-items:center;background:linear-gradient(135deg,var(--mint),var(--em));color:#04130F;box-shadow:0 10px 28px -8px rgba(0,212,170,.8);transition:transform .2s}
.go:hover{transform:scale(1.06)}
.go svg{width:18px;height:18px}
.plan{padding:0 18px 18px;display:grid;grid-template-columns:repeat(4,1fr);gap:8px}
.shot{aspect-ratio:3/4;border-radius:var(--r-sm);border:1px solid var(--line);position:relative;overflow:hidden;
  background:linear-gradient(160deg,color-mix(in srgb,var(--em-deep) 60%,var(--bg-2)),color-mix(in srgb,var(--mint) 20%,var(--bg-2)))}
.shot::after{content:"";position:absolute;inset:0;background:radial-gradient(circle at 60% 30%,rgba(255,255,255,.18),transparent 55%)}
.shot span{position:absolute;left:8px;bottom:6px;font-size:11px;color:#EAF2EE;text-shadow:0 1px 4px rgba(0,0,0,.6)}
html:not([data-theme="dark"]) .shot:not(.pending){background:linear-gradient(160deg,#0B3B2E,#1E8A66 70%,#3FC7A0)}
.shot.pending{background:var(--card);display:grid;place-items:center;color:var(--muted);font-size:12px}
/* 06-09-2026 (Kyle, screenshot): a tile whose media 404s used to leave an
   empty gradient box that read as a gap in the grid. Provider URLs expire
   after ~7 days while history keeps 120 days of records, so this is normal
   for older work and the tile now states it plainly instead of looking
   broken. .gone is set by imgTag's error handler; .tile-gone is rendered
   server-side-shaped for items that never had a URL at all. */
.shot.gone::before,.shot .tile-gone{position:absolute;inset:0;display:grid;place-items:center;
  padding:10px;text-align:center;font-size:11.5px;line-height:1.35;color:var(--muted);
  background:var(--card);z-index:3}
.shot.gone::before{content:"No longer available"}
.shot.gone::after{display:none}
.shot.pending::after{display:none}
.shot.pending i{width:20px;height:20px;border-radius:999px;border:2px solid var(--line-strong);border-top-color:var(--mint);animation:spin 1s linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}

/* Portrait images (model placeholders) */
.shot img,.pmedia img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:center top}
.shot::after{z-index:1}
.shot span{z-index:2}
.pmedia{position:relative;overflow:hidden;border-radius:14px;background:linear-gradient(160deg,#0B3B2E,#1E8A66 70%,#3FC7A0)}
.pmedia::after{content:"";position:absolute;inset:0;background:linear-gradient(to top,rgba(4,14,11,.55),transparent 45%)}
.chip .av{overflow:hidden;position:relative}
.chip .av img{width:100%;height:100%;object-fit:cover}
/* Editorial hub: varied geometry, fewer boxed borders */
#hub .tile{border:0;border-radius:18px;box-shadow:0 24px 60px -34px rgba(4,20,15,.55)}
#hub .t-factory{border-radius:28px 18px 18px 18px;overflow:hidden;position:relative}
#hub .t-factory .media{position:absolute;inset:0;z-index:0}
#hub .t-factory .media img{width:100%;height:100%;object-fit:cover;object-position:70% 20%;opacity:.9}
/* U-UI 4.3: was .96 across the left 34%, which buried the photo. Matched
   to the mockup - readable behind the copy, clear toward the face. */
#hub .t-factory .media::after{content:"";position:absolute;inset:0;background:linear-gradient(100deg,rgba(6,26,20,.92) 26%,rgba(6,26,20,.62) 54%,rgba(6,26,20,.22) 80%,rgba(6,26,20,.10)),linear-gradient(to top,rgba(5,18,14,.72),rgba(5,18,14,.10) 55%,transparent)}
#hub .t-factory>div,#hub .t-factory .foot{position:relative;z-index:1}
#hub .tile.imaged{padding:0;overflow:hidden;justify-content:flex-end;min-height:250px;position:relative;color:#EAF2EE}
#hub .tile.imaged .pmedia{position:absolute;inset:0;border-radius:0}
#hub .tile.imaged .body{position:relative;z-index:1;padding:18px 20px;background:linear-gradient(to top,rgba(4,14,11,.82),rgba(4,14,11,.25) 70%,transparent)}
#hub .tile.imaged h3{color:#fff}
#hub .tile.imaged p{color:rgba(234,242,238,.78)}
#hub .tile.imaged .foot{color:rgba(234,242,238,.65);margin-top:8px}
#hub .tile.imaged .minis span{border-color:rgba(255,255,255,.28);color:#EAF2EE;backdrop-filter:blur(6px)}
#hub .video{border:0;border-radius:16px;box-shadow:0 24px 60px -34px rgba(4,20,15,.55)}
#hub .plan-card{border-radius:16px}
#hub .upd{border-radius:16px}
/* Sections */
.section{padding:72px 0}
.section-head{display:flex;align-items:end;justify-content:space-between;gap:24px;margin-bottom:28px}
.section h2{font-size:clamp(28px,3.2vw,40px);letter-spacing:-1.2px;font-weight:600;margin:0 0 8px;line-height:1.08}
.section .sub{color:var(--muted);font-weight:300;max-width:560px;margin:0}

/* Bento */
.bento{display:grid;grid-template-columns:repeat(6,1fr);grid-auto-rows:minmax(150px,auto);gap:14px}
.tile{padding:22px;display:flex;flex-direction:column;justify-content:space-between;gap:14px}
.tile h3{margin:0;font-size:18px;font-weight:600;letter-spacing:-.3px}
.tile p{margin:6px 0 0;color:var(--muted);font-size:14px;font-weight:300}
.tile .foot{display:flex;justify-content:space-between;align-items:center;font-size:13px;color:var(--muted)}
.tile .ico{width:38px;height:38px;border-radius:11px;display:grid;place-items:center;background:rgba(0,212,170,.12);border:1px solid rgba(0,212,170,.25)}
.tile .ico svg{width:18px;height:18px;color:var(--em)}
html[data-theme="dark"] .tile .ico svg{color:var(--mint)}
.t-factory{grid-column:span 4;grid-row:span 2;background:linear-gradient(160deg,color-mix(in srgb,var(--em-deep) 85%,var(--card-solid)),color-mix(in srgb,var(--em-deep) 40%,var(--card-solid)));color:#EAF2EE}
.t-factory p,.t-factory .foot{color:rgba(234,242,238,.72)}
.t-factory .ico{background:rgba(255,255,255,.1);border-color:rgba(255,255,255,.18)}
.t-factory .ico svg{color:var(--mint)}
.t-factory .flow{display:flex;gap:8px;flex-wrap:wrap;margin-top:8px}
.t-factory .flow span{padding:6px 10px;border-radius:999px;border:1px solid rgba(255,255,255,.18);font-size:12.5px;color:#EAF2EE}
.t-2{grid-column:span 2}
.minis{display:flex;gap:6px;flex-wrap:wrap}
.minis span{font-size:12.5px;padding:5px 9px;border-radius:8px;border:1px solid var(--line-strong);color:var(--ink)}

/* Tutorials */
.videos{display:grid;grid-template-columns:repeat(4,1fr);gap:14px}
.video{position:relative;overflow:hidden;border-radius:var(--r-lg);border:1px solid var(--line);aspect-ratio:16/9;cursor:pointer;
  background:radial-gradient(80% 80% at 30% 20%,color-mix(in srgb,var(--em) 35%,var(--bg-2)),var(--bg-2));display:flex;align-items:end;padding:18px;transition:transform .25s,border-color .25s}
/* 17-09-2026 (Kyle): featured first slot dropped - with 8 cards the 2fr
   column left the two unfilmed placeholders as the biggest things on the
   page. Uniform 4x2 grid, filmed cards first, "Soon" cards dimmed. */
.video.pending{opacity:.55;outline:1px dashed var(--line-strong);outline-offset:-1px}
.video.pending:hover{opacity:.8;transform:none}
.video:hover{transform:translateY(-3px);border-color:var(--mint)}
.video .play{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:58px;height:58px;border-radius:999px;display:grid;place-items:center;
  background:var(--card);border:1px solid var(--line-strong);backdrop-filter:blur(10px);box-shadow:0 12px 30px -10px rgba(0,0,0,.5)}
.video .play svg{width:20px;height:20px;margin-left:3px}
.video .meta{position:relative;z-index:1}
.video .meta b{display:block;font-weight:600;font-size:15px;line-height:1.25}
.video .meta small{color:var(--muted);font-size:12.5px}
.video .len{position:absolute;right:14px;top:12px;font-size:12px;padding:3px 8px;border-radius:6px;background:rgba(4,19,15,.55);color:#EAF2EE}
/* U-UI 12 (06-09-2026): a card with no video ID yet. Never show a runtime for
   a video that does not exist - badge it honestly instead. The class is added
   by hub_v2.js only while data-yt is empty, so pasting an ID removes it. */
.video.pending .len{display:none}
.video.pending::after{content:"Soon";position:absolute;right:14px;top:12px;font-size:12px;padding:3px 8px;
  border-radius:6px;background:rgba(4,19,15,.55);color:var(--muted);z-index:1}

/* Pricing */
.plans{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;align-items:stretch}
.plan-card{padding:24px;display:flex;flex-direction:column;gap:12px}
.plan-card .price{font-size:34px;letter-spacing:-1.5px;font-weight:600;line-height:1}
.plan-card .price small{font-size:14px;color:var(--muted);letter-spacing:0;font-weight:400}
.plan-card .amt{font-size:16px;font-weight:600}
.plan-card ul{margin:6px 0 0;padding:0;list-style:none;color:var(--muted);font-size:14px;font-weight:300;display:grid;gap:6px}
.plan-card li::before{content:"";display:inline-block;width:6px;height:6px;border-radius:999px;background:var(--mint);margin-right:9px;vertical-align:middle}
.plan-card .cta{margin-top:auto}
.plan-card.rec{border-color:rgba(0,212,170,.45)}
.plan-card .tag{align-self:flex-start}

/* Updates */
.feed{display:grid;grid-template-columns:1fr 1fr;gap:14px}
.upd{padding:20px 22px;display:grid;grid-template-columns:auto 1fr;gap:16px;align-items:start}
.upd time{font-size:12.5px;color:var(--muted);padding-top:3px;white-space:nowrap}
.upd b{display:block;font-weight:600;margin:4px 0 6px}
.upd p{margin:0;color:var(--muted);font-size:14px;font-weight:300}

footer{border-top:1px solid var(--line);padding:40px 0 60px;color:var(--muted);font-size:13.5px}
footer .cols{display:grid;grid-template-columns:2fr 1fr 1fr 1fr;gap:24px}
footer h4{margin:0 0 10px;color:var(--ink);font-size:13.5px;font-weight:600}
footer a{display:block;padding:3px 0}
footer a:hover{color:var(--ink)}

/* ============================================================
   APP (the Factory)
   ============================================================ */
#app{display:grid;grid-template-columns:var(--side-w) 1fr;min-height:100vh;min-height:100dvh}
/* M1 (05-09-2026): mobile top bar + drawer scrim. Hidden on desktop; the
   <=1000px block turns the sidebar into the drawer these control. dvh lines
   above/below are progressive - browsers without dvh keep the vh value. */
.mtop{display:none;position:fixed;top:0;left:0;right:0;z-index:110;align-items:center;gap:10px;
  padding:10px 14px;padding-top:calc(10px + env(safe-area-inset-top));
  background:color-mix(in srgb,var(--bg) 72%,transparent);backdrop-filter:blur(20px);border-bottom:1px solid var(--line)}
.mtop .brand{margin:0;font-size:15px}
.mnav{display:flex;align-items:center;justify-content:center;width:44px;height:44px;border-radius:12px;border:1px solid var(--line);
  background:var(--card);color:var(--ink);cursor:pointer;flex:none}
.mtok{display:flex;align-items:center;gap:6px;margin-left:auto;padding:10px 14px;min-height:44px;border-radius:999px;
  border:1px solid var(--line);background:var(--card);color:var(--ink);font-size:13.5px;cursor:pointer;flex:none}
.mtok b{font-weight:600}
.mscrim{display:none;position:fixed;inset:0;z-index:115;background:rgba(3,8,12,.5);backdrop-filter:blur(2px);
  opacity:0;pointer-events:none;transition:opacity .25s}
body.nav-open .mscrim{opacity:1;pointer-events:auto}
.side{position:sticky;top:0;height:100vh;height:100dvh;display:flex;flex-direction:column;gap:10px;padding:16px 14px;border-right:1px solid var(--line);
  background:color-mix(in srgb,var(--bg) 60%,transparent);backdrop-filter:blur(20px);overflow-y:auto;overflow-x:hidden;
  /* 06-09-2026: without this, the sidebar's usable width silently shrinks by
     the scrollbar's 8px ONLY on accounts whose nav content is tall enough to
     need scrolling - a long display name or the bottom Theme/Tutorials/Hub
     row then crowds or overlaps the scrollbar, because nothing downstream
     was sized with that 8px in mind. Reserving it always keeps the content
     width constant whether or not the scrollbar is currently rendered.
     Additive-only: browsers without support just ignore it, unchanged. */
  scrollbar-gutter:stable}
.side .brand{padding:6px 8px 12px}
.usercard{padding:12px;border-radius:var(--r-md);display:grid;gap:10px;box-shadow:0 14px 34px -22px rgba(0,0,0,.6)}
/* min-width:0 - 06-09-2026 (Kyle, third report on this card). THE BREAK IN THE
   CHAIN: .usercard is a GRID and .who is a grid item, which defaults to
   min-width:auto. The display name is one unbreakable word of unbounded
   length, so its min-content width set a FLOOR on the grid track; the track
   grew, and .tokens - which stretches to that same track - was pushed WIDER
   THAN THE CARD, which is the token box not lining up that Kyle screenshotted.
   The ellipsis added earlier could never fire while an ancestor refused to
   shrink. Every link in the chain now shrinks: .who (grid item) ->
   .who-text (flex item) -> the name itself (ellipsis). */
.usercard .who{display:flex;align-items:center;gap:11px;min-width:0}
.usercard .av{width:38px;height:38px;border-radius:999px;display:grid;place-items:center;font-weight:600;color:#04130F;background:linear-gradient(135deg,var(--mint),var(--em))}
/* 06-09-2026: the name is user_email.split('@')[0] - unbounded length, one
   unbreakable word, bold. min-width:0 lets this flex item actually shrink
   below its content size (flex items default to min-width:auto, which is
   what let a long name push the whole card past its right edge instead of
   truncating); the ellipsis on the name then has somewhere to take effect. */
.usercard .who-text{min-width:0;flex:1}
.usercard .who b{display:block;font-weight:600;line-height:1.15;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%}
.usercard .who small{color:var(--muted);font-size:12.5px}
.tokens{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-radius:var(--r-sm);
  background:linear-gradient(135deg,color-mix(in srgb,var(--mint) 18%,transparent),color-mix(in srgb,var(--em) 8%,transparent));border:1px solid rgba(0,212,170,.28)}
.tokens small{display:block;color:var(--muted);font-size:11.5px}
.tokens b{font-size:20px;letter-spacing:-.6px;font-weight:600;font-variant-numeric:tabular-nums}
.tokens .add{width:30px;height:30px;border-radius:999px;display:grid;place-items:center;background:var(--mint);color:#04130F;box-shadow:0 8px 18px -8px rgba(0,212,170,.9)}
.nav{display:grid;gap:2px;padding:4px 0}
.nav .group{padding:10px 10px 4px;font-size:12px;color:var(--muted);font-weight:500}
.nav a{white-space:nowrap;min-width:0;overflow:hidden;display:flex;align-items:center;gap:11px;padding:8px 10px;border-radius:10px;color:var(--ink);font-weight:500;font-size:14px;position:relative;transition:background .2s,transform .2s}
.nav a svg{width:17px;height:17px;color:var(--muted);flex:none}
.nav a:hover{background:var(--card)}
.nav a.active{background:linear-gradient(135deg,color-mix(in srgb,var(--mint) 22%,transparent),color-mix(in srgb,var(--em) 10%,transparent));
  border:1px solid rgba(0,212,170,.3);box-shadow:0 12px 26px -16px rgba(0,212,170,.8),inset 0 1px 0 rgba(255,255,255,.08)}
.nav a.active svg{color:var(--em)}
html[data-theme="dark"] .nav a.active svg{color:var(--mint)}
.nav a .tag{margin-left:auto}
.side .bottom{margin-top:auto;display:grid;gap:8px;padding-top:10px;border-top:1px solid var(--line)}
/* 06-09-2026 (Kyle, twice - the first fix was incomplete and he was right to
   push back). The sidebar is a FIXED 264px. After 28px of padding and the 8px
   reserved scrollbar there are 228px for three buttons, i.e. ~70px each - but
   "Theme" (icon + label) needs ~82px and "Tutorials" ~88px. Flex items default
   to min-width:auto, so they REFUSED to shrink, the row overflowed, and Hub
   was clipped off the right edge. Reserving the scrollbar gutter actually took
   8px more and made it marginally worse.
   Fixed properly: the row may WRAP, and buttons may shrink. Nothing can be
   clipped at any width, font size or label length - if three do not fit on a
   line, the third moves to its own line instead of disappearing. */
.side .rowbtns{display:flex;gap:8px;flex-wrap:wrap}
.side .rowbtns .ghost{flex:1 1 auto;min-width:0;justify-content:center;padding:9px 10px;font-size:12.5px;white-space:nowrap}
.logout{color:#E5484D;font-size:13.5px;font-weight:500;padding:8px 10px;display:flex;gap:9px;align-items:center;border-radius:10px}
.logout:hover{background:rgba(229,72,77,.1)}

.stage{min-width:0}
.stagebar{display:flex;align-items:center;justify-content:space-between;padding:18px 32px;border-bottom:1px solid var(--line);position:sticky;top:0;z-index:5;
  background:color-mix(in srgb,var(--bg) 60%,transparent);backdrop-filter:blur(18px)}
.stagebar h1{margin:0;font-size:18px;font-weight:600;letter-spacing:-.3px}
.factory{padding:40px 32px 60px;max-width:1080px;margin:0 auto}
.factory .lead{text-align:center;margin:20px auto 30px;max-width:640px}
/* 06-09-2026 (Kyle): extension install bar - the OneLinkk and ReelGen tabs
   both describe a browser extension the page depends on, but neither linked
   it. Uses existing tokens only, so it themes with everything else. */
.extbar{display:flex;align-items:center;gap:14px;max-width:640px;margin:0 auto 26px;padding:13px 16px;
  border-radius:var(--r-md);border:1px solid rgba(0,212,170,.28);text-decoration:none;color:var(--ink);
  background:linear-gradient(135deg,color-mix(in srgb,var(--mint) 16%,transparent),color-mix(in srgb,var(--em) 7%,transparent));
  transition:transform .2s,border-color .2s}
.extbar:hover{transform:translateY(-1px);border-color:rgba(0,212,170,.55)}
.extbar-icon{flex:none;width:38px;height:38px;border-radius:999px;display:grid;place-items:center;
  color:#04130F;background:linear-gradient(135deg,var(--mint),var(--em))}
.extbar-text{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1}
.extbar-text b{font-weight:600;line-height:1.2}
.extbar-text small{color:var(--muted);font-size:12.5px;line-height:1.35}
.extbar-cta{flex:none;display:inline-flex;align-items:center;gap:6px;padding:8px 14px;border-radius:999px;
  font-size:13px;font-weight:600;color:#04130F;background:linear-gradient(135deg,var(--mint),var(--em))}
@media(max-width:560px){.extbar{flex-wrap:wrap}.extbar-cta{width:100%;justify-content:center}}
.factory .lead h2{text-wrap:balance;font-size:clamp(28px,3.4vw,44px);letter-spacing:-1.4px;font-weight:600;margin:0 0 10px;line-height:1.05}
.factory .lead p{margin:0;color:var(--muted);font-weight:300;font-size:16px}
.bigcomposer{border-radius:var(--r-lg);background:var(--card-solid);border:1px solid var(--line);box-shadow:var(--shadow)}
.bigcomposer .composer textarea{min-height:110px;font-size:17px}
.recent-head{display:flex;justify-content:space-between;align-items:center;margin:0 0 14px}
.recent-head h3{margin:0;font-size:16px;font-weight:600}
.recent-head a{color:var(--muted);font-size:13.5px}
.grid{display:grid;grid-template-columns:repeat(3,1fr);gap:14px}
.grid .shot{aspect-ratio:3/4;border-radius:var(--r-md)}
/* :not(.gone) - 06-09-2026: this badge and the "No longer available"
   message both use ::before, and this selector is the more specific of
   the two, so without the guard a DEAD video tile kept its little video
   badge and never showed the message - the exact blank-tile symptom
   Kyle screenshotted. */
.grid .shot.video:not(.gone)::before{content:"";position:absolute;right:8px;top:8px;width:22px;height:22px;border-radius:6px;background:rgba(4,19,15,.6);
  -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2'%3E%3Crect x='3' y='6' width='13' height='12' rx='2'/%3E%3Cpath d='m16 10 5-3v10l-5-3z'/%3E%3C/svg%3E") center/16px no-repeat;background:#EAF2EE}

/* 06-09-2026 (Kyle): paperclip attach control in the Nano composer. The file
   input is visually hidden but must stay FOCUSABLE and in the layout for
   keyboard users, so it is clipped rather than display:none'd. */
.hidden-file{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
.pill.attach{display:inline-flex;align-items:center;gap:7px}
.pill.attach.on{border-color:rgba(0,212,170,.55);background:linear-gradient(135deg,color-mix(in srgb,var(--mint) 16%,transparent),color-mix(in srgb,var(--em) 7%,transparent))}

/* ============================================================
   BEFORE/AFTER COMPARE (upscaler) - 06-09-2026 (Kyle)
   Two small side-by-side panes made an upscale impossible to judge. One big
   stage, drag the handle to wipe between the two. The "before" layer is
   clipped from the left, so both images share identical geometry and the
   comparison is honest - no scaling difference to mislead the eye.
   ============================================================ */
.compare{max-width:1100px;margin:0 auto}
.compare-stage{position:relative;overflow:hidden;border-radius:var(--r-md);border:1px solid var(--line);
  background:var(--card);user-select:none;touch-action:pan-y;width:100%;aspect-ratio:var(--compare-ar,3/4);max-height:74vh}
/* 06-09-2026 (Kyle): "roll back the carousel preview as the current one
   doesnt work" - his screenshot showed blank gaps and the before/after
   photos not lining up under the handle. Root cause: the two images were
   sized by TWO DIFFERENT RULES - the after image got width:100%/height:auto/
   object-fit:contain, the before image (more specific selector) got
   width:auto/height:100% - so whenever the two photos weren't the same
   shape, which an edit that reframes or crops will do, they rendered at
   different sizes and the wipe stopped comparing the same physical spot.
   The upscaler never showed this because its before/after are the SAME
   photo, so the shapes always happened to match by accident.
   Fix: ONE rule governs both images - absolute, full box, object-fit:cover -
   so before and after always occupy the identical box and the wipe always
   compares the same region, whatever shape either photo actually is. */
.compare-stage>img,.compare-clip img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block}
.compare-stage.zoom{aspect-ratio:auto;max-height:none;overflow:auto}
.compare-stage.zoom>img,.compare-stage.zoom .compare-clip img{position:absolute;top:0;left:0;width:auto;height:auto;max-width:none;object-fit:none}
.compare-clip{position:absolute;inset:0;width:50%;overflow:hidden}
/* V101: the Upscaler shows ONE image at a time and flips with arrows (Kyle, 19-09-2026) */
.flip-stage>img{object-fit:contain;background:#05090c}
.flip-arrow{position:absolute;top:50%;transform:translateY(-50%);width:44px;height:44px;border-radius:999px;
  border:1px solid var(--line);background:rgba(6,12,16,.72);color:#EAF2EE;font-size:26px;line-height:1;
  display:flex;align-items:center;justify-content:center;cursor:pointer;z-index:3;padding:0 0 3px}
.flip-arrow.left{left:10px}.flip-arrow.right{right:10px}
.flip-arrow:hover{background:rgba(6,12,16,.9)}
.flip-arrow:focus-visible{outline:2px solid var(--mint);outline-offset:2px}
.flip-note{position:absolute;left:10px;right:10px;top:10px;padding:7px 10px;border-radius:10px;font-size:12px;
  background:rgba(6,12,16,.78);color:#EAF2EE;text-align:center;z-index:3}
.compare-handle{position:absolute;top:0;bottom:0;left:50%;width:34px;margin-left:-17px;cursor:ew-resize;
  display:grid;place-items:center;z-index:4}
.compare-handle::before{content:"";position:absolute;top:0;bottom:0;width:2px;background:#EAF2EE;opacity:.9}
.compare-handle span{width:34px;height:34px;border-radius:999px;background:#EAF2EE;color:#04130F;
  display:grid;place-items:center;box-shadow:0 6px 18px -6px rgba(0,0,0,.7)}
.compare-handle span::after{content:"\21C4";font-size:15px;font-weight:700}
.compare-handle:focus-visible{outline:2px solid var(--mint);outline-offset:2px}
.compare-tag{position:absolute;bottom:10px;padding:5px 10px;border-radius:999px;font-size:11.5px;
  background:rgba(4,19,15,.72);color:#EAF2EE;z-index:3;pointer-events:none}
.compare-tag.left{left:10px}
.compare-tag.right{right:10px}
.compare-actions{display:flex;gap:8px;justify-content:center;margin-top:12px}
.compare-actions .pill{text-decoration:none}

/* ============================================================
   MODALS / TOAST
   ============================================================ */
.modal{position:fixed;inset:0;z-index:50;display:grid;place-items:center;padding:20px;background:rgba(3,8,12,.55);backdrop-filter:blur(10px);animation:fade .25s ease}
@keyframes fade{from{opacity:0}to{opacity:1}}
.gatecard{position:relative;width:min(440px,100%);padding:30px;border-radius:26px;background:var(--card-solid);border:1px solid var(--line);box-shadow:var(--shadow);animation:pop .3s cubic-bezier(.2,.8,.2,1)}
@keyframes pop{from{transform:translateY(12px) scale(.98);opacity:0}to{transform:none;opacity:1}}
.gatecard h3{margin:0 0 4px;font-size:24px;letter-spacing:-.8px;font-weight:600}
.gatecard p{margin:0 0 22px;color:var(--muted);font-weight:300}
.tabs{display:grid;grid-template-columns:1fr 1fr;padding:3px;border-radius:999px;border:1px solid var(--line-strong);background:var(--card);margin-bottom:18px}
.tabs button{padding:9px;border-radius:999px;font-weight:500;color:var(--muted)}
.tabs button.on{background:var(--ink);color:var(--bg)}
.field{display:grid;gap:6px;margin-bottom:12px}
.field label{font-size:12.5px;color:var(--muted)}
.field input{padding:12px 14px;border-radius:12px;border:1px solid var(--line-strong);background:var(--card);color:var(--ink);font:inherit;outline:0;transition:border-color .2s}
.field input:focus{border-color:var(--mint)}
.gatecard .shimmer{width:100%;justify-content:center;margin-top:8px}
.gatecard .fine{font-size:12.5px;color:var(--muted);text-align:center;margin-top:14px}
.gatecard .fine a{color:var(--em)}
/* 08-09-2026: forgot-password link on the gate modal */
.gatecard .gate-forgot{font-size:12.5px;text-align:right;margin:2px 0 12px}
.gatecard .gate-forgot a{color:var(--em);text-decoration:none}
.gatecard .gate-forgot a:hover{text-decoration:underline}
.playercard{width:min(960px,100%);aspect-ratio:16/9;border-radius:22px;overflow:hidden;background:var(--card-solid);border:1px solid var(--line);box-shadow:var(--shadow);display:grid;place-items:center;color:var(--muted);text-align:center;padding:24px;position:relative}
.playercard iframe{position:absolute;inset:0;width:100%;height:100%;border:0;z-index:2}
/* 17-09-2026 (Kyle): the player sat dark for 2-3s while YouTube/Loom booted.
   hub_v2.js now paints the clicked card's thumbnail as a poster (--poster)
   with a spinner until the iframe fires load. */
.playercard.loading::before{content:"";position:absolute;inset:0;background:var(--poster,none) center/cover no-repeat;opacity:.45;filter:blur(2px);z-index:0}
.playercard.loading::after{content:"";position:absolute;left:50%;top:50%;width:44px;height:44px;margin:-22px 0 0 -22px;border-radius:50%;
  border:3px solid rgba(234,242,238,.25);border-top-color:var(--mint);animation:vspin .8s linear infinite;z-index:1}
.playercard.loading #playerTitle{position:relative;z-index:1;text-shadow:0 2px 12px rgba(0,0,0,.6)}
@keyframes vspin{to{transform:rotate(360deg)}}
@media (prefers-reduced-motion:reduce){.playercard.loading::after{animation:none}}
.close{position:absolute;right:14px;top:14px}
#toast{position:fixed;left:50%;bottom:calc(26px + env(safe-area-inset-bottom));transform:translate(-50%,20px);z-index:60;padding:11px 16px;border-radius:999px;background:var(--ink);color:var(--bg);font-size:13.5px;opacity:0;pointer-events:none;transition:opacity .25s,transform .25s}
#toast.show{opacity:1;transform:translate(-50%,0)}

/* Responsive + motion */
@media (max-width:1000px){
  .hero{grid-template-columns:1fr;padding-top:48px;min-height:0}
  .bento{grid-template-columns:repeat(2,1fr)}.t-factory,.t-2{grid-column:span 2}
  .videos{grid-template-columns:1fr 1fr}
  .plans{grid-template-columns:1fr 1fr}.feed{grid-template-columns:1fr}
  footer .cols{grid-template-columns:1fr 1fr}
  .topnav{display:none}
  /* M1 (05-09-2026): the sidebar used to become a full block stacked ABOVE the
     content here. It is now an off-canvas drawer; .mtop takes over as the
     header and #main gets top padding so nothing hides under the fixed bar. */
  #app{grid-template-columns:1fr}
  .mtop{display:flex}
  .mscrim{display:block}
  /* One rule clears the fixed bar for both views (fixed elements ignore body
     padding, so the bar itself stays put). The bar also replaces .topnav on
     the marketing view, which is display:none at this width - but the token
     pill is meaningless when logged out, so it hides itself. */
  body{padding-top:calc(60px + env(safe-area-inset-top))}
  body[data-logged-in="0"] .mtok{display:none}
  .side{position:fixed;top:0;bottom:0;left:0;z-index:120;width:min(320px,86vw);height:auto;
    border-right:1px solid var(--line);border-bottom:0;background:var(--bg);
    transform:translateX(-105%);transition:transform .28s ease;box-shadow:0 0 60px rgba(0,0,0,.35);
    padding-bottom:calc(16px + env(safe-area-inset-bottom))}
  body.nav-open .side{transform:none}
  body.nav-open{overflow:hidden}
  .grid{grid-template-columns:repeat(2,1fr)}
}
@media (max-width:640px){.grid{grid-template-columns:1fr}.plans{grid-template-columns:1fr}.plan{grid-template-columns:repeat(2,1fr)}.wrap{padding:0 18px}.topbar{padding:12px 16px}}
@media (prefers-reduced-motion:reduce){
  .shimmer::before,.beam::after,.reveal{animation:none}
  .console{transform:none!important}
}

/* ============================================================
   REAL-DATA WIRING ADDITIONS (v2 beta)
   ============================================================ */
.hero-slot{position:absolute;inset:0;display:block}
.hero-slot img{width:100%;height:100%;object-fit:cover;object-position:center top;display:block;opacity:0;transition:opacity .3s}
.hero-slot video{width:100%;height:100%;object-fit:cover;object-position:center top;display:block}
.hero-slot img.ld{opacity:1}
.shot.done .hero-slot img{opacity:1}  /* safety: finished tiles are never left transparent */
.shot span{background:linear-gradient(to top,rgba(4,14,11,.75),transparent);left:0;right:0;bottom:0;padding:14px 8px 6px}
.srcthumb{font-size:10px;text-align:center;padding:4px;line-height:1.2;overflow:hidden}
/* v2 feedback round */
.batchgrid{display:grid;grid-template-columns:220px 1fr;gap:18px;align-items:start;margin-bottom:16px}
@media(max-width:760px){.batchgrid{grid-template-columns:1fr}}
.upzone.square{aspect-ratio:1/1;display:grid;place-items:center;text-align:center;padding:20px}
.charpanel{display:grid;gap:10px}
.charpanel .srcthumb{width:100%;height:auto;aspect-ratio:3/4;font-size:12px}
.chips{position:relative;z-index:50}
.lightbox{position:fixed;inset:0;z-index:200;background:rgba(3,8,12,.9);backdrop-filter:blur(10px);display:grid;place-items:center;padding:28px;opacity:0;pointer-events:none;transition:opacity .18s}
.lightbox.on{opacity:1;pointer-events:auto}
.lightbox img,.lightbox video{max-width:92vw;max-height:84vh;border-radius:14px;border:1px solid var(--line);box-shadow:0 40px 120px -30px rgba(0,0,0,.9)}
.lightbox .lbbar{position:absolute;top:16px;right:20px;display:flex;gap:10px;z-index:2}
.lightbox .lbbar a,.lightbox .lbbar button{padding:9px 16px;border-radius:999px;border:1px solid var(--line-strong);background:rgba(10,18,25,.8);color:var(--ink);font-size:13px;cursor:pointer;text-decoration:none}
.lightbox .lbbar a:hover,.lightbox .lbbar button:hover{border-color:var(--mint)}
.shot{cursor:zoom-in}
.carsrcbig{width:150px;height:200px;border-radius:12px;overflow:hidden;border:1px solid var(--line);flex:none;background:var(--card);display:grid;place-items:center;color:var(--muted);font-size:12px;text-align:center;padding:8px}
.carsrcbig img{width:100%;height:100%;object-fit:cover;padding:0}
.results:empty::after{content:"Nothing here yet.";color:var(--muted);font-size:13px;grid-column:1/-1;padding:14px 0}
.tag.classic{background:transparent;color:var(--muted);border-color:var(--line-strong);font-weight:500}

.gate-error{background:rgba(229,72,77,.12);border:1px solid rgba(229,72,77,.35);color:#E5484D;
  padding:10px 12px;border-radius:10px;font-size:13.5px;margin-bottom:14px}
.gate-error.hidden{display:none}
#gateSubmit[disabled]{opacity:.6;pointer-events:none}

.verify-banner{background:rgba(201,179,126,.14);border:1px solid rgba(201,179,126,.35);color:#9A7F3F;
  padding:10px 14px;border-radius:12px;font-size:13.5px;margin-bottom:16px;text-align:center}
html[data-theme="dark"] .verify-banner{color:var(--gold)}
.verify-banner.hidden{display:none}
.verify-banner a{font-weight:600;text-decoration:underline}

.char-picker{position:absolute;z-index:6;margin-top:8px;min-width:220px;max-height:260px;overflow:auto;
  background:var(--card-solid);border:1px solid var(--line-strong);border-radius:14px;box-shadow:var(--shadow);padding:6px}
.char-picker.hidden{display:none}
.char-picker button{display:flex;align-items:center;gap:9px;width:100%;padding:8px 10px;border-radius:10px;font-size:13.5px;text-align:left}
.char-picker button:hover{background:var(--card)}
.char-picker .cpav{width:24px;height:24px;border-radius:999px;background:linear-gradient(135deg,var(--mint),var(--em));flex:none}
.char-picker .empty{padding:10px;color:var(--muted);font-size:13px}

.plan-result{margin-top:16px;padding-top:16px;border-top:1px solid var(--line)}
.plan-result.hidden{display:none}
.plan-result .reply{color:var(--ink);font-size:14.5px;margin:0 0 12px;font-weight:300;line-height:1.5}
.plan-jobs{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:14px}
.plan-jobs span{padding:6px 11px;border-radius:999px;border:1px solid var(--line-strong);font-size:12.5px;color:var(--muted)}
.plan-actions{display:flex;gap:10px;align-items:center}
.plan-actions .estimate{color:var(--muted);font-size:13px;margin-left:auto}

.progress{display:flex;align-items:center;gap:12px;padding:14px 0}
.progress .bar{flex:1;height:6px;border-radius:999px;background:var(--card);border:1px solid var(--line-strong);overflow:hidden}
.progress .bar i{display:block;height:100%;background:linear-gradient(90deg,var(--mint),var(--em));border-radius:999px;transition:width .4s ease;width:0%}
.progress small{color:var(--muted);white-space:nowrap;font-size:12.5px}

.grid .shot.done img{opacity:0;animation:reveal .5s ease forwards}
@keyframes reveal{to{opacity:1}}
.shot.err{background:rgba(229,72,77,.12);display:grid;place-items:center;color:#E5484D;font-size:11.5px;text-align:center;padding:8px}

@media (max-width:640px){.char-picker{position:fixed;left:16px;right:16px;bottom:calc(16px + env(safe-area-inset-bottom));margin-top:0}}
/* In-app view switching + Images (Batch) view */
.appview{display:none}
.appview.on{display:block}
.upzone{display:block;width:100%;box-sizing:border-box;border:1.5px dashed var(--line-strong);border-radius:16px;padding:26px;text-align:center;color:var(--muted);cursor:pointer;transition:border-color .2s;background:var(--card)}
.upzone:hover,.upzone.drag{border-color:var(--mint);color:var(--ink)}
.upzone input{display:none}
.refgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(96px,1fr));gap:10px;margin-top:14px}
.refgrid .ref{position:relative;aspect-ratio:3/4;border-radius:10px;overflow:hidden;border:1px solid var(--line)}
.refgrid .ref img{width:100%;height:100%;object-fit:cover}
.refgrid .ref button{position:absolute;top:5px;right:5px;width:22px;height:22px;border-radius:999px;background:rgba(4,14,11,.7);color:#fff;display:grid;place-items:center;font-size:12px}
/* 06-09-2026 (Kyle): uploads that could not be added stay VISIBLE. An
   oversized photo previously produced only a toast, which vanished - leaving
   no trace of why it was missing from the grid. */
.refgrid .ref-rejected{grid-column:1/-1;padding:8px 10px;border-radius:var(--r-sm);font-size:12px;
  line-height:1.4;color:var(--muted);border:1px dashed var(--line-strong);background:var(--card)}
.batchbar{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-top:16px}
.batchbar .estimate{color:var(--muted);font-size:13px;margin-left:auto}
.results{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:14px;margin-top:18px}
.results .shot{aspect-ratio:3/4}
.results .shot a{position:absolute;inset:0;z-index:3}
.cancel-link{color:#E5484D;font-size:13px;font-weight:500;cursor:pointer}
.singleframe{max-width:420px;margin:18px auto 0;aspect-ratio:3/4;border-radius:16px;overflow:hidden;position:relative;border:1px solid var(--line);
  background:linear-gradient(160deg,color-mix(in srgb,var(--em-deep) 60%,var(--bg-2)),color-mix(in srgb,var(--mint) 20%,var(--bg-2)))}
.singleframe img{width:100%;height:100%;object-fit:cover;display:block}
.singleframe.empty{display:grid;place-items:center;color:var(--muted);font-size:13px;text-align:center;padding:20px}
.poserows{display:grid;gap:8px;margin-top:14px}
.poserow{display:flex;gap:8px;align-items:center}
.poserow input{flex:1;padding:11px 13px;border-radius:11px;border:1px solid var(--line-strong);background:var(--card);color:var(--ink);font:inherit;font-size:16px;outline:0}
.poserow input:focus{border-color:var(--mint)}
.poserow button{width:34px;height:34px;border-radius:9px;border:1px solid var(--line-strong);color:var(--muted);flex:none}
.poserow button:hover{border-color:#E5484D;color:#E5484D}
.addpose{margin-top:10px;font-size:13.5px;color:var(--muted);border:1px dashed var(--line-strong);border-radius:11px;padding:9px 14px;width:100%}
.addpose:hover{border-color:var(--mint);color:var(--ink)}
.srcpick{display:flex;gap:12px;align-items:center;margin-bottom:14px;flex-wrap:wrap}
.srcthumb{width:64px;height:80px;border-radius:10px;overflow:hidden;border:1px solid var(--line);flex:none;background:var(--card);display:grid;place-items:center;color:var(--muted);font-size:11px}
.srcthumb img{width:100%;height:100%;object-fit:cover}
.videoframe{max-width:460px;margin:18px auto 0;aspect-ratio:9/16;border-radius:16px;overflow:hidden;position:relative;border:1px solid var(--line);
  background:linear-gradient(160deg,color-mix(in srgb,var(--em-deep) 60%,var(--bg-2)),color-mix(in srgb,var(--mint) 20%,var(--bg-2)))}
.videoframe video{width:100%;height:100%;object-fit:cover;display:block;background:#000}
.videoframe.empty{display:grid;place-items:center;color:var(--muted);font-size:13px;text-align:center;padding:20px}
.costline{font-size:13px;color:var(--muted)}
.costline b{color:var(--ink);font-weight:600}
.costbox{margin:14px 0 4px;padding:13px 15px;border-radius:13px;border:1px solid var(--line-strong);
  background:linear-gradient(180deg,rgba(0,212,170,.07),transparent)}
.costbox.short{border-color:rgba(229,72,77,.55);background:linear-gradient(180deg,rgba(229,72,77,.09),transparent)}
.costbox-main{display:flex;justify-content:space-between;align-items:baseline;gap:14px;font-size:14.5px}
.costbox-main b{font-size:17px;font-variant-numeric:tabular-nums}
.costbox-sub{margin-top:5px;font-size:12.5px;color:var(--muted)}
.costbox-sub a{color:var(--mint);font-weight:600}
.plan-actions button[disabled]{opacity:.5;cursor:not-allowed;filter:grayscale(.4)}
.upgrid{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-top:18px}
.upgrid .col{text-align:center}
.upgrid .col small{display:block;color:var(--muted);font-size:12px;margin-bottom:8px}
.upgrid .pane{aspect-ratio:1/1;border-radius:14px;overflow:hidden;border:1px solid var(--line);background:var(--card);display:grid;place-items:center;color:var(--muted);font-size:12.5px;position:relative}
.upgrid .pane img{width:100%;height:100%;object-fit:contain}
.editchain{display:flex;gap:8px;flex-wrap:wrap;margin-top:12px}
.editchain button{font-size:12px;padding:6px 12px;border-radius:999px;border:1px solid var(--line-strong);
  color:var(--muted);background:transparent;cursor:pointer}
.editchain button:hover{border-color:var(--mint);color:var(--ink)}
.editchain .lbl{font-size:12px;color:var(--muted);align-self:center;margin-right:2px}
@media (max-width:640px){.upgrid{grid-template-columns:1fr}}
.settingrow{display:flex;align-items:center;justify-content:space-between;gap:14px;padding:13px 0;border-bottom:1px solid var(--line)}
.settingrow:last-child{border-bottom:0}
.settingrow .lbl{font-size:14px}
.settingrow .lbl small{display:block;color:var(--muted);font-size:12.5px;margin-top:2px}
.toggle{width:46px;height:26px;border-radius:999px;background:var(--card);border:1px solid var(--line-strong);position:relative;flex:none;transition:background .2s,border-color .2s}
.toggle i{position:absolute;top:2px;left:2px;width:20px;height:20px;border-radius:999px;background:var(--muted);transition:transform .2s,background .2s}
.toggle.on{background:rgba(0,212,170,.2);border-color:var(--mint)}
.toggle.on i{transform:translateX(20px);background:var(--mint)}
.slider{display:flex;align-items:center;gap:12px}
.slider input{width:150px;accent-color:var(--mint)}
.slider b{font-variant-numeric:tabular-nums;min-width:28px;text-align:right}
.urlrow{display:flex;gap:10px;flex-wrap:wrap}
.urlrow input{flex:1;min-width:240px;padding:13px 15px;border-radius:12px;border:1px solid var(--line-strong);background:var(--card);color:var(--ink);font:inherit;outline:0}
.urlrow input:focus{border-color:var(--mint)}
.steps{display:grid;gap:8px;margin-top:14px}
.stepline{display:flex;align-items:center;gap:10px;font-size:13.5px;color:var(--muted)}
.stepline .dot{width:7px;height:7px;border-radius:999px;background:var(--line-strong);flex:none}
.stepline.on{color:var(--ink)}
.stepline.on .dot{background:var(--mint);box-shadow:0 0 0 4px var(--glow)}






/* U-UI 1.1 (06-09-2026): advertising banner strip. Server vars existed all
   along and were passed to this template but never rendered. Hidden entirely
   when BANNER_TEXT is empty, so it switches off from Railway with no deploy. */
.promo-strip{display:flex;align-items:center;justify-content:center;gap:10px;padding:9px 40px 9px 16px;position:relative;
  background:color-mix(in srgb,var(--mint) 8%,var(--card));border-bottom:1px solid var(--line);color:var(--muted);font-size:13.5px}
.promo-strip a{color:var(--ink);font-weight:600;text-decoration:underline;text-underline-offset:2px}
.promo-strip button{position:absolute;right:10px;top:50%;transform:translateY(-50%);width:24px;height:24px;border-radius:8px;
  border:0;background:transparent;color:var(--muted);font-size:16px;line-height:1;cursor:pointer}
.promo-strip button:hover{color:var(--ink);background:color-mix(in srgb,var(--line) 50%,transparent)}
/* 06-09-2026 (Kyle): the in-app copy of the strip. Smaller than the hub's, as
   asked - it sits above the stage bar and must not steal height from the work
   area. Sticky so it stays visible while the stage scrolls. */
.promo-strip.app-promo{font-size:12.5px;padding:7px 36px 7px 14px;position:sticky;top:0;z-index:5;
  background:color-mix(in srgb,var(--mint) 7%,var(--bg));backdrop-filter:blur(20px)}
.promo-strip.app-promo button{width:22px;height:22px;right:8px}

/* U-UI 1.5 (06-09-2026): themed scrollbars. None existed anywhere in this
   stylesheet, so browsers drew OS defaults. Thin, token-coloured, and still
   visible - hiding a scrollbar on scrolling content is a usability bug. */
.side,.char-picker{scrollbar-width:thin;scrollbar-color:var(--line-strong) transparent}
.side::-webkit-scrollbar,.char-picker::-webkit-scrollbar{width:8px;height:8px}
.side::-webkit-scrollbar-track,.char-picker::-webkit-scrollbar-track{background:transparent}
.side::-webkit-scrollbar-thumb,.char-picker::-webkit-scrollbar-thumb{background:var(--line-strong);border-radius:999px;border:2px solid transparent;background-clip:content-box}
.side::-webkit-scrollbar-thumb:hover,.char-picker::-webkit-scrollbar-thumb:hover{background:var(--mint)}
.usercard,.tokens{min-width:0}

/* U-UI 2.2 (06-09-2026): empty and error states for History and Saved.
   A blank rectangle used to mean "empty account" AND "request failed". */
.grid-empty{grid-column:1/-1;padding:34px 20px;text-align:center;color:var(--muted);font-size:13.5px;
  border:1px dashed var(--line-strong);border-radius:var(--r-md)}

/* M2 (05-09-2026, touch hygiene): on coarse pointers the nav links computed to
   ~35px tall against the 44px guideline, and bare :hover rules left sticky
   highlight states after a tap. Both fixed here; desktop is untouched. */
@media (pointer:coarse){
  .nav a{padding:12px 10px;min-height:44px}
  .rowbtns .ghost{min-height:42px}
  .tokens .add{width:42px;height:42px}
}
@media (hover:none){
  .nav a:hover{background:transparent;transform:none}
  .ghost:hover,.mtok:hover,.mnav:hover{background:var(--card)}
  .plan:hover,.tile:hover,.video:hover,.shot:hover{transform:none}
}

/* ============================================================
   M6-M9 (05-09-2026, phone pass 2 - from Kyle's device screenshots)
   ============================================================ */

/* M6 - TUTORIAL CARDS. At <=640px the cards kept aspect-ratio:16/9 at half
   width (~150px tall), so the two-line title + subtitle overflowed upward and
   the 58px centred play button landed ON TOP of the words, while the "Soon"
   badge collided with the wrapped title. Screenshots showed all three at once.
   On phones the card becomes a row instead: thumbnail left, text right,
   badge inline. Nothing overlaps because nothing is absolutely positioned. */
@media (max-width:640px){
  .videos{grid-template-columns:1fr;gap:10px}
  .video,.video:first-child{grid-column:auto;grid-row:auto;aspect-ratio:auto;
    display:grid;grid-template-columns:104px minmax(0,1fr);align-items:center;
    gap:12px;padding:10px;min-height:78px}
  .video .hero-slot{position:relative;inset:auto;grid-column:1;grid-row:1;
    width:104px;height:60px;border-radius:10px;overflow:hidden}
  .video .play{position:relative;left:auto;top:auto;transform:none;
    grid-column:1;grid-row:1;justify-self:center;align-self:center;
    width:34px;height:34px;z-index:2}
  .video .play svg{width:13px;height:13px;margin-left:2px}
  .video .meta{grid-column:2;grid-row:1;min-width:0}
  .video .meta b{font-size:14px;line-height:1.25;margin-bottom:2px}
  .video .meta small{font-size:12px;line-height:1.35;display:block}
  .video .len{position:static;display:inline-block;margin-top:4px;font-size:11px}
  .video.pending::after{position:static;display:inline-block;margin-top:4px;font-size:11px}
  .video:hover{transform:none}
}

/* M8 - TEXT DENSITY. `.lead h2` clamps at a 28px FLOOR, so on a 390px screen
   it rendered at 28px with -1.4px tracking and ate a third of the fold; the
   same for the marketing h1 at its 40px floor. Sizes only - no copy is
   removed by CSS, the shortened strings are real elements (see .sm-only). */
@media (max-width:640px){
  .hero h1{font-size:30px;letter-spacing:-.9px;margin-bottom:14px}
  .hero .lede{font-size:15px;margin-bottom:20px}
  .lead h2{font-size:22px;letter-spacing:-.6px;line-height:1.15}
  .lead p{font-size:13.5px;line-height:1.45}
  .factory .lead{margin:12px auto 18px}
  .section .sub{font-size:14px}
  .upzone{padding:16px;font-size:13.5px}
  .composer textarea{min-height:76px;font-size:16px}
  .stagebar h1{font-size:16px}
  .lg-only{display:none}
  .sm-only{display:inline}
}
.sm-only{display:none}
.lg-only{display:inline}

/* M9 - LIGHTBOX CHROME. The bar used `color:var(--ink)` over a HARDCODED dark
   pill, so in light theme it painted near-black text on near-black - which is
   exactly what the screenshots show. The scrim is always dark, so the chrome
   is now fixed light-on-dark rather than theme-following. Also given a
   safe-area top inset (it was sliding under the iOS status bar) and 44px
   targets. */
.lightbox .lbbar{top:calc(16px + env(safe-area-inset-top))}
.lightbox .lbbar a,.lightbox .lbbar button{
  background:rgba(6,12,18,.92);color:#EAF2EE;border-color:rgba(234,242,238,.34)}
.lightbox .lbbar #lbDownload{background:var(--mint);color:#04130F;border-color:var(--mint);font-weight:500}
@media (max-width:640px){
  .lightbox{padding:12px}
  .lightbox .lbbar{left:12px;right:12px;top:auto;
    bottom:calc(12px + env(safe-area-inset-bottom));gap:8px}
  .lightbox .lbbar a,.lightbox .lbbar button{
    flex:1 1 0;min-width:0;min-height:46px;display:flex;align-items:center;
    justify-content:center;font-size:14px;padding:9px 10px;text-align:center}
  .lightbox img,.lightbox video{max-width:96vw;max-height:70vh}
}

/* RS length slider + Voice Swap (05-09-2026) */
.lenrow{display:flex;align-items:center;gap:12px;margin-top:10px}
.lenrow input[type=range]{flex:1;accent-color:var(--mint)}
.lenrow #refLenOut{min-width:44px;font-size:13.5px;color:var(--ink)}
.ghost.mini{padding:7px 12px;font-size:12.5px}
#view-voiceswap audio{margin-top:6px;border-radius:12px}
.lenhint{margin:6px 0 0;font-size:12.5px;color:var(--muted)}
#vsVoice option{background:var(--card-solid);color:var(--ink)}

/* 05-09-2026 (Kyle): play the uploaded video/voice before spending tokens. */
.srcpreview{margin:10px 0 2px;padding:10px;border:1px solid var(--line);border-radius:12px;background:var(--panel-2,rgba(255,255,255,.02))}
.srcpreview video,.srcpreview audio,.srcpreview img{display:block;margin:0 auto}
.previewname{margin-top:6px;font-size:12px;color:var(--muted);text-align:center;word-break:break-all}
#refEstimate{margin:6px 0 0}

/* 05-09-2026 (Kyle): play the uploaded video / voice before spending on it */
.previewrow{display:flex;gap:12px;flex-wrap:wrap;margin:6px 0 2px}
.srcpreview{flex:1 1 260px;min-width:0;background:var(--bg-2);border:1px solid var(--line,rgba(255,255,255,.08));border-radius:12px;padding:8px}
.srcpreview video,.srcpreview audio,.srcpreview img{display:block;max-width:100%}
.previewname{font-size:12px;color:var(--muted,#8a94a6);margin-top:6px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* 05-09-2026 (Kyle): finished shots appear one by one instead of a bar. */
.ftiles{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:10px;margin-top:12px}
.ftile{position:relative;aspect-ratio:9/16;border:1px solid var(--line);border-radius:12px;overflow:hidden;display:flex;align-items:center;justify-content:center;background:var(--panel-2,rgba(255,255,255,.02));text-decoration:none;color:var(--muted);font-size:12px;text-align:center}
.ftile img,.ftile video{width:100%;height:100%;object-fit:cover;display:block}
.ftile.waiting .spin{width:22px;height:22px;border-radius:50%;border:2px solid var(--line);border-top-color:var(--em);animation:fspin 1s linear infinite}
@keyframes fspin{to{transform:rotate(360deg)}}
@media (prefers-reduced-motion:reduce){.ftile.waiting .spin{animation:none}}
.ftile.failed{color:var(--muted);padding:8px}

/* 05-09-2026 (Kyle): up to 5 edits at once, each with its own wipe */
.editmulti{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(300px,100%),1fr));gap:14px;margin-top:14px}
.editmulti .emtile{border:1px solid var(--line);border-radius:var(--r-md);padding:10px;background:var(--panel-2,rgba(255,255,255,.02))}
.editmulti .compare-stage{max-height:340px}
.editmulti .compare-stage img{max-height:340px}
.editmulti .emwait{display:flex;align-items:center;justify-content:center;min-height:180px;color:var(--muted)}
.editmulti .emactions{display:flex;gap:8px;margin-top:8px;align-items:center}
.editmulti .emactions .pill{font-size:12px;padding:6px 10px}
.editmulti .emerr{color:var(--muted);padding:12px}

/* 05-09-2026 (Kyle): real outputs on the home screen, fast */
.showcase-strip{display:grid;grid-template-columns:repeat(4,1fr);gap:14px}
.showcase-strip img{width:100%;height:auto;border-radius:var(--r-md);border:1px solid var(--line);display:block}
@media (max-width:900px){.showcase-strip{grid-template-columns:repeat(2,1fr)}}

/* ============================================================
   M11 / V22 (06-09-2026, Kyle): "on some phones I have to zoom out and it's
   hard to navigate - the UI doesn't fit the screen."
   With a correct viewport meta (every template has one), that symptom has
   exactly one mechanism: SOME element renders wider than the viewport, the
   page gains horizontal scroll, and the phone browser opens zoomed-out so
   the too-wide layout fits. The classic stylesheet has carried a page-level
   lock against this since v47.443 (style.css, <=992px: html,body
   overflow-x:hidden). The new interface - which is what / serves - never
   got it, so any residual too-wide element (any device, any future edit)
   still forced the zoom-out. Same proven lock, scoped to the same <=1000px
   breakpoint where this stylesheet's own mobile layout takes over, so
   desktop is untouched. overflow-x:clip is preferred over hidden where
   supported because clip does not create a scroll container - the sticky
   promo strip and stagebar keep working; hidden remains as the fallback
   for older browsers.
   ============================================================ */
@media (max-width:1000px){
  html,body{overflow-x:hidden;overflow-x:clip;max-width:100%}
}

/* U-UI 4.1 (06-09-2026, Kyle): "these Every tool one character buttons do
   nothing and look shit". They were inert cards with a faint radial glow and
   no affordance at all - nothing said "click me" and nothing happened when
   you did. These styles apply ONLY to tiles that now actually route
   somewhere ([data-tool]/[data-dest]), so a non-interactive card can never
   pretend to be clickable. Layered on top of the existing .spot glow, never
   replacing it. */
#hub .tile[data-tool],#hub .tile[data-dest]{
  cursor:pointer;
  transition:transform .28s cubic-bezier(.2,.7,.3,1),border-color .28s,box-shadow .28s;
  will-change:transform}
/* A soft top sheen so a flat card reads as a raised surface. */
#hub .tile[data-tool]::after,#hub .tile[data-dest]::after{
  content:"";position:absolute;inset:0;pointer-events:none;border-radius:inherit;
  background:linear-gradient(180deg,rgba(255,255,255,.06),transparent 38%);
  opacity:.9;transition:opacity .28s}
#hub .tile[data-tool]:hover,#hub .tile[data-dest]:hover,
#hub .tile[data-tool]:focus-visible,#hub .tile[data-dest]:focus-visible{
  transform:translateY(-4px);
  border-color:rgba(63,199,160,.55);
  box-shadow:0 18px 40px -18px rgba(0,0,0,.75),0 0 0 1px rgba(63,199,160,.18) inset,
             0 0 34px -12px rgba(63,199,160,.45)}
#hub .tile[data-tool]:active,#hub .tile[data-dest]:active{transform:translateY(-1px)}
#hub .tile[data-tool]:focus-visible,#hub .tile[data-dest]:focus-visible{
  outline:2px solid rgba(63,199,160,.85);outline-offset:3px}
/* An arrow that slides in on hover - the clearest "this goes somewhere" cue. */
#hub .tile[data-tool] .foot::after,#hub .tile[data-dest] .foot::after,
#hub .tile.imaged[data-tool] .body .foot::after{
  content:"\2192";margin-left:auto;padding-left:12px;font-size:16px;line-height:1;
  color:rgba(63,199,160,.95);opacity:0;transform:translateX(-6px);
  transition:opacity .28s,transform .28s}
#hub .tile[data-tool]:hover .foot::after,#hub .tile[data-dest]:hover .foot::after,
#hub .tile[data-tool]:focus-visible .foot::after,#hub .tile[data-dest]:focus-visible .foot::after{
  opacity:1;transform:translateX(0)}
/* The image-backed tiles lift their photo slightly instead of the whole card
   feeling detached from its background. */
#hub .tile.imaged[data-tool] .pmedia{transition:transform .45s cubic-bezier(.2,.7,.3,1)}
#hub .tile.imaged[data-tool]:hover .pmedia{transform:scale(1.02)}
/* Respect people who asked their OS for less motion. */
@media (prefers-reduced-motion:reduce){
  #hub .tile[data-tool],#hub .tile[data-dest],
  #hub .tile.imaged[data-tool] .pmedia,
  #hub .tile[data-tool] .foot::after,#hub .tile[data-dest] .foot::after{
    transition:none}
  #hub .tile[data-tool]:hover,#hub .tile[data-dest]:hover,
  #hub .tile.imaged[data-tool]:hover .pmedia{transform:none}}

/* U-UI 4.2 (06-09-2026, Kyle: "no graphics of faded in models in the
   background"). The tool tiles carried empty hero-slot placeholders that
   nothing ever filled, so every card rendered as a flat gradient. The
   photos now sit behind the existing gradient overlays, faded so the text
   on top stays readable, and lift slightly with the card on hover. */
#hub .tile .hero-slot img{
  position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  object-position:center 18%;opacity:.88;
  transition:opacity .35s,transform .45s cubic-bezier(.2,.7,.3,1)}
#hub .tile[data-tool]:hover .hero-slot img,
#hub .tile[data-tool]:focus-visible .hero-slot img{opacity:1}
/* The big Factory card carries more text, so its photo sits back further
   and its existing left-to-right scrim does the rest of the work. */
#hub .t-factory .media .hero-slot img{opacity:.90;object-position:70% 20%}
#hub .t-factory[data-tool]:hover .media .hero-slot img{opacity:1}

/* Tutorial cards: real preview frames instead of an empty green box. The
   thumbnail sits under the existing play button and title overlay. */
.video .hero-slot{position:absolute;inset:0;display:block;z-index:0}
.video .hero-slot img.vthumb{
  width:100%;height:100%;object-fit:cover;object-position:center;display:block;
  opacity:.72;transition:opacity .35s,transform .5s cubic-bezier(.2,.7,.3,1)}
.video:hover .hero-slot img.vthumb{opacity:.92;transform:scale(1.04)}
/* Keep the title/badges above the new thumbnail layer. */
.video .play,.video .meta,.video .len{z-index:2}
.video::after{z-index:2}
/* A scrim so white title text stays readable on a bright frame. */
.video .hero-slot::after{
  content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(to top,rgba(4,14,11,.86),rgba(4,14,11,.15) 55%,rgba(4,14,11,.30))}
@media (prefers-reduced-motion:reduce){
  #hub .tile .hero-slot img,.video .hero-slot img.vthumb{transition:none}
  .video:hover .hero-slot img.vthumb{transform:none}}

/* U-UI 4.3 (06-09-2026, Kyle supplied a target mockup): EVERY tool card
   carries its own photo. Nano Create, Images and Connect to Claude had no
   media layer in the markup at all, which is why they stayed flat.
   The first attempt buried the photos at .5 opacity under a near-black
   scrim - Kyle's mockup shows them clearly, so the wash here is much
   lighter and the photo is the point, not a texture. */
#hub .tile .tmedia{position:absolute;inset:0;z-index:0;overflow:hidden;border-radius:inherit}
#hub .tile .tmedia .hero-slot{position:absolute;inset:0;display:block}
#hub .tile .tmedia img{
  position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  object-position:var(--fpos,center 18%);
  /* U-UI 4.4 (06-09-2026, Kyle supplied a detailed spec): per-card
     visibility instead of one flat number for every card - Factory reads
     confident (~62%), Connect to Claude stays understated (~28%). --vopac
     is set inline per card; this is the base state, hover raises it. */
  opacity:var(--vopac,.55);
  transition:opacity .5s,transform .6s cubic-bezier(.2,.7,.3,1)}
/* A green wash that tints rather than hides: strong enough behind the copy
   on the left, clearing away toward the top-right where the photo reads. */
#hub .tile .tmedia::after{
  content:"";position:absolute;inset:0;pointer-events:none;
  background:
    linear-gradient(100deg,rgba(6,26,20,.93) 22%,rgba(6,26,20,.72) 48%,rgba(6,26,20,.34) 78%,rgba(6,26,20,.18)),
    linear-gradient(to top,rgba(5,18,14,.80),rgba(5,18,14,.20) 52%,transparent),
    linear-gradient(0deg,rgba(26,92,70,.34),rgba(26,92,70,.34))}
#hub .tile>div:not(.tmedia),#hub .tile>.foot{position:relative;z-index:1}
#hub .tile[data-tool]:hover .tmedia img,
#hub .tile[data-dest]:hover .tmedia img,
#hub .tile[data-tool]:focus-visible .tmedia img{
  opacity:calc(var(--vopac,.55) + .22);transform:scale(1.02)}
/* The photo-backed cards get the same treatment and honour their own crop. */
#hub .tile.imaged .pmedia img{
  object-position:var(--fpos,center top);opacity:var(--vopac,.6);
  transition:opacity .5s,transform .6s cubic-bezier(.2,.7,.3,1)}
#hub .tile.imaged[data-tool]:hover .pmedia img,
#hub .tile.imaged[data-tool]:focus-visible .pmedia img{
  opacity:calc(var(--vopac,.6) + .2)}
#hub .tile.imaged .pmedia::after{
  background:
    linear-gradient(to top,rgba(5,18,14,.90) 10%,rgba(5,18,14,.42) 52%,rgba(5,18,14,.12)),
    linear-gradient(0deg,rgba(26,92,70,.30),rgba(26,92,70,.30))}
@media (prefers-reduced-motion:reduce){
  #hub .tile .tmedia img{transition:none}
  #hub .tile[data-tool]:hover .tmedia img,
  #hub .tile[data-dest]:hover .tmedia img{transform:none}}

/* U-UI 4.4: responsive crop - a --fpos-m override shifts the visible focal
   point further toward the photo on narrow cards, so a face is never
   pushed out of frame when the card gets thinner on mobile. Falls back to
   the desktop --fpos when a card has no override. */
@media (max-width:720px){
  #hub .tile .tmedia img,#hub .tile.imaged .pmedia img{
    object-position:var(--fpos-m,var(--fpos,center 18%))}
}

/* 06-09-2026 (Kyle): Factory result tiles and recent tiles zoom in place
   instead of opening a raw image URL in a new tab. They were anchors, so
   they got a pointer cursor for free; as divs they need it stated, plus a
   visible focus ring since they are now keyboard-operable. */
.ftile[data-zoom],.shot[data-zoom]{cursor:zoom-in}
.ftile[data-zoom]:focus-visible,.shot[data-zoom]:focus-visible{
  outline:2px solid var(--em,#10B981);outline-offset:2px}

/* 06-09-2026 (Kyle): save controls for video. The video view had no save
   button at all and the Factory video tiles relied on the browser menu,
   which is inconsistent on Safari and effectively missing on iOS. */
.vid-save{margin-top:10px}
.ftile-dl{position:absolute;right:8px;bottom:8px;z-index:2;
  width:30px;height:30px;border-radius:9px;line-height:1;font-size:15px;
  background:rgba(5,18,14,.72);color:var(--ink,#EAF2EE);
  border:1px solid var(--line,rgba(234,242,238,.18));
  backdrop-filter:blur(6px);cursor:pointer;
  display:flex;align-items:center;justify-content:center;
  transition:background .2s,border-color .2s,transform .2s}
.ftile-dl:hover{background:rgba(0,212,170,.22);border-color:rgba(0,212,170,.55);transform:translateY(-1px)}
.ftile-dl:focus-visible{outline:2px solid var(--em,#10B981);outline-offset:2px}

/* 06-09-2026 (Kyle): OneLinkk engine picker. Both engines were already
   wired server-side with different token costs; only the control was
   missing. Provider names deliberately absent - the price is the signal. */
.segbtns{display:flex;gap:8px;flex-wrap:wrap}
.segbtn{display:flex;flex-direction:column;align-items:flex-start;gap:2px;
  padding:8px 14px;border-radius:12px;cursor:pointer;
  background:rgba(255,255,255,.04);border:1px solid var(--line);
  color:var(--muted);font-weight:600;font-size:13px;line-height:1.2;
  transition:background .2s,border-color .2s,color .2s}
.segbtn small{font-weight:500;font-size:11px;opacity:.8}
.segbtn:hover{border-color:var(--line-strong);color:var(--ink)}
.segbtn.on{background:rgba(0,212,170,.14);border-color:rgba(0,212,170,.55);color:var(--ink)}
.segbtn:focus-visible{outline:2px solid var(--em,#10B981);outline-offset:2px}

/* 06-09-2026 (Kyle): video results - enlarge and save controls on the
   single-video frame, matching the tile controls used elsewhere. */
.videoframe{position:relative}
.ftile-zoom{position:absolute;right:46px;bottom:8px;z-index:2;
  width:30px;height:30px;border-radius:9px;line-height:1;font-size:15px;
  background:rgba(5,18,14,.72);color:var(--ink,#EAF2EE);
  border:1px solid var(--line,rgba(234,242,238,.18));
  backdrop-filter:blur(6px);cursor:zoom-in;
  display:flex;align-items:center;justify-content:center;
  transition:background .2s,border-color .2s,transform .2s}
.ftile-zoom:hover{background:rgba(0,212,170,.22);border-color:rgba(0,212,170,.55);transform:translateY(-1px)}
.ftile-zoom:focus-visible{outline:2px solid var(--em,#10B981);outline-offset:2px}
#vidResults .shot .ftile-dl{right:8px;bottom:8px}
#vidRecentHead{margin-top:26px}

/* 07-09-2026 (Kyle): "The save video button is tiny and not good" - real
   button row under the finished video. Tiles keep the compact overlay but
   it grows to a comfortable tap size. A dead tile also gets a proper
   surface so it can never read as a hole in the grid. */
.vid-actions{display:flex;gap:10px;margin-top:12px;flex-wrap:wrap}
.vid-actions .btn.ghost{background:rgba(255,255,255,.05);border:1px solid var(--line-strong);
  border-radius:12px;padding:10px 18px;font-weight:600;color:var(--ink);cursor:zoom-in}
.vid-actions .btn.ghost:hover{border-color:rgba(0,212,170,.55);background:rgba(0,212,170,.10)}
.ftile-dl{width:36px;height:36px;font-size:17px}
.shot .ftile-dl{right:8px;bottom:36px}
.shot.gone{background:linear-gradient(180deg,rgba(255,255,255,.035),rgba(255,255,255,.015));
  border:1px dashed var(--line-strong)}

/* 06-09-2026 (Kyle: "history doesnt look fixed" - wall of dark tiles and a
   perceived hole). Tiles previously had NO background of their own, so any
   tile whose media was still loading, lazy, or dead rendered as a void.
   Every tile now reads as an intentional card in every state: a panel
   surface, a soft loading sheen while media is on its way, and the
   existing "No longer available" treatment when it is dead. */
.results .shot,.grid .shot{
  background:
    radial-gradient(120% 90% at 50% 0%,rgba(0,212,170,.07),transparent 60%),
    var(--card-solid,#0C151D)}
.results .shot:not(.gone)::after,.grid .shot:not(.gone)::after{
  content:"";position:absolute;inset:0;pointer-events:none;opacity:0;
  background:linear-gradient(110deg,transparent 30%,rgba(234,242,238,.05) 50%,transparent 70%);
  background-size:220% 100%;animation:shotsheen 1.4s linear infinite}
.results .shot:not(.ready):not(.gone)::after,.grid .shot:not(.ready):not(.gone)::after{opacity:1}
@keyframes shotsheen{from{background-position:130% 0}to{background-position:-90% 0}}
@media (prefers-reduced-motion:reduce){
  .results .shot::after,.grid .shot::after{animation:none}}

/* 07-09-2026 (Kyle: "make sure this whole software works well on the
   phone"). Touch-target pass on the controls added this sprint. Apple and
   Android guidance is a 44px minimum; these were 30px, fine for a mouse,
   fiddly for thumbs. Scoped to coarse pointers so desktop keeps the
   tighter look. */
@media (pointer:coarse){
  .ftile-dl,.ftile-zoom{width:42px;height:42px;font-size:18px;border-radius:12px}
  .ftile-zoom{right:52px}
  .segbtn{padding:11px 16px;min-height:44px}
  .pill{padding:10px 14px;min-height:40px}
  #vidSave.vid-save{min-height:44px;padding:0 20px}
  /* history/recent tiles are role=button - give the whole tile obvious
     affordance rather than relying on hover states phones don't have */
  .shot[data-zoom]{-webkit-tap-highlight-color:rgba(0,212,170,.18)}
}

/* 07-09-2026 (Kyle): Edit Image flick viewer - one image at a time,
   arrows to flip Before/After. Replaces the drag-wipe on this view only. */
.flick{position:relative;border-radius:16px;overflow:hidden;background:var(--card-solid,#0C151D);
  border:1px solid var(--line);display:flex;align-items:center;justify-content:center;min-height:280px}
.flick img{display:block;max-width:100%;max-height:72vh;cursor:pointer}
.flick.zoomfull img{max-height:none}
.flick-tag{position:absolute;left:12px;bottom:12px;z-index:2;
  background:rgba(5,18,14,.78);border:1px solid var(--line);color:var(--ink);
  font-size:12px;font-weight:600;padding:4px 12px;border-radius:999px;backdrop-filter:blur(6px)}
.flick-arrow{position:absolute;top:50%;transform:translateY(-50%);z-index:2;
  width:44px;height:44px;border-radius:50%;font-size:26px;line-height:1;
  display:flex;align-items:center;justify-content:center;cursor:pointer;
  background:rgba(5,18,14,.72);color:var(--ink);border:1px solid var(--line);
  backdrop-filter:blur(6px);transition:background .2s,border-color .2s,opacity .2s}
.flick-arrow.left{left:10px}
.flick-arrow.right{right:10px}
.flick-arrow:hover:not(:disabled){background:rgba(0,212,170,.22);border-color:rgba(0,212,170,.55)}
.flick-arrow:disabled{opacity:.28;cursor:default}
.flick-arrow:focus-visible{outline:2px solid var(--em,#10B981);outline-offset:2px}
.flick-dots{position:absolute;bottom:14px;left:50%;transform:translateX(-50%);
  display:flex;gap:7px;z-index:2}
.flick-dots i{width:7px;height:7px;border-radius:50%;background:rgba(234,242,238,.28);
  transition:background .2s,transform .2s}
.flick-dots i.on{background:var(--mint,#00D4AA);transform:scale(1.25)}


/* 11-09-2026 (Kyle): subscription management in the hub. The hub had no way
   to cancel at all - only the classic page did - so a subscriber who never
   leaves the hub was stuck. */
.usercard .subman{width:100%;margin-top:10px;padding:8px 10px;border-radius:10px;
  background:transparent;border:1px solid rgba(255,255,255,.14);color:var(--muted);
  font:inherit;font-size:12.5px;cursor:pointer}
.usercard .subman:hover{color:var(--ink);border-color:rgba(255,255,255,.28)}
.submodal{position:fixed;inset:0;z-index:80;display:grid;place-items:center;
  background:rgba(2,10,8,.72);padding:20px}
.submodal.hidden{display:none}
.subcard{position:relative;width:min(420px,100%);padding:22px 22px 18px;border-radius:16px}
.subcard h3{margin:0 0 6px;font-size:18px}
.subcard .subline{margin:0 0 14px;color:var(--muted);font-size:13.5px}
.subcard .subx{position:absolute;right:10px;top:8px;background:none;border:0;
  color:var(--muted);font-size:22px;line-height:1;cursor:pointer}
.subactions{display:flex;gap:10px;flex-wrap:wrap}
.subcancel,.subkeep{padding:9px 14px;border-radius:10px;font:inherit;font-size:13.5px;cursor:pointer}
.subcancel{background:transparent;border:1px solid rgba(255,120,120,.45);color:#ff9a9a}
.subcancel:hover{border-color:rgba(255,120,120,.75)}
.subcancel[disabled]{opacity:.55;cursor:default}
.subkeep{background:var(--em);border:0;color:#04130F;font-weight:600}
.subconfirm{margin-top:14px;padding-top:14px;border-top:1px solid rgba(255,255,255,.08)}
.subconfirm.hidden{display:none}
.subconfirm p{margin:0 0 12px;font-size:13px;color:var(--muted)}
.suberr{margin:0 0 12px;font-size:13px;color:#ff9a9a}
.suberr.hidden{display:none}

/* 11-09-2026 (Kyle): upload your own reference in Solo, instead of being
   limited to a saved character. */
#sUploadChip.hidden{display:none}
#sUploadChip .av{background-size:cover;background-position:center}
#sUploadChip button{margin-left:6px;background:none;border:0;color:inherit;
  font-size:15px;line-height:1;cursor:pointer;opacity:.7}
#sUploadChip button:hover{opacity:1}
#sUploadToggle{cursor:pointer}

/* 11-09-2026 (Kyle): a single-image generation drew an empty 0%-wide bar for
   its whole run, so it looked like nothing was happening. Indeterminate bar
   + animated dots until there is real progress to measure. */
.progress .bar.indet{position:relative;overflow:hidden}
.progress .bar.indet i{position:absolute;left:0;top:0;bottom:0;width:40%;
  border-radius:inherit;animation:amf-indet 1.15s ease-in-out infinite}
@keyframes amf-indet{
  0%{transform:translateX(-100%)} 100%{transform:translateX(250%)}
}
.progress small.gen .dots b{animation:amf-dot 1.4s infinite;opacity:.2;font-weight:700}
.progress small.gen .dots b:nth-child(2){animation-delay:.2s}
.progress small.gen .dots b:nth-child(3){animation-delay:.4s}
@keyframes amf-dot{0%,60%,100%{opacity:.2} 30%{opacity:1}}
@media (prefers-reduced-motion: reduce){
  .progress .bar.indet i{animation:none;width:100%;opacity:.45}
  .progress small.gen .dots b{animation:none;opacity:.6}
}

/* V85 (17-09-2026): first-run checklist on The Factory, sibling of the
   verify banner. Hidden by default; hub_v2.js reveals it from /api/onboarding. */
.onboard-card{background:color-mix(in srgb,var(--mint) 9%,var(--bg-2));border:1px solid color-mix(in srgb,var(--mint) 35%,var(--line));border-radius:14px;padding:14px 16px;margin-bottom:16px}
.onboard-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}
.onboard-head b{font-weight:600}
.onboard-dismiss{background:transparent;border:0;color:var(--muted);font-size:18px;line-height:1;cursor:pointer;padding:2px 6px;border-radius:6px}
.onboard-dismiss:hover{color:var(--ink);background:var(--card)}
.onboard-steps{margin:0;padding-left:20px;display:grid;gap:8px}
.onboard-steps li a{font-weight:600;color:var(--ink);text-decoration:none}
.onboard-steps li a:hover{color:var(--mint)}
.onboard-steps li small{display:block;color:var(--muted);font-size:12.5px}
.onboard-steps li.done a{text-decoration:line-through;color:var(--muted)}
.onboard-steps li.done::marker{color:var(--mint)}
@media (max-width:640px){.onboard-card{padding:12px}}
