/* ============================================================
   כתוביות לעצלנים
   Warm black, cream, one hot orange — deliberately not the
   blue-on-slate that every AI tool ships with.
   ============================================================ */
:root{
  --ink:#0D0B0A; --surface:#17130F; --raised:#221C17; --high:#2E251E;
  --line:rgba(255,240,225,.09); --line2:rgba(255,240,225,.17);
  --cream:#F7EFE6; --dim:#A79684; --faint:#95816F;
  --hot:#FF5A1F; --hot-dim:rgba(255,90,31,.15);
  --sun:#FFC93C; --mint:#3ED598; --rose:#FF4D6D; --sky:#5AA9FF;
  --r:20px; --shadow:0 22px 54px rgba(0,0,0,.6);
  /* Measured from the real nav by syncChrome(); the sticky layers below key
     off it instead of guessing. The literal here is only the value used for
     the first frame, before JS runs. */
  --navH:66px;
  /* Motion. Two curves, both strong: the stock CSS easings are too weak to
     read as intentional. Entering = ease-out (it moves the instant you look);
     moving across the screen = ease-in-out (it accelerates and settles). */
  --ease-out:cubic-bezier(.23,1,.32,1);
  --ease-in-out:cubic-bezier(.77,0,.175,1);
}
/* dvh follows the browser chrome as it collapses; vh does not, which is why
   a "42vh" player was taller than 42% of what the user could actually see. */
@supports (height:100dvh){ :root{ --vh:1dvh } }
@supports not (height:100dvh){ :root{ --vh:1vh } }
*{box-sizing:border-box}
[hidden]{display:none!important}
html{-webkit-text-size-adjust:100%}
body{margin:0;background:var(--ink);color:var(--cream);
  font:15px/1.65 "Rubik",-apple-system,"SF Hebrew",system-ui,sans-serif;
  -webkit-font-smoothing:antialiased;padding-bottom:env(safe-area-inset-bottom)}
h1,h2,h3,.display{font-family:"Secular One","Rubik",system-ui,sans-serif;font-weight:400}
button{font:inherit;color:inherit;border:0;background:none;cursor:pointer}
::-webkit-scrollbar{width:10px;height:10px}
::-webkit-scrollbar-thumb{background:#332921;border-radius:99px;border:3px solid var(--ink)}
svg{display:block}

/* ---------- chrome ---------- */
.nav{position:sticky;top:0;z-index:40;display:flex;align-items:center;gap:12px;
  padding:calc(12px + env(safe-area-inset-top)) 24px 12px;
  background:rgba(13,11,10,.86);backdrop-filter:blur(20px);border-bottom:1px solid var(--line)}
.meChip{display:inline-flex;align-items:center;gap:7px;max-width:140px;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.meChip img{width:20px;height:20px;border-radius:50%;flex:0 0 auto;display:block}
@media(max-width:960px){.meChip{max-width:92px;padding:7px 9px}
  .meChip img{width:18px;height:18px}}
.touchOnly{display:none}
body.touch .touchOnly{display:inline-flex}
.brand{display:flex;align-items:center;gap:10px;font-family:"Secular One",sans-serif;
  font-size:19px;letter-spacing:-.01em;flex:0 0 auto;white-space:nowrap}
/* Under this the wordmark is the first thing to go: the logo still says
   whose site this is, and the row has three other things to fit. */
.brand .mark{height:26px;width:auto;display:block;flex:0 0 auto}
.nav .sp{margin-inline-start:auto;display:flex;align-items:center;gap:6px}
/* The brand and the way back travel together at the start of the row. */
.navSide{display:flex;align-items:center;gap:12px;min-width:0}
/* Centring the quota strip takes three groups of EQUAL weight, not an auto
   margin: with the sides free to be different widths, "push it along" only
   ever lands it off-centre by however much the brand and the account chip
   differ. The sides flex, the strip does not, and the middle is the middle.
   Laptop and up only — the phone nav is a one-row budget tuned to the pixel
   further down, and it has its own answer (the strip sheds its words). */
@media (min-width:961px){
  .navSide{flex:1 1 0}
  .nav .sp{flex:1 1 0;justify-content:flex-end;margin-inline-start:0}
  .nav .quotaBar{flex:0 0 auto;margin-inline:0}
}
.activityToggle{position:relative;width:42px;height:42px;flex:0 0 auto;display:grid;
  place-items:center;border-radius:12px;color:var(--dim);border:1px solid transparent;
  transition:color .15s,background .15s,border-color .15s}
.activityToggle:hover,.activityToggle[aria-expanded="true"]{color:var(--cream);
  background:var(--raised);border-color:var(--line)}
.activityToggle svg{width:20px;height:20px;fill:none;stroke:currentColor;stroke-width:2;
  stroke-linecap:round;stroke-linejoin:round}
.activityBadge{position:absolute;top:2px;inset-inline-end:1px;min-width:17px;height:17px;
  padding:0 4px;border-radius:99px;display:grid;place-items:center;background:var(--hot);
  color:#1A0E06;font-size:10px;font-weight:800;line-height:1;border:2px solid var(--ink)}
.activityBack{position:fixed;inset:0;z-index:58;background:rgba(0,0,0,.24)}
.activityPanel{position:fixed;z-index:59;top:calc(var(--navH) + 8px);
  inset-inline-end:16px;width:min(390px,calc(100vw - 32px));max-height:calc(100 * var(--vh) - var(--navH) - 24px);
  display:flex;flex-direction:column;background:var(--surface);border:1px solid var(--line2);
  border-radius:16px;box-shadow:0 22px 54px rgba(0,0,0,.64);overflow:hidden;
  animation:activityIn .18s cubic-bezier(.16,1,.3,1)}
@keyframes activityIn{from{opacity:0;transform:translateY(-7px) scale(.985)}
  to{opacity:1;transform:none}}
.activityHead{display:flex;align-items:flex-start;gap:14px;padding:18px 18px 15px;
  border-bottom:1px solid var(--line)}
.activityHead > div{flex:1;min-width:0}
.activityHead h2{font-family:"Rubik",sans-serif;font-size:17px;font-weight:700;
  line-height:1.35;margin:0}
.activityHead p{font-size:12.5px;color:var(--dim);margin:3px 0 0}
.activityClose{width:36px;height:36px;flex:0 0 auto;display:grid;place-items:center;
  border-radius:11px;color:var(--dim);font-size:24px;line-height:1}
.activityClose:hover{background:var(--raised);color:var(--cream)}
.activityList{overflow:auto;overscroll-behavior:contain;padding:0 16px}
.activityItem{display:flex;align-items:flex-start;gap:12px;padding:14px 0;
  border-bottom:1px solid var(--line)}
.activityItem:last-child{border-bottom:0}
.activityItem > img{width:52px;height:68px;flex:0 0 auto;border-radius:10px;
  object-fit:cover;background:#100D0B}
.activityMain{flex:1;min-width:0}
.activityName{display:block;font-size:13.5px;font-weight:600;white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis}
.activityState{display:block;margin-top:2px;font-size:12px;color:var(--dim)}
.activityState.work{color:var(--sun)}
.activityState.done{color:var(--mint)}
.activityState.failed{color:var(--rose)}
.activityProgress{height:5px;margin-top:9px;border-radius:99px;background:var(--high);overflow:hidden}
.activityProgress i{display:block;height:100%;width:100%;border-radius:inherit;background:var(--hot);
  clip-path:inset(0 0 0 calc(100% - var(--p,0%)) round 99px);transition:none}
.activityNote{display:block;margin-top:5px;color:var(--faint);font-size:11.5px}
.activityActions{display:flex;align-items:center;gap:12px;margin-top:9px}
.activityActions button,.activityActions a{padding:0;color:var(--cream);font-size:12px;
  font-weight:600;text-decoration:none}
.activityActions button:hover,.activityActions a:hover{color:var(--hot)}
.activityActions .danger{color:#D998A7}
.activityActions button:disabled{opacity:.5;cursor:default}
.activityEmpty{padding:34px 20px;text-align:center}
.activityEmpty b{display:block;font-size:14px;margin-bottom:4px}
.activityEmpty span{display:block;color:var(--dim);font-size:12.5px;line-height:1.7}
.activityAll{flex:0 0 auto;padding:13px 18px;border-top:1px solid var(--line);
  color:var(--dim);font-size:12.5px;text-align:start}
.activityAll:hover{color:var(--cream);background:var(--raised)}
body.activityOpen{overflow:hidden}
.ghost{padding:8px 14px;border-radius:12px;font-size:13.5px;color:var(--dim);
  border:1px solid transparent;text-decoration:none;display:inline-block;
  transition:color .15s ease,background-color .15s ease,border-color .15s ease}
.ghost:hover{color:var(--cream);background:var(--raised);border-color:var(--line)}
.btn{padding:11px 20px;border-radius:14px;font-size:14px;font-weight:600;
  background:var(--raised);border:1px solid var(--line2);
  transition:border-color .16s ease,box-shadow .16s ease,transform .16s var(--ease-out);
  display:inline-flex;align-items:center;gap:8px;justify-content:center}
.btn:hover:not(:disabled){border-color:var(--hot);transform:translateY(-1px)}
.btn:disabled{opacity:.38;cursor:default}
.btn:active:not(:disabled){transform:scale(.97)}
.btn.cta{background:linear-gradient(135deg,var(--hot),#FF8A3D);border:0;color:#1A0E06;
  font-weight:700;
  /* Depth, not a halo: an orange blur on a black page is the stock "AI tool"
     glow, and the colour is already saying everything a glow could. */
  box-shadow:inset 0 1px 0 rgba(255,236,214,.35),0 6px 16px rgba(0,0,0,.45)}
.btn.cta:hover:not(:disabled){box-shadow:inset 0 1px 0 rgba(255,236,214,.4),0 10px 22px rgba(0,0,0,.55)}
.btn.sm{padding:7px 12px;font-size:12.5px;border-radius:11px;font-weight:500}
.btn.wide{width:100%}
.row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.wrap{max-width:1200px;margin:0 auto;padding:26px 24px 90px}

/* ---------- library ---------- */
.hero{text-align:center;padding:26px 0}
.hero h1{font-size:46px;margin:0 0 10px;letter-spacing:-.02em;line-height:1.1}
.hero h1 em{font-style:normal;color:var(--hot)}
.hero p{color:var(--dim);margin:0;font-size:16.5px}
.drop{border:2px dashed var(--line2);border-radius:26px;background:var(--surface);
  padding:40px 26px;text-align:center;cursor:pointer;
  transition:border-color .2s ease,background-color .2s ease}
.drop:hover,.drop.hot{border-color:var(--hot);background:#1D1611}
.drop .ic{width:60px;height:60px;margin:0 auto 14px;border-radius:20px;display:grid;
  place-items:center;background:linear-gradient(150deg,var(--hot),var(--sun));
  box-shadow:inset 0 1px 0 rgba(255,236,214,.35),0 6px 14px rgba(0,0,0,.45)}
.drop b{display:block;font-family:"Secular One",sans-serif;font-size:21px;margin-bottom:5px}
.drop span{color:var(--dim);font-size:13.5px}
.tabs{display:flex;gap:4px;margin:28px 0 6px;background:var(--surface);
  border:1px solid var(--line);border-radius:16px;padding:4px;width:max-content}
.tab{padding:9px 22px;border-radius:12px;font-size:14px;font-weight:600;color:var(--dim);transition:.15s}
.tab:hover{color:var(--cream)}
.tab.on{background:var(--raised);color:var(--cream);box-shadow:0 2px 8px rgba(0,0,0,.5)}
.steps{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin-top:30px}
@media(max-width:720px){.steps{grid-template-columns:1fr}}
.step{background:var(--surface);border:1px solid var(--line);border-radius:20px;padding:20px}
.step .ic{width:38px;height:38px;border-radius:13px;display:grid;place-items:center;
  margin-bottom:12px;font-family:"Secular One",sans-serif;font-size:16px}
.step b{display:block;font-family:"Secular One",sans-serif;font-size:17px;margin-bottom:4px}
.step span{color:var(--dim);font-size:13px;line-height:1.7}
.reassure{text-align:center;color:var(--faint);font-size:13px;margin-top:20px}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:16px;margin-top:24px}
/* Past four videos the library stops being a short list and starts being a
   library: four to a row shows a working set at a glance instead of making
   somebody scroll past what they already know they have. */
body.manyVideos .grid{grid-template-columns:repeat(4,minmax(0,1fr))}
@media(max-width:1100px){body.manyVideos .grid{grid-template-columns:repeat(3,minmax(0,1fr))}}
@media(max-width:760px){body.manyVideos .grid{grid-template-columns:repeat(2,minmax(0,1fr))}}

/* Hover plays the video itself. For a finished project that is the exported
   file, captions burned in — the card shows the actual result rather than a
   poster of the source, which is the difference between "a video I made"
   and "a video I uploaded". Muted and inline; it never steals audio. */
.card .th{position:relative}
.card video.peek{position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;opacity:0;transition:opacity .22s;pointer-events:none;
  background:var(--ink)}
.card video.peek.on{opacity:1}
@media (prefers-reduced-motion:reduce){.card video.peek{display:none}}
.card{background:var(--surface);border:1px solid var(--line);border-radius:var(--r);
  overflow:hidden;position:relative;
  transition:border-color .2s,box-shadow .2s,transform .2s cubic-bezier(.22,1,.36,1)}
@media (hover:hover) and (pointer:fine){
  .card:hover{border-color:var(--line2);transform:translateY(-3px);box-shadow:var(--shadow)}
}
.card:has(.cardHit:active){transform:scale(.985);transition-duration:.1s}
/* The card is one button. Its focus ring is drawn inside the card's own
   border, so the rounded corner and overflow:hidden cannot clip it. */
.cardHit{display:block;width:100%;padding:0;text-align:start;color:inherit;
  border-radius:inherit;cursor:pointer}
.cardHit:focus-visible{outline:2px solid var(--hot);outline-offset:-3px;border-radius:inherit}

/* The picture, and what is behind it. Only the picture turns over: the name
   underneath is the same on both sides, so it stays put and reads as the
   label of the thing that is turning. */
.card .face{display:block;position:relative;aspect-ratio:9/16;transform-style:preserve-3d}
.card .th,.card .back{position:absolute;inset:0;display:block;
  -webkit-backface-visibility:hidden;backface-visibility:hidden}
.card .th{background:#100D0B center/cover no-repeat}
.card .back{transform:rotateY(180deg);overflow:hidden;background:#100D0B;
  display:grid;place-items:center;align-content:center;gap:12px}
/* The back is lit by the clip it hides: the same poster, blurred past
   recognition. Every back is a different colour, and none of them is a
   glow — it is the footage, out of focus. */
.card .backArt{position:absolute;inset:-14%;background:center/cover no-repeat;
  filter:blur(22px) saturate(1.3) brightness(.52)}
.card .back::after{content:"";position:absolute;inset:9px;border-radius:14px;
  border:1px solid rgba(247,239,230,.22);pointer-events:none}
.card .backMark{position:relative;z-index:1;width:40%;max-width:88px;height:auto}
.card .backCue{position:relative;z-index:1;font-size:12px;font-weight:600;
  color:var(--cream);padding:5px 12px;border-radius:99px;
  background:rgba(13,11,10,.78);border:1px solid var(--line2)}
/* Only while there is a back to see. Left in place on a face-up card it is
   text hidden behind the picture, which is still text on the page. */
.card:not(.faceDown):not(.turning) .back{display:none}
.card.faceDown .face{transform:rotateY(180deg)}
/* Rare, and the one place in the library that is allowed to take its time.
   ease-in-out, because it is a turn on the spot, not an arrival: it gathers,
   passes edge-on at the halfway mark where the back gives way to the clip,
   and settles. At ease-out it was 90% over before the eye caught it. */
.card.turning .face{animation:turnOver .56s var(--ease-in-out) both}
@keyframes turnOver{from{transform:rotateY(180deg)}to{transform:none}}

/* Under the picture, on the card's own surface: contrast that does not
   depend on what the footage looks like at the bottom of the frame. */
.card .meta{display:flex;flex-direction:column;gap:2px;padding:10px 12px 11px;
  border-top:1px solid var(--line)}
.card .meta b{display:block;font-size:13.5px;font-weight:600;overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
.card .sub{display:flex;align-items:center;justify-content:space-between;
  flex-wrap:wrap;gap:0 8px;font-size:11.5px;line-height:1.55}
.card .info{color:var(--dim);font-variant-numeric:tabular-nums}
.pill{font-size:11.5px;font-weight:600;color:var(--dim);white-space:nowrap}
.pill.work{color:var(--sun)}.pill.ok{color:var(--mint)}.pill.bad{color:var(--rose)}
.pill.hot{color:var(--hot)}
.pill .dot{display:inline-block;width:6px;height:6px;border-radius:50%;background:currentColor;
  margin-inline-end:5px;vertical-align:1px;animation:pulse 1.4s infinite}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.25}}
@media (prefers-reduced-motion:reduce){
  .card.turning .face{animation:none}
  .card.turning .th{animation:fadeIn .18s ease-out both}
}
@keyframes fadeIn{from{opacity:0}}

/* Once there are projects, the headline and the drop zone are 374px of
   things the returning user already knows — measured, they pushed the first
   card to y=558 on an 844px phone. Keep the door open, just smaller. */
body.hasProjects .hero{padding:10px 0}
body.hasProjects .hero h1{font-size:26px;margin-bottom:2px}
body.hasProjects .hero p{display:none}
body.hasProjects .drop{padding:16px 20px;display:flex;align-items:center;
  gap:12px;text-align:start;border-radius:18px}
body.hasProjects .drop .ic{width:38px;height:38px;border-radius:13px;margin:0;flex:0 0 auto}
body.hasProjects .drop .ic svg{width:19px;height:19px}
body.hasProjects .drop b{font-size:15px;margin:0}
body.hasProjects .drop span{font-size:12px}
body.hasProjects .tabs{margin-top:16px}
.folderbar{display:flex;gap:7px;flex-wrap:wrap;align-items:center;margin:20px 0 4px}
.fchip{padding:8px 15px;border-radius:99px;border:1px solid var(--line);background:var(--surface);
  font-size:13px;color:var(--dim);transition:.15s;display:flex;align-items:center;gap:7px}
.fchip:hover{border-color:var(--line2);color:var(--cream)}
.fchip.on{background:var(--hot-dim);border-color:var(--hot);color:var(--cream)}
.fchip .n{font-size:11px;opacity:.7;font-variant-numeric:tabular-nums}
.fchip.add{border-style:dashed}
.vcard .foot{display:flex;gap:6px;padding:10px 11px;border-top:1px solid var(--line)}
.vcard .foot button,.vcard .foot a{flex:1;text-align:center;padding:8px 0;border-radius:10px;
  font-size:12px;border:1px solid var(--line);color:var(--dim);text-decoration:none}
.vcard .foot button:hover,.vcard .foot a:hover{border-color:var(--hot);color:var(--cream)}

/* ---------- editor ---------- */
.editor{display:grid;grid-template-columns:1fr minmax(300px,380px);gap:26px;align-items:start}
/* max-width, not just min-width:0 — as a flex item this still resolved to
   its content's width and dragged the layout viewport wider than the phone. */
.panel{display:flex;flex-direction:column;gap:16px;min-width:0;max-width:100%;
  min-height:calc(100 * var(--vh) - 120px)}
/* Sticky, and NEVER a scroller of its own.
 *
 * This used to be position:sticky and overflow-y:auto at the same time, with
 * a max-height off the viewport that the content did not fit: measured at
 * 1440x900, 834px of stage inside a 787px box — so there was always a second,
 * nested scrollbar here, and the playing <video> sat inside a scrolling layer
 * inside a sticky layer. Chrome composites that badly on a laptop: the
 * picture strobes while it plays, and the state persists until something
 * forces the layer to be rebuilt, which is why resizing the window "fixed" it
 * until the next time. Reported as the video flickering on play — on a
 * desktop browser only, never on a phone, because below 960px this element is
 * already overflow:visible with no height cap on it.
 *
 * The room is the room. The PICTURE fits what is left, the way it already
 * works in the Premiere dock; see the min-width:961px block further down. */
.stageWrap{position:sticky;top:calc(var(--navH) + 12px);z-index:5;background:var(--ink);
  max-height:calc(100 * var(--vh) - var(--navH) - 28px);
  min-height:0;overflow:hidden;padding-bottom:6px}
/* pan-y, not none. touch-action:none meant a finger dragged upward over the
   player did not scroll the page — it silently dragged the caption to the top
   of the frame instead. The picture is the top third of a phone screen, so
   that is where people naturally put their thumb to scroll. Placing a caption
   by touch is an explicit mode (.moving) that turns the gesture back off. */
.player{position:relative;border-radius:var(--r);overflow:hidden;background:#000;
  border:1px solid var(--line);box-shadow:var(--shadow);line-height:0;cursor:grab;
  touch-action:pan-y;user-select:none;-webkit-user-select:none}
.player.moving{touch-action:none;outline:2px solid var(--hot);outline-offset:-2px}
.player.dragging{cursor:grabbing}
.player video,.player img.poster{width:100%;display:block}
/* A one-shot glyph confirming a tap landed, since a paused frame looks the
   same whether or not the tap registered. */
.sizeGrip{display:grid;height:22px;margin:5px auto 0;width:110px;
  place-items:center;cursor:ns-resize;touch-action:none}
.sizeGrip i{width:52px;height:4px;border-radius:99px;background:var(--line2);
  transition:background .15s,transform .15s}
.sizeGrip:active i,.sizeGrip:focus-visible i{background:var(--hot);transform:scaleX(1.27)}
.sizeGrip:focus-visible{outline:2px solid var(--hot);outline-offset:2px;border-radius:8px}

.tapcue{position:absolute;inset:0;display:grid;place-items:center;z-index:7;
  font-size:34px;color:#fff;text-shadow:0 4px 20px rgba(0,0,0,.9);
  opacity:0;pointer-events:none;transition:opacity .18s}
.player.cue .tapcue{opacity:.92;transition:none}
/* In the controls, not on the picture: as a labelled chip over the frame it
   wrapped to two lines and covered a third of a portrait player. */
/* No display here. It said display:flex, which came after .touchOnly{display:
   none} with the same specificity and won, so a touch-only control showed on
   every desktop: "move captions" next to a mouse that already drags them. */
.moveChip{min-height:40px;padding:0 11px;flex:0 0 auto;border-radius:13px;
  align-items:center;justify-content:center;gap:6px;font-size:12px;
  font-weight:600;line-height:1;
  background:var(--raised);border:1px solid var(--line);color:var(--dim)}
.moveChip svg{width:17px;height:17px;fill:none;stroke:currentColor;stroke-width:1.8;
  stroke-linecap:round;stroke-linejoin:round;flex:0 0 auto}
.moveChip:hover{border-color:var(--hot);color:var(--cream)}
.moveChip.on{background:var(--hot);border-color:var(--hot);color:#1A0E06}
body.touch .moveChip{display:flex}
.savedTag{height:16px;margin-top:6px;font-size:11.5px;color:var(--mint);
  text-align:center;opacity:0;transition:opacity .2s}
.savedTag.on{opacity:1}
.overlay{position:absolute;inset:0;pointer-events:none;overflow:hidden;direction:ltr}
#stage{position:absolute;top:0;left:0;transform-origin:top left}
.guide{position:absolute;inset-inline:0;top:50%;border-top:2px dashed rgba(255,90,31,.9);
  opacity:0;transition:opacity .15s;z-index:5;pointer-events:none}
.player.dragging .guide{opacity:1}
/* One line, never two: a portrait player is 152px wide on an iPhone SE and
   a second line of this ran back over the first. */
.hintbar{position:absolute;inset-inline:0;bottom:9px;text-align:center;z-index:6;font-size:11.5px;
  color:rgba(255,255,255,.72);pointer-events:none;text-shadow:0 2px 8px rgba(0,0,0,.95);
  opacity:0;transition:.2s;padding:0 10px;white-space:nowrap;overflow:hidden;
  text-overflow:ellipsis}
.player:hover .hintbar{opacity:1}
/* The hint is for someone deciding what to do, not for someone watching. */
.player.playing .hintbar{opacity:0!important}
.touchHint{display:none}
body.touch .mouseHint{display:none}
body.touch .touchHint{display:inline}
.player.moving .hintbar .touchHint::after{content:". גררו למקום"}
.transport{display:flex;align-items:center;gap:11px;margin-top:12px;flex-wrap:wrap}
.pp{width:42px;height:42px;border-radius:14px;background:var(--raised);border:1px solid var(--line);
  display:grid;place-items:center;flex:0 0 auto}
.pp:hover{border-color:var(--hot)}
/* The bar is 8px; the TARGET is 30. A 6px line is something you aim at,
   and on a phone aiming at it while the video plays is most of why
   scrubbing felt broken. The padding does the work, so the visual weight
   stays light. */
.scrub{flex:1;height:30px;position:relative;cursor:pointer;touch-action:none;
  display:flex;align-items:center}
.scrub::before{content:"";position:absolute;inset-inline:0;height:8px;
  border-radius:99px;background:var(--high)}
.scrub i{position:absolute;inset-inline-start:0;height:8px;border-radius:99px;
  background:var(--hot);width:0}
/* A handle you can see, so the position is legible at a glance and there is
   something obvious to grab. */
.scrub .knob{position:absolute;inset-inline-start:0;width:15px;height:15px;
  margin-inline-start:-7px;border-radius:50%;background:var(--cream);
  box-shadow:0 2px 8px rgba(0,0,0,.6);pointer-events:none;transition:transform .12s}
.scrub:hover .knob,.scrub:active .knob{transform:scale(1.25)}
.scrub:focus-visible{outline:2px solid var(--hot);outline-offset:4px;border-radius:99px}

/* ±5s and fullscreen */
.skipBtn{flex:0 0 auto;width:38px;height:38px;border-radius:12px;display:grid;
  place-items:center;color:var(--dim);border:1px solid transparent;transition:.15s}
.skipBtn:hover{color:var(--cream);background:var(--raised);border-color:var(--line)}
.skipBtn svg{width:22px;height:22px;fill:none;stroke:currentColor;stroke-width:1.9;
  stroke-linecap:round;stroke-linejoin:round}
.skipBtn svg text{font:700 8px/1 "Rubik",sans-serif;fill:currentColor;stroke:none}
@media(max-width:420px){.skipBtn{width:34px;height:34px}
  .skipBtn svg{width:20px;height:20px}}
.tc{font:12px/1 ui-monospace,SFMono-Regular,monospace;color:var(--dim);direction:ltr;
  min-width:86px;text-align:center;font-variant-numeric:tabular-nums}
#exportBox{margin-top:16px}

/* ---------- sections ---------- */
/* Style is layer 1: always open, never a header you can collapse. */
.styleHero{margin-bottom:2px}
.styleHead{display:flex;align-items:baseline;gap:12px;margin-bottom:14px;flex-wrap:wrap}
.styleHead h2{font-family:"Secular One",sans-serif;font-weight:400;font-size:26px;
  letter-spacing:-.015em;margin:0}
.styleHead .note{margin:0}

/* min-width:0 all the way down the flex chain. A flex item defaults to
   min-width:auto, which floors it at its content's min-content width — so
   the horizontal scrollers below (.styles, .toolbar) pushed their full
   content width up through .cols and stretched the whole layout viewport
   instead of scrolling inside it. */
/* Deliberately a block, not a flex column: block children take the parent's
   width and let their content overflow into their own scroller, whereas
   flex items would each re-introduce the min-width:auto floor. */
.cols{min-width:0}
.cols > * + *{margin-top:16px}
.stylesCarousel{position:relative;min-width:0}
.styles{display:flex;gap:10px;min-width:0;max-width:100%;overflow-x:auto;
  padding:4px 4px 8px;scroll-padding-inline:4px;scroll-snap-type:inline mandatory;scrollbar-width:none;
  -webkit-overflow-scrolling:touch}
.styles::-webkit-scrollbar{display:none}
/* Four cards fill the desktop rail: 4 × (25% - 7.5px) plus three 10px gaps.
   Keeping the fifth card beyond the edge makes the carousel useful without
   shrinking every preview into an unreadable thumbnail. */
.style{flex:0 0 auto;width:auto;min-width:0;scroll-snap-align:start}
.styleNav{position:absolute;z-index:6;top:50%;width:44px;height:44px;
  transform:translateY(-50%);display:grid;place-items:center;border-radius:50%;
  background:rgba(13,11,10,.92);border:1px solid var(--line2);
  box-shadow:0 8px 24px rgba(0,0,0,.5);color:var(--cream)}
.styleNav:hover{border-color:var(--hot);background:var(--surface)}
.styleNav svg{width:20px;height:20px;fill:none;stroke:currentColor;stroke-width:2.2;
  stroke-linecap:round;stroke-linejoin:round}
.styleNext{inset-inline-end:-8px}
.stylePrev{inset-inline-start:-8px}
.stylesCarousel::before,.stylesCarousel::after{content:"";position:absolute;z-index:5;
  top:0;bottom:0;width:38px;pointer-events:none;opacity:0;transition:opacity .15s}
.stylesCarousel::before{inset-inline-start:0;background:linear-gradient(to left,var(--ink),transparent)}
.stylesCarousel::after{inset-inline-end:0;background:linear-gradient(to right,var(--ink),transparent)}
.stylesCarousel.hasBack::before,.stylesCarousel.hasMore::after{opacity:1}
/* The ring is a border, not a halo: it used to be set inline from JS with a
   4px zero-offset orange shadow on top of it. */
.style{position:relative;border-radius:16px;overflow:hidden;background:var(--raised);
  cursor:pointer;text-align:center;border:2px solid var(--line);
  transition:border-color .15s ease,transform .16s var(--ease-out)}
@media (hover:hover) and (pointer:fine){.style:hover{border-color:var(--line2)}}
.style.on{border-color:var(--hot)}
.style.on .cap b{color:var(--hot)}
.style:active{transform:scale(.98)}
.style .mineTag{background:var(--cream);color:var(--ink)}
/* "Save this as a style": the same size as a card, so it reads as the next
   card in the row rather than a button that wandered in. Deliberately not a
   .style (see renderStyles), so it carries the card's box itself. */
.styleAdd{position:relative;flex:0 0 auto;scroll-snap-align:start;border-radius:16px;
  overflow:hidden;cursor:pointer;text-align:center;background:var(--surface);
  border:2px dashed var(--line2);transition:border-color .15s ease,transform .16s var(--ease-out)}
.styleAdd:active{transform:scale(.98)}
.styleAdd .addPrev{display:flex;flex-direction:column;align-items:center;
  justify-content:center;gap:8px;padding:0 14px;height:330px;color:var(--dim)}
.styleAdd .addPrev svg{width:30px;height:30px;fill:none;stroke:var(--hot);
  stroke-width:2;stroke-linecap:round;padding:9px;box-sizing:content-box;
  border-radius:14px;background:var(--hot-dim)}
.styleAdd .addPrev b{font-size:14px;color:var(--cream);font-weight:700}
.styleAdd .addPrev span{font-size:12px;line-height:1.5;max-width:15ch}
.styleAdd .cap{padding:9px 6px 10px}
.styleAdd .cap b{display:block;font-size:13px;font-weight:700}
.styleAdd .cap span{display:block;font-size:11px;color:var(--dim);margin-top:2px}
@media (max-width:700px){.styleAdd .addPrev{height:250px}}
.inPanel .styleAdd .addPrev{height:300px}
@media (hover:hover) and (pointer:fine){
  .styleAdd:hover{border-color:var(--hot)}
  .styleAdd:hover .addPrev{color:var(--cream)}
}
/* The frame is a plain <img> from the server. It replaced a <canvas> fed by a
   hidden <video>, which Safari refuses to decode at all — on an iPhone every
   card was a black square, and the QA never caught it because headless
   Chrome happily decodes one. */
/* The card is a WINDOW onto the frame, and its edges say so.
   A caption wider than the crop runs off both sides; hard-clipped it reads
   as a word that broke, faded it reads as a view that continues. The mask
   goes here, on the window — inside .zoom it would be measured in frame
   pixels and fade a thousand pixels outside anything visible. */
.style .prev{position:relative;overflow:hidden;background:#100D0B;
  -webkit-mask-image:linear-gradient(to right,transparent 0,#000 7%,#000 93%,transparent 100%);
  mask-image:linear-gradient(to right,transparent 0,#000 7%,#000 93%,transparent 100%)}
/* One zoomed box holding both the still and the caption drawn over it, so
   the two can never slide apart. fitMini sets its size and its transform. */
.style .zoom{position:absolute;top:0;left:0;transform-origin:top left;
  will-change:transform}
.style .prev img.shot{width:100%;height:100%;object-fit:cover;display:block}
.style .mini{position:absolute;inset:0;direction:ltr;overflow:hidden}
.style .mini > div{position:absolute;top:0;left:0}
/* The cards are stills: paintStyles() draws one settled moment and nothing
   on them ever animates. The engine's per-caption will-change is right for
   the player and wrong here, where it asked for a composited layer for every
   caption of the clip, on every card: measured 515 on a 46-caption project. */
.style .mini .cap-box{will-change:auto!important}
.loadingCard{display:flex;align-items:center;gap:10px;justify-content:center;
  padding:44px 20px;color:var(--faint);font-size:13.5px}
.style .cap{padding:9px 6px 10px}
.style .cap b{display:block;font-size:13px;font-weight:700}
.style .cap span{display:block;font-size:11px;color:var(--dim);margin-top:2px;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.style .rec{position:absolute;top:8px;inset-inline-end:8px;z-index:3;background:var(--hot);
  color:#1A0E06;font-size:9.5px;font-weight:800;padding:3px 7px;border-radius:99px}
.style .tick{position:absolute;top:8px;inset-inline-start:8px;z-index:3;width:20px;height:20px;
  border-radius:50%;background:var(--hot);color:#1A0E06;display:grid;place-items:center;
  font-size:12px;font-weight:800}

/* Layer 2: five tools, one panel. Opening one closes the last, so the
   column never grows the way six stacked accordions did. */
.toolbar{display:flex;overflow-x:auto;background:var(--surface);border:1px solid var(--line);
  border-radius:18px;scrollbar-width:thin;min-width:0;max-width:100%}
/* Six of these now, not five: the publishing pack joined them. At a 104px
   floor six no longer fit a laptop's editing column and the sixth fell off
   the end of a bar nobody thinks to scroll sideways. They shrink instead,
   and the labels ellipsis rather than spill. */
.tool{position:relative;padding:13px 6px;text-align:center;flex:1 1 84px;
  min-width:0;white-space:nowrap;
  transition:.15s;border-inline-start:1px solid var(--line)}
.tool:first-child{border-inline-start:0}
.tool:hover{background:rgba(255,255,255,.03)}
.tool .tt{font-size:14px;font-weight:600;overflow:hidden;
  text-overflow:ellipsis;max-width:100%}
.tool .sum{font-size:11px;color:var(--dim);margin-top:3px;overflow:hidden;
  text-overflow:ellipsis;max-width:100%}
.tool.on .tt{color:var(--cream)}
.tool.on::after{content:"";position:absolute;inset-inline:0;bottom:0;height:2px;background:var(--hot)}
.toolPanel{background:var(--surface);border:1px solid var(--line);border-radius:18px;
  padding:16px 18px}
.toolBody[hidden]{display:none}

.dotpulse{display:inline-block;width:9px;height:9px;border-radius:50%;background:var(--sun);
  animation:pulse 1.4s infinite;flex:0 0 auto}

/* Layer 3: status and export, pinned to the bottom of the column. */
/* Content scrolls underneath, so it fades out rather than being chopped
   off mid-row against a hard edge. */
.pinned{position:sticky;bottom:0;z-index:12;margin-top:auto;
  padding:18px 0 calc(4px + env(safe-area-inset-bottom));
  background:linear-gradient(180deg,transparent,var(--ink) 20px)}
#statusBar:not(:empty){margin-bottom:10px}
.ic-hot{background:var(--hot-dim);color:var(--hot)}
.ic-sun{background:rgba(255,201,60,.15);color:var(--sun)}
.ic-mint{background:rgba(62,213,152,.14);color:var(--mint)}
.ic-rose{background:rgba(255,77,109,.14);color:var(--rose)}
.ic-sky{background:rgba(90,169,255,.14);color:var(--sky)}
.ctrl{padding:12px 0;border-bottom:1px solid rgba(255,255,255,.05)}
.ctrl:last-child{border:0}
.ctrl > .lbl{display:flex;justify-content:space-between;font-size:13px;color:var(--dim);margin-bottom:9px}
.ctrl > .lbl b{color:var(--cream);font-variant-numeric:tabular-nums;font-weight:600}
input[type=range]{width:100%;-webkit-appearance:none;appearance:none;height:28px;
  background:transparent;display:block}
input[type=range]::-webkit-slider-runnable-track{height:6px;border-radius:99px;background:var(--high)}
input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:24px;height:24px;border-radius:50%;
  background:var(--cream);margin-top:-9px;box-shadow:0 2px 10px rgba(0,0,0,.6);cursor:grab}
select,input[type=text],input[type=number]{background:var(--raised);border:1px solid var(--line);
  color:var(--cream);border-radius:12px;padding:10px 12px;font:inherit;font-size:15px;width:100%}
select:focus,input:focus{outline:none;border-color:var(--hot)}
.swatches{display:flex;gap:10px;align-items:center;flex-wrap:wrap}
.sw{display:flex;flex-direction:column;align-items:center;gap:5px;cursor:pointer}
.sw input[type=color]{width:46px;height:36px;border:1px solid var(--line2);border-radius:11px;
  background:none;padding:2px;cursor:pointer}
.sw b{font-size:10.5px;font-weight:500;color:var(--faint)}
.seg{display:flex;gap:5px;background:var(--raised);border:1px solid var(--line);
  border-radius:13px;padding:4px}
.seg button{flex:1;padding:9px 6px;border-radius:10px;font-size:12.5px;color:var(--dim)}
.seg button.on{background:var(--high);color:var(--cream);font-weight:600}

/* ---------- cutting ---------- */
/* pan-y, not none: selecting a range here is a horizontal gesture, so the
   browser can keep the vertical one. With touch-action:none a finger that
   landed on this 56px band could not scroll the page at all. */
.strip{position:relative;height:56px;border-radius:14px;overflow:hidden;border:1px solid var(--line);
  background:#100D0B;cursor:crosshair;touch-action:pan-y;display:flex}
.strip .fr{flex:1;background-size:cover;background-position:center;opacity:.8;min-width:0}
/* ---- the cut editor ----
   The timeline is a row of PIECES, not a bar you draw a box on: split at the
   playhead, click a piece, delete it. A deleted piece stays visible and
   struck through rather than vanishing, because an edit you cannot see is an
   edit you cannot undo by eye. */
.strip{cursor:default}
/* NOT .seg — that class is already a segmented control elsewhere in this
   stylesheet, with an opaque background and a 13px radius, and reusing the
   name made every kept piece a dark slab hiding the filmstrip underneath. */
.strip .piece{position:absolute;top:0;bottom:0;cursor:pointer;
  background:transparent;border-radius:0;
  border-inline:1px solid rgba(255,240,225,.22);transition:background .13s}
.strip .piece:hover{background:rgba(255,255,255,.12)}
.strip .piece.on{background:rgba(255,90,31,.32);
  border-inline:2px solid var(--hot);box-shadow:inset 0 0 0 1px rgba(255,90,31,.6)}
.strip .piece.gone{background:repeating-linear-gradient(135deg,
    rgba(10,8,7,.72) 0 7px,rgba(255,77,109,.5) 7px 14px)}
.strip .piece.gone::after{content:"";position:absolute;inset-inline:2px;top:50%;
  height:2px;background:var(--rose);border-radius:2px;transform:translateY(-1px)}
.strip .piece.gone:hover{background:repeating-linear-gradient(135deg,
    rgba(10,8,7,.6) 0 7px,rgba(255,77,109,.7) 7px 14px)}
.strip .splitMark{position:absolute;top:-2px;bottom:-2px;width:2px;
  background:var(--sun);pointer-events:none}

.cutBar{display:flex;gap:7px;margin-bottom:10px;flex-wrap:wrap}
.cutTool{display:inline-flex;align-items:center;gap:6px;padding:8px 12px;
  font-size:12.5px;min-height:0}
.cutTool svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:2;
  stroke-linecap:round;stroke-linejoin:round;flex:0 0 auto}
.cutTool:disabled{opacity:.4}
.cutTool.wide{margin-inline-start:auto}
@media(max-width:520px){
  .cutTool{padding:8px 10px;font-size:12px}
  .cutTool.wide{margin-inline-start:0;width:100%;justify-content:center}
}
.cutHint{margin-top:7px;font-size:12px}
.strip .ph{position:absolute;top:0;bottom:0;width:2px;background:#fff;
  box-shadow:0 0 8px rgba(0,0,0,.9);pointer-events:none}
.striprow{display:flex;align-items:center;gap:9px;margin-top:9px}
.striprow .note{flex:1}

/* ---------- transcript ---------- */
.rows{max-height:min(56vh,620px);overflow:auto;margin:0 -11px;padding:0 5px}
.rw{display:flex;gap:10px;padding:10px 11px;border-radius:14px;transition:.12s;align-items:flex-start}
.rw:hover{background:rgba(255,255,255,.03)}
/* The playing line is tinted and outlined all round. It was a 3px stripe down
   the physical LEFT edge, which in this RTL list is the end of the line, not
   its start. */
.rw.on{background:var(--hot-dim);box-shadow:inset 0 0 0 1px rgba(255,90,31,.38)}
.rw.gone{opacity:.34}
.rw.gone .ws{text-decoration:line-through;text-decoration-color:var(--rose);
  text-decoration-thickness:2px}
.rw .t{font:11.5px/2 ui-monospace,monospace;color:var(--faint);direction:ltr;min-width:42px;
  cursor:pointer;font-variant-numeric:tabular-nums;flex:0 0 auto}
.rw .t:hover{color:var(--hot)}
.ws{display:flex;flex-wrap:wrap;gap:4px;align-items:center;flex:1;min-width:0}
.wd{padding:3px 8px;border-radius:9px;font-size:15px;cursor:pointer;border:1px solid transparent;
  transition:.1s;white-space:pre}
.wd:hover{background:rgba(255,255,255,.08)}
.wd.low{box-shadow:inset 0 -2px 0 var(--sun)}
/* These follow the caption palette, set by paintTranscriptColors(). The list
   is a preview of the video, so a highlight colour chosen in Design has to
   show up here too — it used to stay the app's own rose for ever. */
.wd.k-punch{background:var(--capPunchBg,var(--rose));color:var(--capPunchText,#fff);font-weight:600}
.wd.k-strike{background:rgba(255,77,109,.16);text-decoration:line-through;
  text-decoration-color:var(--rose);text-decoration-thickness:2px}
.wd.k-brand{background:rgba(255,77,109,.14);color:var(--capBrand,#FF9BB0);font-weight:600}
.rw.on .ws .wd.k-word{color:inherit}
/* --faint on the tinted playing row measured 4.1:1; one step up clears AA. */
.rw.on .t{color:var(--dim)}
.wd.k-emoji{font-size:18px}
.rwact{display:flex;gap:3px;opacity:0;transition:.12s;flex:0 0 auto}
.rw:hover .rwact,.rw.on .rwact,.rw.gone .rwact{opacity:1}
.ic-btn{width:30px;height:30px;border-radius:10px;display:grid;place-items:center;
  color:var(--faint);border:1px solid transparent}
.ic-btn:hover{background:var(--raised);border-color:var(--line);color:var(--cream)}
.ic-btn.danger:hover{background:rgba(255,77,109,.16);border-color:var(--rose);color:var(--rose)}

/* ---------- word sheet ---------- */
.pop{position:fixed;z-index:160;background:var(--raised);border:1px solid var(--line2);
  border-radius:18px;padding:13px;box-shadow:var(--shadow);width:min(340px,92vw);
  transform:translate(-50%,-100%) translateY(-10px)}
.pop input{margin-bottom:10px;text-align:center;font-size:16px}
.pop .acts{display:flex;gap:6px;flex-wrap:wrap}
.pop button{padding:10px 13px;border-radius:11px;font-size:13px;white-space:nowrap;
  color:var(--dim);border:1px solid var(--line);min-height:40px}
.pop button:hover{background:var(--high);color:var(--cream)}
.pop button.on{background:var(--rose);color:#fff;border-color:var(--rose)}
.pop button.sug{border-color:var(--hot);background:var(--hot-dim)}
.pop .grow{flex:1}
.popBack{position:fixed;inset:0;z-index:159;background:rgba(8,6,5,.55)}
@media(max-width:720px){
  /* --kb is the keyboard's height, measured from visualViewport in
     studio.js. Without it the sheet sits at bottom:0 — underneath the keys
     on iOS, so you were typing at a field you could not see. */
  .pop{left:0!important;right:0;bottom:var(--kb,0px);top:auto!important;width:100%;
    transform:none;border-radius:24px 24px 0 0;
    padding:16px 16px calc(16px + env(safe-area-inset-bottom));
    animation:sheet .22s cubic-bezier(.22,1,.36,1);
    transition:bottom .18s;max-height:70vh;overflow:auto}
  body.kb .pop{padding-bottom:16px}
  .sheet{bottom:var(--kb,0px);transition:bottom .18s}
  @keyframes sheet{from{transform:translateY(100%)}}
}

/* ---------- the offer, and what is left of it ---------- */
.signedOutOnly{display:none}
body.signedOut .signedOutOnly{display:flex}
/* The nav's sign-in button: a real call to action, sized to the nav rather
   than to the page, and never shown to somebody already signed in. */
.navIn{padding:8px 16px;font-size:13px;min-height:0}
/* The admin link carries an icon so its label can go on a narrow screen —
   adding it as plain text pushed the nav 24px past a 375px viewport and put
   a horizontal scrollbar on every page. */
#navAdmin{display:inline-flex;align-items:center;gap:6px}
#navAdmin svg{width:17px;height:17px;flex:0 0 auto}
@media(max-width:700px){
  #navAdmin span{position:absolute;width:1px;height:1px;overflow:hidden;
    clip-path:inset(50%);white-space:nowrap}
  #navAdmin{padding:7px 9px}
}
@media(max-width:520px){.navIn{padding:7px 12px;font-size:12.5px}}
body.signedOut .hasProjectsOnly{display:none}
/* ---------- the quota strip ----------
   One honest number — X of Y videos — dressed as a strip instead of a
   sentence: a segmented meter, a headline that changes with how much is
   left, and (only when it is genuinely useful) a button. It covers four
   real states and deliberately does none of the things that make a "you're
   running low" banner feel hostile: no countdown, no hiding what you
   already made, no dark-grey "not now" link buried to punish the no. */
/* In the nav, on the logo's line. It is a strip inside a row now, not a
   card on a page: no shadow, no heavy padding, and it must never be the
   thing that makes the nav wrap or scroll sideways — hence min-width:0 and
   the staged drop-outs further down. */
.quotaBar{display:flex;align-items:center;gap:10px;min-width:0;
  margin:0 0 0 6px;padding:6px 12px;border-radius:99px;background:var(--surface);
  border:1px solid var(--line)}
/* The big number. This is the whole point of the strip. */
.qNum{flex:0 0 auto;font-family:"Secular One",sans-serif;font-weight:400;
  font-size:26px;line-height:1;color:var(--hot);letter-spacing:-.02em}
.quotaBar.out .qNum{color:var(--faint)}
.quotaBar.low .qNum{color:var(--sun)}

/* Each pill is a little 9:16 frame with a caption bar across it — at this
   size that reads as "a captioned video" rather than as an abstract unit,
   which is what the meter is actually counting. Lit = still yours. */
.quotaMeter{display:flex;gap:4px;flex:0 0 auto}
.quotaMeter i{position:relative;width:11px;height:18px;border-radius:3px;
  border:1.5px solid var(--line2);background:transparent;
  transition:border-color .2s,background .2s}
.quotaMeter i::after{content:"";position:absolute;inset-inline:2px;bottom:3px;
  height:2px;border-radius:1px;background:var(--line2);transition:background .2s}
.quotaMeter i.lit{border-color:var(--hot);
  background:linear-gradient(180deg,rgba(255,201,60,.22),rgba(255,90,31,.3))}
.quotaMeter i.lit::after{background:var(--sun)}
.quotaText{display:flex;flex-direction:column;gap:1px;min-width:0}
.quotaText b{font-family:"Secular One",sans-serif;font-weight:400;font-size:13.5px;
  color:var(--cream);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.quotaText span{font-size:11.5px;color:var(--faint);white-space:nowrap}
.quotaCta{flex:0 0 auto;padding:6px 12px;border-radius:99px;
  font-size:12px;font-weight:700;color:#1A0E06;border:0;white-space:nowrap;
  background:linear-gradient(135deg,var(--hot),var(--sun));cursor:pointer;transition:.15s}
.quotaCta:hover{transform:translateY(-1px);box-shadow:0 8px 18px rgba(0,0,0,.45)}
/* Getting low: still calm, just a little warmer — this fires BEFORE the
   upload that would empty it, never after. */
.quotaBar.low{border-color:rgba(255,201,60,.35)}
.quotaBar.low .quotaText b{color:var(--sun)}
/* Empty: the meter goes dim rather than red. Running out is not an error. */
.quotaBar.out .quotaMeter i{background:var(--line)}
.quotaBar.out .quotaText b{color:var(--cream)}
/* Narrowing, in the order things can be given up. The sub line goes first
   (it repeats what the headline already implies), then the headline, and
   the meter is last because it says the same thing without any words at
   all — five pills, two lit. */
@media(max-width:980px){ .quotaText span{display:none} }
@media(max-width:760px){
  .quotaText b{font-size:12px;max-width:118px}
  .quotaBar{padding:5px 9px;gap:7px}
  .quotaMeter i{width:9px;height:15px}
}
@media(max-width:560px){
  .quotaText{display:none}
  .qNum{font-size:22px}
  .quotaBar{padding:6px 9px;margin-inline-start:4px}
  .quotaMeter i{width:6px;height:14px}
  .quotaCta{padding:5px 9px;font-size:11.5px}
}

/* ---------- the first-time demo reel ----------
   Nobody who has not uploaded yet has seen what this app does, and the
   previous version did not tell them: a gradient card, six unexplained
   dots, and a caption too small and too thin to read as the thing being
   sold. It showed motion, not meaning.

   This version is narrated BY the product. Every line on screen is a real
   caption in a real preset from captions/presets.py — the pitch and the
   demonstration are the same pixels, so "this is what you get" needs no
   claim. The sequence is the actual job: a file is dragged in, the frame
   is scanned, four caption chunks REPLACE each other the way captions do
   over speech, each in a different style with the matching swatch lit,
   and it ends ready to post.

   Type is copied from the viral preset rather than approximated: Rubik
   900, a black stroke at 17.5% of the font size, the hard drop shadow,
   #FFD93D on the spoken word and the #E11D48 box. That stroke is what
   makes captions read as "viral" instead of "subtitles", and its absence
   was most of what was wrong before.

   One 10s cycle; every piece is keyed to a percentage of it, so nothing
   can drift out of phase and no script is needed. The video underneath
   loops on its own — it is footage, not choreography.

   Beats: drop 0.6–1.5s · scan 1.6–2.7s · four styles 2.7–8.4s ·
   ready 7.8–9.4s · reset 9.4–10s. */
.demoReel{max-width:300px;margin:0 auto 26px;text-align:center}

/* --- the clip being dragged in, as itself --- */
/* It starts ABOVE the card, which on this page is where the real drop zone
   is, and travels down into the phone: the same gesture the user is being
   asked to make. The thumbnail is a real frame with no captions on it, so
   the "before" is shown rather than imagined. */
.dDrag{position:absolute;z-index:4;top:0;inset-inline-start:50%;
  width:62px;margin-inline-start:-31px;border-radius:10px;overflow:hidden;
  background:var(--high);border:1px solid var(--line2);
  box-shadow:0 18px 40px rgba(0,0,0,.6);opacity:0;
  animation:dDrag 10s cubic-bezier(.34,.9,.3,1) infinite}
.dDrag img{display:block;width:100%;height:auto}
.dName{display:block;padding:4px 6px;font-size:8px;font-weight:600;
  color:var(--dim);direction:ltr;text-align:center;white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis}

/* --- the phone --- */
/* Headroom for the clip that hangs above the phone before it falls. It is
   62px wide and about 110 tall at -9% of the frame, so this is measured to
   clear it rather than guessed — an earlier 34px cut its top off. */
.demoStage{position:relative;padding-top:56px}
.demoFrame{position:relative;aspect-ratio:9/16;border-radius:22px;overflow:hidden;
  background:var(--ink);border:1px solid var(--line2);
  box-shadow:0 20px 50px rgba(0,0,0,.5);animation:dFrame 10s ease infinite}
.demoVid{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
/* Legibility for what sits on top, without flattening the footage into a
   silhouette: darkest at the two edges, lightest across the face. */
.demoScrim{position:absolute;inset:0;
  background:linear-gradient(180deg,rgba(10,8,7,.55) 0%,rgba(10,8,7,.06) 24%,
    rgba(10,8,7,.06) 48%,rgba(10,8,7,.78) 100%)}
.dFlash{position:absolute;inset:0;background:#fff;opacity:0;
  animation:dFlash 10s ease infinite}
/* transcribing — one pass down the frame */
.dScan{position:absolute;inset-inline:0;height:38%;top:-38%;
  background:linear-gradient(180deg,transparent,rgba(255,90,31,.16) 45%,
    rgba(255,201,60,.5) 82%,rgba(255,255,255,.85));
  opacity:0;
  animation:dScan 10s ease-in-out infinite}

/* --- captions: the real thing, at the real proportions --- */
.dcap{position:absolute;inset-inline:5%;top:57%;display:flex;flex-wrap:wrap;
  justify-content:center;align-items:center;gap:0 .13em;
  font-family:"Rubik",sans-serif;font-weight:900;letter-spacing:-.015em;
  line-height:1.3;opacity:0;transform:translateY(-50%) scale(.72)}
.dcap b{font-weight:900;display:inline-block}
/* 17.5% of the font size, the preset's own ratio — this is the whole look */
.st-viral,.st-stage,.st-solo{-webkit-text-stroke:3.6px #000;paint-order:stroke fill}
.st-bold{-webkit-text-stroke:2.6px #000;paint-order:stroke fill}
.st-viral,.st-bold{font-size:22px;
  text-shadow:0 2px 0 rgba(0,0,0,.35),0 4px 13px rgba(0,0,0,.6)}
.st-viral{color:#fff}
.st-viral .hi{color:#FFD93D}
.st-bold{color:#fff}
.st-bold .hi{color:#00E58A}
.st-solo{font-size:27px;color:#fff;
  text-shadow:0 2px 0 rgba(0,0,0,.3),0 5px 15px rgba(0,0,0,.6)}
/* the red box — the single most recognisable thing about this style */
.st-solo .box{background:#E11D48;border-radius:12px;padding:1px 13px 5px;
  box-shadow:0 5px 0 rgba(0,0,0,.3)}
.st-stage{font-size:22px;color:#FFE600;
  text-shadow:0 2px 0 rgba(0,0,0,.35),0 4px 13px rgba(0,0,0,.6)}

.dc1{animation:dCap1 10s cubic-bezier(.22,1,.36,1) infinite}
.dc2{animation:dCap2 10s cubic-bezier(.22,1,.36,1) infinite}
.dc3{animation:dCap3 10s cubic-bezier(.22,1,.36,1) infinite}
.dc4{animation:dCap4 10s cubic-bezier(.22,1,.36,1) infinite}

/* --- where it goes next --- */
.dSocial{position:absolute;inset-inline:0;bottom:15%;display:flex;
  justify-content:center;gap:11px;opacity:0;animation:dSocial 10s ease infinite}
.sIc{width:22px;height:22px;border-radius:50%;display:grid;place-items:center;
  background:rgba(255,255,255,.93);flex:0 0 auto;
  box-shadow:0 3px 10px rgba(0,0,0,.45)}
.sIc svg{width:13px;height:13px;fill:#14100D}
.sIc:nth-child(1){animation:dSocIn 10s ease infinite}
.sIc:nth-child(2){animation:dSocIn2 10s ease infinite}
.sIc:nth-child(3){animation:dSocIn3 10s ease infinite}
.sIc:nth-child(4){animation:dSocIn4 10s ease infinite}
.sIc:nth-child(5){animation:dSocIn5 10s ease infinite}

/* --- ready to post --- */
.dReady{position:absolute;inset-inline:0;bottom:6.5%;display:flex;justify-content:center;
  opacity:0;animation:dReady 10s ease infinite}
.dReady::before{content:"מוכן להעלאה";padding:7px 13px;border-radius:99px;
  font-size:10.5px;font-weight:700;color:#0E3324;background:var(--mint);
  white-space:nowrap;box-shadow:0 6px 14px rgba(0,0,0,.4)}

/* --- the six styles, active one tracking the caption on screen --- */
.dDots{display:flex;justify-content:center;gap:8px;margin-top:13px;
  opacity:0;animation:dDotsIn 10s ease infinite}
.dd{width:17px;height:17px;border-radius:50%;flex:0 0 auto;position:relative;
  border:2px solid rgba(255,255,255,.45);opacity:.32;transform:scale(.82)}
.d1{background:#FFD93D;animation:dDot1 10s ease infinite}
.d2{background:#00E58A;animation:dDot2 10s ease infinite}
.d3{background:#E11D48;animation:dDot3 10s ease infinite}
.d4{background:#FFE600;animation:dDot4 10s ease infinite}
.d5{background:#241C16;border-color:rgba(255,255,255,.3)}
.d6{background:#fff}
/* The selection ring is cream, not the swatch's own colour: a yellow ring
   around a yellow dot is invisible, and this ring's whole job is to say
   "this is the one that was kept". */
.d4::after{content:"";position:absolute;inset:-4px;border-radius:50%;
  border:2px solid var(--cream);opacity:0;animation:dPickRing 10s ease infinite}

/* --- the step being shown, in words, under the phone --- */
.dPhase{position:relative;height:17px;margin-top:11px}
.dph{position:absolute;inset-inline:0;top:0;color:var(--faint);font-size:12.5px;
  opacity:0}
.p1{animation:dPh1 10s ease infinite}
.p2{animation:dPh2 10s ease infinite}
.p3{animation:dPh3 10s ease infinite}
.p4{animation:dPh4 10s ease infinite}

/* Held, then dropped: it hangs above the card for a beat so the caption-less
   clip is actually legible, then falls into the phone and vanishes as the
   frame lights up around it. */
@keyframes dDrag{
  0%{opacity:0;transform:translateY(-12px) scale(.9) rotate(-8deg)}
  3%{opacity:1;transform:translateY(0) scale(1) rotate(-6deg)}
  9%{opacity:1;transform:translateY(7px) scale(1.02) rotate(-6deg)}
  15%{opacity:1;transform:translateY(170px) scale(.8) rotate(-1deg)}
  19%{opacity:0;transform:translateY(240px) scale(.5) rotate(0deg)}
  100%{opacity:0;transform:translateY(240px) scale(.5)}
}
@keyframes dSocial{
  0%,76%{opacity:0}
  82%,93%{opacity:1}
  97%,100%{opacity:0}
}
/* one after another, quickly — a little run of destinations */
@keyframes dSocIn{0%,77%{opacity:0;transform:translateY(7px) scale(.6)}
  81%{opacity:1;transform:translateY(0) scale(1.1)}83%,94%{opacity:1;transform:none}
  98%,100%{opacity:0}}
@keyframes dSocIn2{0%,79%{opacity:0;transform:translateY(7px) scale(.6)}
  83%{opacity:1;transform:translateY(0) scale(1.1)}85%,94%{opacity:1;transform:none}
  98%,100%{opacity:0}}
@keyframes dSocIn3{0%,81%{opacity:0;transform:translateY(7px) scale(.6)}
  85%{opacity:1;transform:translateY(0) scale(1.1)}87%,94%{opacity:1;transform:none}
  98%,100%{opacity:0}}
@keyframes dSocIn4{0%,83%{opacity:0;transform:translateY(7px) scale(.6)}
  87%{opacity:1;transform:translateY(0) scale(1.1)}89%,94%{opacity:1;transform:none}
  98%,100%{opacity:0}}
@keyframes dSocIn5{0%,85%{opacity:0;transform:translateY(7px) scale(.6)}
  89%{opacity:1;transform:translateY(0) scale(1.1)}91%,94%{opacity:1;transform:none}
  98%,100%{opacity:0}}
/* the frame reacting to something landing in it */
@keyframes dFrame{
  0%,15%{box-shadow:0 20px 50px rgba(0,0,0,.5);border-color:var(--line2)}
  19%{box-shadow:0 20px 50px rgba(0,0,0,.5);border-color:var(--hot)}
  27%,100%{box-shadow:0 20px 50px rgba(0,0,0,.5);border-color:var(--line2)}
}
@keyframes dFlash{
  0%,17%{opacity:0}
  19%{opacity:.18}
  25%,100%{opacity:0}
}
/* One second of scanning, deliberately: the promise is "within seconds",
   so the demo must not dwell here. */
@keyframes dScan{
  0%,19%{opacity:0;transform:translateY(0)}
  21%{opacity:1}
  28%{opacity:1;transform:translateY(420%)}
  30%,100%{opacity:0;transform:translateY(420%)}
}
/* Each chunk owns a slice of the cycle and hands over to the next, which is
   how captions actually behave: they replace, they do not accumulate. */
@keyframes dCap1{
  0%,27%{opacity:0;transform:translateY(-50%) scale(.72)}
  31%{opacity:1;transform:translateY(-50%) scale(1.06)}
  33%,40%{opacity:1;transform:translateY(-50%) scale(1)}
  42%,100%{opacity:0;transform:translateY(-50%) scale(.96)}
}
@keyframes dCap2{
  0%,42%{opacity:0;transform:translateY(-50%) scale(.72)}
  46%{opacity:1;transform:translateY(-50%) scale(1.06)}
  48%,54%{opacity:1;transform:translateY(-50%) scale(1)}
  56%,100%{opacity:0;transform:translateY(-50%) scale(.96)}
}
@keyframes dCap3{
  0%,56%{opacity:0;transform:translateY(-50%) scale(.66)}
  60%{opacity:1;transform:translateY(-50%) scale(1.08)}
  62%,66%{opacity:1;transform:translateY(-50%) scale(1)}
  68%,100%{opacity:0;transform:translateY(-50%) scale(.96)}
}
@keyframes dCap4{
  0%,68%{opacity:0;transform:translateY(-50%) scale(.72)}
  72%{opacity:1;transform:translateY(-50%) scale(1.06)}
  74%,92%{opacity:1;transform:translateY(-50%) scale(1)}
  97%,100%{opacity:0;transform:translateY(-50%) scale(.96)}
}
@keyframes dReady{
  0%,77%{opacity:0;transform:translateY(8px)}
  82%,92%{opacity:1;transform:translateY(0)}
  97%,100%{opacity:0;transform:translateY(8px)}
}
@keyframes dDotsIn{
  0%,25%{opacity:0}
  29%,94%{opacity:1}
  100%{opacity:0}
}
@keyframes dDot1{
  0%,27%{opacity:.32;transform:scale(.82)}
  31%,40%{opacity:1;transform:scale(1.2)}
  44%,100%{opacity:.32;transform:scale(.82)}
}
@keyframes dDot2{
  0%,42%{opacity:.32;transform:scale(.82)}
  46%,54%{opacity:1;transform:scale(1.2)}
  58%,100%{opacity:.32;transform:scale(.82)}
}
@keyframes dDot3{
  0%,56%{opacity:.32;transform:scale(.82)}
  60%,66%{opacity:1;transform:scale(1.2)}
  70%,100%{opacity:.32;transform:scale(.82)}
}
/* the one that gets kept */
@keyframes dDot4{
  0%,68%{opacity:.32;transform:scale(.82)}
  72%,92%{opacity:1;transform:scale(1.2)}
  97%,100%{opacity:.32;transform:scale(.82)}
}
@keyframes dPickRing{
  0%,70%{opacity:0;transform:scale(.7)}
  75%,92%{opacity:1;transform:scale(1)}
  96%,100%{opacity:0}
}
@keyframes dPh1{0%{opacity:1}14%{opacity:1}17%,100%{opacity:0}}
@keyframes dPh2{0%,15%{opacity:0}18%{opacity:1}25%{opacity:1}28%,100%{opacity:0}}
@keyframes dPh3{0%,26%{opacity:0}29%{opacity:1}64%{opacity:1}67%,100%{opacity:0}}
@keyframes dPh4{0%,66%{opacity:0}69%{opacity:1}94%{opacity:1}97%,100%{opacity:0}}

/* Motion is decoration here, never the only carrier of information — the
   three steps in words sit right below it. Reduced motion gets the finished
   frame: the last caption up, the style kept, ready to post. The video
   keeps playing; it is footage, not a motion effect. */
@media(prefers-reduced-motion:reduce){
  .dDrag,.dScan,.dFlash{display:none;animation:none!important}
  .dSocial{animation:none!important;opacity:1!important}
  .sIc{animation:none!important;opacity:1!important;transform:none!important}
  .demoFrame{animation:none!important}
  .dcap{animation:none!important}
  .dc1,.dc2,.dc3{opacity:0!important}
  .dc4{opacity:1!important;transform:translateY(-50%)!important}
  .dReady{animation:none!important;opacity:1!important;transform:none!important}
  .dDots{animation:none!important;opacity:1!important}
  .dd{animation:none!important}
  .d4{opacity:1!important;transform:scale(1.2)!important}
  .d4::after{animation:none!important;opacity:1!important;transform:scale(1)!important}
  .dph{animation:none!important}
  .p1,.p2,.p3{opacity:0!important}
  .p4{opacity:1!important}
}

/* ---------- the gate ----------
   Shown after a video has been dropped, never before: asking who somebody is
   before they have seen what the thing does is the fastest way to lose them. */
.sheet.gate{text-align:center}
.sheet.gate .gateArt{margin:2px auto 14px}
.sheet.gate .gateArt img{height:46px;width:auto;display:block;margin:0 auto}
.sheet.gate b{font-size:19px;margin-bottom:8px}
.sheet.gate .note{margin-bottom:16px;font-size:13.5px}
.sheet.gate .note strong{color:var(--sun);font-weight:700}
.gateWhy span{display:flex;align-items:center;gap:7px}
.gateWhy svg{color:var(--mint);flex:0 0 auto}
.gateWhy{display:flex;flex-direction:column;gap:6px;margin-bottom:18px;
  color:var(--faint);font-size:12.5px;text-align:start;
  padding:12px 14px;border-radius:14px;background:var(--surface);
  border:1px solid var(--line)}
.btn.google{background:#fff;color:#1f1f1f;border:0;font-weight:600;min-height:52px}
.btn.google:hover:not(:disabled){background:#f2f2f2;transform:translateY(-1px)}
.ghost.wide{width:100%;text-align:center}
.gateErr{background:rgba(255,77,109,.1);border:1px solid rgba(255,77,109,.32);
  color:#FFB3C0;border-radius:12px;padding:10px 13px;font-size:12.5px;
  margin-top:12px;text-align:start;line-height:1.6}

/* ---------- toast + dialogs ----------
   These replace prompt/confirm/alert, which are unstyled OS sheets on a
   phone and are suppressed outright inside some in-app browsers — the
   folder buttons simply did nothing there, with no way to tell why. */
#toast{position:fixed;z-index:180;inset-inline:0;bottom:calc(22px + env(safe-area-inset-bottom));
  margin-inline:auto;width:max-content;max-width:min(420px,calc(100vw - 32px));
  background:var(--high);border:1px solid var(--line2);color:var(--cream);
  border-radius:14px;padding:12px 18px;font-size:13.5px;text-align:center;
  box-shadow:var(--shadow);opacity:0;transform:translateY(14px);
  transition:.22s cubic-bezier(.22,1,.36,1);pointer-events:none}
#toast.up{opacity:1;transform:none}
#toast.bad{border-color:rgba(255,77,109,.5);color:#FFB3C0}

.sheetBack{position:fixed;inset:0;z-index:190;background:rgba(8,6,5,.62);
  backdrop-filter:blur(3px)}
.sheet{position:fixed;z-index:191;left:50%;top:50%;transform:translate(-50%,-50%);
  width:min(400px,calc(100vw - 32px));background:var(--raised);
  border:1px solid var(--line2);border-radius:20px;padding:20px;box-shadow:var(--shadow)}
.sheet b{display:block;font-family:"Secular One",sans-serif;font-size:18px;margin-bottom:6px}
.sheet .note{margin-bottom:12px}
.sheet input{margin:6px 0 14px}
.sheetList{display:flex;flex-direction:column;gap:6px;margin:4px 0 12px;
  max-height:44vh;overflow:auto}
.sheetItem{text-align:start;padding:12px 14px;border-radius:12px;border:1px solid var(--line);
  background:var(--surface);font-size:14px;min-height:46px}
.sheetItem:hover{border-color:var(--hot);background:var(--high)}
.sheetAct{display:flex;gap:8px}
.sheetAct .btn{flex:1}
.btn.danger{background:var(--rose);border:0;color:#fff;font-weight:700}
@media(max-width:720px){
  .sheet{left:0;top:auto;bottom:0;transform:none;width:100%;
    border-radius:24px 24px 0 0;padding:18px 18px calc(18px + env(safe-area-inset-bottom));
    animation:sheet .22s cubic-bezier(.22,1,.36,1)}
}

/* ---------- hooks ---------- */
.hooklist{display:flex;flex-direction:column;gap:8px;margin-bottom:10px}
.hookrow{display:flex;align-items:center;gap:10px;padding:11px 12px;border-radius:14px;
  background:var(--raised);border:1px solid var(--line);cursor:pointer}
.hookrow.on{border-color:var(--hot);background:var(--hot-dim)}
.hookrow .tx{flex:1;font-size:14px;font-weight:600;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap}
.hookrow .tm{font:11.5px/1 ui-monospace,monospace;color:var(--faint);direction:ltr}
.hookEd{margin-top:12px;padding-top:12px;border-top:1px solid var(--line)}
.chips{display:flex;gap:8px;flex-wrap:wrap}
.chip{width:30px;height:30px;border-radius:50%;border:2px solid transparent;cursor:pointer;
  padding:0;min-height:0}
.chip.on{border-color:var(--cream);transform:scale(1.12)}

/* ---------- one player, two views ---------- */
.viewSwitch{display:flex;gap:4px;margin-top:10px;background:var(--surface);
  border:1px solid var(--line);border-radius:13px;padding:3px;align-self:center}
.viewSwitch button{padding:7px 18px;border-radius:10px;font-size:13px;
  font-weight:600;color:var(--dim);transition:.15s}
.viewSwitch button.on{background:var(--raised);color:var(--cream);
  box-shadow:0 2px 8px rgba(0,0,0,.5)}
.player.isResult{outline:2px solid var(--mint);outline-offset:-2px}
/* Only once somebody has actually dragged. Until then the default layout —
   which the screen-size tests measure — is untouched at every width. */
.stageWrap.sized .player{width:min(100%,calc(var(--phUser) * var(--ar,.5625)));
  aspect-ratio:var(--ar,.5625);height:auto;max-height:var(--phUser);
  margin-inline:auto}
.stageWrap.sized .player video,.stageWrap.sized .player img.poster{
  width:100%;height:100%;object-fit:contain}

/* ---------- laptop: the stage is a column, not a scroll box ----------
 *
 * The counterpart to the overflow:hidden above. With no scrollbar to spill
 * into, the picture has to be given a height it can actually have, or it
 * would simply be clipped — so the stage is a fixed-height flex column and
 * the player is the one item in it that flexes. Height is the scarce axis;
 * the width follows from the ratio, exactly as it does in the dock.
 *
 * Scoped away from the panel: --vh there is the whole Premiere WINDOW, not
 * this frame, and sizing off it is how a 460px player ended up inside a 380px
 * dock (see .inPanel .stageWrap). Scoped away from .isFull for the same kind
 * of reason — in fullscreen the player owns the screen, not this column. */
@media (min-width:961px){
  /* No height of its own: the stage is as tall as it needs to be, up to the
     cap. Only when it would pass the cap does anything have to give — and
     what gives is the picture, not a scrollbar. */
  html:not(.inPanel) .stageWrap{display:flex;flex-direction:column;
    align-items:center}
  /* Everything that is not the picture keeps its own height and its own
     width: .transport lays out a scrubber across the column, and centring
     it would shrink-wrap it to its buttons. */
  html:not(.inPanel) .stageWrap > .sizeGrip,
  html:not(.inPanel) .stageWrap > .transport,
  html:not(.inPanel) .stageWrap > .viewSwitch,
  html:not(.inPanel) .stageWrap > .savedTag{flex:0 0 auto;align-self:stretch}
  html:not(.inPanel) .stageWrap > .viewSwitch{align-self:center}
  /* Shrink, never grow: at its natural size the picture is exactly what it
     was before this change, and it only gives ground when the stage would
     otherwise have to scroll. Height is the main axis, so that is what flex
     takes from it; width is auto and follows the ratio back down. */
  html:not(.inPanel) .stageWrap > .player:not(.isFull){flex:0 1 auto;min-height:0;
    width:auto;height:auto;max-width:100%;aspect-ratio:var(--ar,.5625);
    margin-inline:auto}
  html:not(.inPanel) .stageWrap > .player:not(.isFull) video,
  html:not(.inPanel) .stageWrap > .player:not(.isFull) img.poster{
    width:100%;height:100%;object-fit:contain}
  /* The grip's choice is a ceiling on the picture, never a licence to grow
     past the stage: the flex line is what it is, and --phUser only ever
     makes the picture smaller than the room it was given. */
  html:not(.inPanel) .stageWrap.sized > .player:not(.isFull){
    max-height:var(--phUser)}
}

/* In fullscreen the player owns the screen, so the frame is centred in it
   rather than filling a column. */
/* Fullscreen: the player is the screen, and the picture is centred inside
   it at its own aspect ratio. A vertical clip gets black bars either side
   rather than being stretched across a landscape monitor — and fit() scales
   the caption layer to the picture's box, not the screen's, so the captions
   stay on the video where they belong. */
.player.isFull{width:100vw;height:100vh;max-height:none;aspect-ratio:auto;
  border-radius:0;background:#000;display:grid;place-items:center}
.player.isFull video,.player.isFull img.poster{width:auto;height:100%;
  max-width:100vw;max-height:100vh;object-fit:contain}
.player.isFull .stageWrap{max-height:none}

/* ---------- what "improve" actually does ---------- */
/* Written next to the switch that turns it on, because the old version was a
   button called "improve and download" that changed your captions and your
   sound without saying so until after it had done it. */
.prep{background:var(--surface);border:1px solid var(--line);border-radius:18px;
  padding:14px 16px;margin-bottom:12px}
.prepHead{font-family:"Secular One",sans-serif;font-size:14px;color:var(--dim);
  margin-bottom:10px}
.opt + .opt{margin-top:14px;padding-top:14px;border-top:1px solid rgba(255,255,255,.05)}
.optTop{display:flex;align-items:center;gap:10px;margin-bottom:5px;flex-wrap:wrap}
.optTop b{font-size:14px;font-weight:600}
.optTop .seg{margin-inline-start:auto}
.seg.sm button{padding:6px 10px;font-size:11.5px}
.sw2{margin-inline-start:auto;position:relative;width:46px;height:27px;flex:0 0 auto;cursor:pointer}
.sw2 input{position:absolute;opacity:0;width:100%;height:100%;margin:0;cursor:pointer}
.sw2 i{position:absolute;inset:0;border-radius:99px;background:var(--high);
  border:1px solid var(--line2);transition:.18s}
.sw2 i::after{content:"";position:absolute;top:3px;inset-inline-start:3px;width:19px;height:19px;
  border-radius:50%;background:var(--cream);transition:.18s}
.sw2 input:checked + i{background:var(--hot);border-color:var(--hot)}
/* inset-inline flips with the writing direction on its own, so the knob
   travels from the start edge to the end edge in both RTL and LTR. */
.sw2 input:checked + i::after{inset-inline-start:auto;inset-inline-end:3px}
.sw2 input:focus-visible + i{outline:2px solid var(--hot);outline-offset:2px}

/* ---------- saved looks + recent colours ---------- */
.recents{display:flex;align-items:center;gap:7px;margin-top:10px;flex-wrap:wrap}
.recents .note{margin-inline-end:2px}
.looks{display:flex;gap:7px;flex-wrap:wrap}
.look{display:inline-flex;align-items:center;gap:7px;padding:8px 12px;border-radius:99px;
  border:1px solid var(--line);background:var(--raised);font-size:12.5px;
  color:var(--dim);transition:.15s;min-height:38px}
.look:hover{border-color:var(--hot);color:var(--cream)}
.look i{width:11px;height:11px;border-radius:50%;flex:0 0 auto;display:block}
.look .rm{opacity:.45;font-size:11px;margin-inline-start:2px}
.look .rm:hover{opacity:1;color:var(--rose)}
.look.add{border-style:dashed}
/* Delete sits beside its chip, not inside it: one control per job. */
.lookRm{width:28px;height:28px;display:grid;place-items:center;border-radius:99px;
  color:var(--faint);border:1px solid transparent;margin-inline-start:-4px;
  transition:color .15s ease,border-color .15s ease,background-color .15s ease}
.lookRm svg{width:13px;height:13px;fill:none;stroke:currentColor;stroke-width:2.4;stroke-linecap:round}
@media (hover:hover) and (pointer:fine){.lookRm:hover{color:var(--rose);border-color:var(--line2)}}
/* Icons inside a sentence sit on the line, not on a line of their own. */
.note svg,.banner svg:not(.okdot svg){display:inline-block;vertical-align:-2px}

/* ---------- misc ---------- */
.note{color:var(--faint);font-size:12.5px;line-height:1.85}
.banner{border-radius:15px;padding:13px 15px;font-size:13.5px;line-height:1.7}
/* Opaque, precomposited on --ink: a translucent banner over the pinned
   footer's own gradient measured anywhere from 1.4:1 to fine depending on
   what scrolled underneath it. */
.banner.bad{background:#251214;border:1px solid rgba(255,77,109,.32);color:#FFB3C0}
.banner.ok{background:#111D17;border:1px solid rgba(62,213,152,.3);color:#A6EBCC}
.okdot{width:26px;height:26px;border-radius:50%;background:var(--mint);color:#08251A;
  display:grid;place-items:center;font-weight:800;flex:0 0 auto}
.banner.ok b{font-family:"Secular One",sans-serif;font-size:17px}
.banner.info{background:var(--raised);border:1px solid var(--line);color:var(--dim)}
.bar{height:6px;border-radius:99px;background:var(--high);overflow:hidden}
/* The fill is revealed, not resized: a width transition re-lays-out the row
   on every poll, and clip-path keeps the rounded end rounded at any value,
   which a scaleX would squash. RTL page, so it fills from the right, and the
   inset is taken off the left. --p is set from JS as a percentage. */
.bar>i{display:block;height:100%;width:100%;border-radius:99px;
  background:linear-gradient(90deg,var(--sun),var(--hot));
  clip-path:inset(0 0 0 calc(100% - var(--p,0%)) round 99px);
  transition:clip-path .45s cubic-bezier(.22,1,.36,1)}
/* Before the first measurement there is no percentage to show, and an empty
   bar looks like nothing is happening. */
.bar.idle{background:linear-gradient(90deg,var(--high) 40%,var(--hot) 50%,var(--high) 60%)
  var(--high);background-size:250% 100%;animation:sweep 1.6s linear infinite}
@keyframes sweep{from{background-position:150% 0}to{background-position:-50% 0}}
.eta{white-space:nowrap;font-variant-numeric:tabular-nums}
.spin{display:inline-block;width:13px;height:13px;border:2px solid var(--line2);
  border-top-color:var(--hot);border-radius:50%;animation:sp .7s linear infinite;
  vertical-align:-2px;margin-inline-end:7px}
@keyframes sp{to{transform:rotate(360deg)}}
.slide{animation:slide .35s cubic-bezier(.22,1,.36,1)}
@keyframes slide{from{opacity:0;transform:translateY(9px)}}
.empty{color:var(--faint);text-align:center;padding:42px 20px;font-size:13.5px}
kbd{background:var(--raised);border:1px solid var(--line);border-radius:6px;padding:1px 6px;
  font:11px/1.7 ui-monospace,monospace}
#offline{position:fixed;inset:0;z-index:200;display:grid;place-content:center;text-align:center;
  gap:6px;background:rgba(13,11,10,.95);backdrop-filter:blur(8px)}
#offline b{font-family:"Secular One",sans-serif;font-size:22px}
#offline div{color:var(--dim);font-size:13.5px}
#offline code{background:var(--raised);border:1px solid var(--line);border-radius:7px;
  padding:3px 9px;direction:ltr;display:inline-block;font-size:12.5px}

/* ---------- tour ---------- */
#tourMask{position:fixed;inset:0;z-index:150}
#tourHole{position:absolute;border-radius:18px;box-shadow:0 0 0 9999px rgba(8,6,5,.8);
  pointer-events:none;outline:2px solid var(--hot);outline-offset:4px}
#tourCard{position:fixed;z-index:151;width:min(340px,calc(100vw - 28px));background:var(--raised);
  border:1px solid var(--line2);border-radius:20px;padding:17px 19px;box-shadow:var(--shadow)}
#tourCard b{display:block;font-family:"Secular One",sans-serif;font-size:17px;margin-bottom:6px}
#tourCard p{margin:0;color:var(--dim);font-size:13.5px;line-height:1.75}
#tourCard .foot{display:flex;align-items:center;gap:9px;margin-top:15px}
#tourCard .dots{display:flex;gap:5px;margin-inline-end:auto}
#tourCard .dots i{width:6px;height:6px;border-radius:50%;background:var(--line2)}
#tourCard .dots i.on{background:var(--hot);width:17px;border-radius:99px}

/* ---------- phone: last, so it wins ----------
   The vertical budget is the whole problem here. Measured on the old build:
   nav 88 + player 354 + transport 54 + pinned footer 206 = 702 of an iPhone
   14's 844px, leaving 142px to actually edit in — and 73px on an SE. Every
   number below is chosen against that budget, and qa/qa.js asserts the
   result stays above 30% of the screen. */
@media(max-width:960px){
  .editor{grid-template-columns:1fr;gap:14px;display:flex;flex-direction:column}
  /* Slimmer chrome: the brand does not need 88px of a phone. */
  /* One row, always. A second row here costs the editor 22px and pushes the
     sticky player out of step with the offset it is positioned against. */
  .nav{padding:calc(8px + env(safe-area-inset-top)) 12px 8px;gap:6px;flex-wrap:nowrap}
  .nav .sp{gap:3px;flex:0 0 auto}
  .activityToggle{width:36px;height:36px;border-radius:10px}
  .activityToggle svg{width:19px;height:19px}
  .activityBadge{top:-2px;inset-inline-end:-2px}
  .activityBack{background:rgba(0,0,0,.58)}
  .activityPanel{top:auto;bottom:0;inset-inline:0;width:100%;max-height:min(78 * var(--vh),680px);
    border-radius:20px 20px 0 0;border-bottom:0;padding-bottom:env(safe-area-inset-bottom);
    animation-name:activityUp}
  @keyframes activityUp{from{opacity:0;transform:translateY(18px)}to{opacity:1;transform:none}}
  .activityHead{padding:17px 16px 14px}
  .activityList{padding:0 15px}
  .activityItem > img{width:48px;height:64px}
  .brand{font-size:15px;min-width:0;overflow:hidden;text-overflow:ellipsis;
    white-space:nowrap;flex:0 0 auto}
  .brand .mark{height:21px}
  .ghost{padding:7px 9px;font-size:12.5px;white-space:nowrap}
  /* The avatar is enough to say whose account this is; the name is not
     worth a row of the screen. */
  /* The avatar alone is enough to say whose account this is, but the name
     is collapsed rather than erased: font-size:0 was the same trick that
     left the back button announced as an unnamed "button". Clipping keeps
     the text in the accessibility tree while taking no space. */
  /* min-size, because with no profile picture the chip was its padding and
     nothing else: measured 14x14 on a 390px phone, under WCAG 2.5.8's 24. */
  .meChip{gap:0;padding:6px;min-width:36px;min-height:36px;justify-content:center}
  #navHelp{min-width:36px;min-height:36px}
  .meChip .nm{position:absolute;width:1px;height:1px;overflow:hidden;
    clip-path:inset(50%);white-space:nowrap}
  .meChip img{width:22px;height:22px}
  /* Narrow: the chevron alone, but the label stays in the accessibility
     tree rather than being collapsed to nothing. */
  #navLib{padding:7px 10px;gap:0;min-height:36px;min-width:36px}
  #navLib span{position:absolute;width:1px;height:1px;overflow:hidden;
    clip-path:inset(50%);white-space:nowrap}

  /* align-self/width, or the wrapper shrink-wraps to the player and takes
     the player with it to the inline edge instead of centring. */
  .stageWrap{order:0;position:sticky;top:var(--navH);z-index:20;max-height:none;
    overflow:visible;display:flex;flex-direction:column;align-items:center;
    align-self:stretch;width:100%;padding-top:8px}
  /* Drive the box off its aspect ratio, not off the media's intrinsic size:
     a 1080-wide video with width:auto lays out 1080 wide and drags the whole
     layout viewport out with it. fit() already publishes --ar.

     Size the WIDTH and let the ratio set the height. Fixing the height and
     capping the width instead over-constrained the box, so a 16:9 clip sat
     in a 9:16-tall frame with 67px of black above and below it. */
  /* The default is a guess that suits most people; --phUser is what the
     size grip writes when somebody decides otherwise, and it is remembered
     per device. One knob, theirs when they touch it. */
  .stageWrap{--phMax:var(--phUser,min(30 * var(--vh),280px))}
  .player{width:min(100%,calc(var(--phMax) * var(--ar,.5625)));
    aspect-ratio:var(--ar,.5625);height:auto;max-height:var(--phMax);
    margin-inline:auto}
  .player video,.player img.poster{width:100%;height:100%;object-fit:contain}
  /* No hover on touch, so the one line that teaches the core gesture was
     invisible on exactly the devices that needed it most. */
  .hintbar{opacity:.85;font-size:10.5px;bottom:6px}
  .transport{align-self:stretch;margin-top:8px;gap:8px}
  /* Was height:8px, which undid the 30px touch target — on a phone that is
     exactly where it is needed. The BAR is still 8px; it is drawn by
     ::before inside a taller, invisible hit area. */
  .transport .scrub{height:30px}
  /* "Saved" floats over the top of the picture instead of holding a 22px
     row open under the transport for a word that is on screen for 1.6s:
     on a phone that row came straight out of the editing space. */
  .savedTag{position:absolute;top:8px;inset-inline:0;margin:0 auto;width:max-content;
    height:auto;z-index:8;padding:3px 10px;border-radius:99px;pointer-events:none;
    background:rgba(13,11,10,.82);border:1px solid rgba(62,213,152,.35)}
  /* Only shown once a result exists, but when it is, it competes with the
     transcript for the same pixels. */
  .viewSwitch{margin-top:7px;padding:2px}
  .viewSwitch button{padding:6px 14px;font-size:12px}

  .panel{order:1;min-height:0}
  /* A pinned footer is worth about 90px of a phone, not 206. One action and
     one line of status stay; the second button and all the prose move up
     into the flow, where scrolling past them costs nothing. */
  .pinned{padding:10px 0 calc(4px + env(safe-area-inset-bottom))}
  .pinned .banner{padding:9px 12px;font-size:12.5px}
  .pinned .banner .note,.pinned .banner .row .note{display:none}
  /* A finished project's banner was 13px taller than a ready one's, which is
     enough to drop an iPhone below the editing-space floor. The tick and the
     display face are decoration; the switch above already says there is a
     result. */
  .pinned .okdot{display:none}
  .pinned .banner.ok b{font-family:inherit;font-size:13.5px}
  .pinned > #exportBox > .note{display:none}
  .pinned .btn{padding:13px 16px}
  .pinned .btn.cta{font-size:16px}

  .style{flex:0 0 40vw;max-width:160px;scroll-snap-align:start}
  .styleNav{width:42px;height:42px}
  .styleHero{margin-bottom:0}
  .styleHead{margin-bottom:10px}
  .styleHead h2{font-size:19px}
  .styleHead .note{display:none}

  /* Five tools, five equal columns, no scroller. It used to be
     flex:1 0 104px — 480px of tabs in a 345px viewport, so "חיתוך" and
     "הוק פותח" were off-screen with nothing to say they existed. */
  .toolbar{overflow:visible}
  .tool{flex:1 1 0;min-width:0;padding:10px 3px}
  .tool .tt{font-size:12.5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .tool .sum{display:none}
  .toolPanel{padding:14px 13px}

  .wrap{padding:14px 14px 40px}
  .hero h1{font-size:30px}
  /* For a stranger this page has one job: show the thing working. The demo
     reel started 533px down, so an iPhone SE showed 134px of it — the top
     edge of a video and nothing happening. Everything above it is trimmed
     so the reel starts near the fold instead of below it. The drop zone
     stays the biggest target on the screen; it just stops being padding. */
  body.signedOut .hero{padding:12px 0}
  body.signedOut .hero h1{margin-bottom:5px}
  body.signedOut .hero p{font-size:15px;line-height:1.45}
  body.signedOut .drop{padding:24px 20px}
  body.signedOut .drop .ic{width:48px;height:48px;border-radius:16px;margin-bottom:10px}
  body.signedOut .drop .ic svg{width:22px;height:22px}
  body.signedOut .drop b{font-size:19px}
  body.signedOut .demoReel{margin-top:18px}
  /* Two columns. At one column a 9:16 card is 644px tall, so a phone showed
     exactly ONE project per screen and ten projects meant ten screens of
     scrolling. 4/5 keeps the vertical feel and fits three rows. */
  .grid{grid-template-columns:repeat(2,1fr);gap:10px;margin-top:16px}
  .card .face{aspect-ratio:4/5}
  .card .meta{padding:8px 10px 9px}
  .card .meta b{font-size:12.5px}
  /* 10px and 10.5px here were the smallest type in the app, on the screen
     where it is hardest to read and in a script with no ascenders to help.
     No WCAG criterion names a minimum size, so this is a house rule rather
     than a compliance fix — but "מוכן לעריכה" at 10px on a phone is a label
     you squint at, and these two carry a card's entire status. */
  .card .sub{font-size:11.5px}
  body.hasProjects .drop span{display:none}
  body.hasProjects .drop b{font-size:14.5px}
  .tabs{margin:14px 0 0}
  .tab{padding:8px 18px;font-size:13.5px}
  .rows{max-height:none}
  .ic-btn{width:38px;height:38px}
  .pp{width:46px;height:46px;border-radius:13px}
  /* track is 6px and the thumb 28px, so the offset is -(28-6)/2 = -11.
     It said -19, which floated the thumb 8px above the track it controls. */
  input[type=range]{height:44px}
  input[type=range]::-webkit-slider-thumb{margin-top:-11px;width:28px;height:28px}
  .wd{font-size:16px;padding:6px 10px}
  .rwact{opacity:1}
  .ctrl{padding:10px 0}
}
/* The transport on a phone. Six controls and a move chip in 337px meant a
   63px scrubber, which is not something a thumb can land a word with, and
   the chip wrapped to a second row anyway. So the second row is made on
   purpose: the scrubber and the clock get the full width on top, the buttons
   share the row under it. Measured on an iPhone SE: 94px became 84px, and
   the scrubber went from 63px to the width of the column. */
@media (max-width:480px){
  .transport{row-gap:8px}
  .transport .scrub{order:-2;flex:1 1 0;min-width:0}
  .transport .tc{order:-1}
  .transport .scrub + .tc{flex:0 0 auto}
  .transport > :not(.scrub):not(.tc){order:0}
  .transport::after{content:"";order:-1;flex-basis:100%;height:0}
}
/* In the pinned footer the banner already has 10px under it; the button's
   own 16px on top made a 26px hole in the most expensive strip of a phone. */
@media (max-width:720px){
  .pinned #exportBox{margin-top:0}
}
/* On a phone the four row actions are 161px of a 336px row, and they sat
   BESIDE the words: measured, the words got 91px and fell into a column of
   one word per line. The words get the whole width; the actions get a line
   of their own under them, at the end of the row, quieter on rows that are
   not the one playing. */
@media (max-width:720px){
  .rw{flex-wrap:wrap;row-gap:2px}
  .rw .ws{flex:1 1 calc(100% - 52px)}
  .rwact{flex:1 0 100%;justify-content:flex-end}
  .rw:not(.on):not(.gone) .rwact{opacity:.6}
}

/* An iPhone SE is 667px tall, so the same proportions that leave an iPhone 14
   comfortable leave it cramped. Below 720px the player and the footer both
   give up a little more, which is the right trade: on the small screen the
   list of words matters more than the size of the preview. */
@media(max-width:960px) and (max-height:720px){
  .stageWrap{--phMax:min(24 * var(--vh),215px)}
  .transport{margin-top:6px}
  .savedTag{display:none}
  .pinned{padding:8px 0 calc(2px + env(safe-area-inset-bottom))}
  .pinned .banner{padding:7px 11px;font-size:12px;border-radius:11px}
  #statusBar:not(:empty){margin-bottom:7px}
  .pinned .btn{padding:11px 14px}
  .styleHead{margin-bottom:8px}
}

/* Short screens: the footer gives up its prose first, and on a landscape
   phone — where the old build produced a NEGATIVE amount of usable space —
   the layout goes back to two columns, which is what a wide short screen
   actually wants. */
@media(max-width:960px) and (max-height:560px){
  .pinned{position:static;background:none;padding-top:8px}
  .stageWrap{position:static}
}
@media(max-width:960px) and (max-height:560px) and (orientation:landscape){
  .editor{flex-direction:row;align-items:flex-start;gap:14px}
  .stageWrap{order:1;flex:0 0 min(38vw,320px);width:auto;align-self:flex-start;
    position:sticky;top:var(--navH)}
  /* Only the budget changes here; the box still takes its shape from the
     video through the base rule above. */
  .stageWrap{--phMax:calc(100 * var(--vh) - var(--navH) - 92px)}
  .panel{order:0;flex:1 1 auto;min-width:0}
  .styles{gap:8px}
  .style{flex:0 0 132px}
  .nav{padding:calc(5px + env(safe-area-inset-top)) 14px 5px}
}

/* Below this the wordmark goes and the logo speaks for the brand: the nav
   also carries the quota strip, a bell, an admin link and a sign-in button,
   and at 375px the row was 14px wider than the screen — which puts a
   horizontal scrollbar on every page, not just this one.
   It lives here, after the 960px block, because a .brand rule in there was
   overriding the same declaration written earlier in the file. */
@media(max-width:520px){
  /* The name is HIDDEN on a narrow screen, not shrunk to nothing.
     font-size:0 leaves real text at zero pixels: it still occupies the
     accessibility tree, it is what a text-size preference scales up into
     view, and it is the kind of thing that reads as a bug in an audit. The
     logo carries the brand here; the words are kept for a screen reader. */
  .brand{gap:0}
  .brand .brandName{position:absolute;width:1px;height:1px;margin:-1px;
    padding:0;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);
    white-space:nowrap;border:0}
  .brand .mark{height:23px}
}

/* ---------- library bar: folders, Drive-style ---------- */
.libBar{display:flex;align-items:center;gap:10px;margin:26px 0 2px;flex-wrap:wrap}
.folderbar{display:flex;align-items:center;gap:7px;flex-wrap:wrap;flex:1;min-width:0}
.fchip{display:inline-flex;align-items:center;gap:7px;padding:7px 13px;
  border-radius:99px;font-size:13px;font-weight:600;color:var(--dim);
  background:var(--surface);border:1px solid var(--line);
  transition:color .15s ease,border-color .15s ease,background-color .15s ease;
  white-space:nowrap}
.fchip:hover{color:var(--cream);border-color:var(--line2)}
.fchip.on{color:var(--cream);background:var(--raised);border-color:var(--hot)}
.fchip .n{font-size:11.5px;color:var(--faint);
  background:var(--ink);border-radius:99px;padding:1px 7px}
.fchip.on .n{color:var(--dim)}
.fedit{width:30px;height:30px;display:grid;place-items:center;border-radius:99px;
  color:var(--faint);margin-inline-start:-4px;border:1px solid transparent;
  transition:color .15s ease,border-color .15s ease}
.fedit svg{width:14px;height:14px;fill:none;stroke:currentColor;stroke-width:2;
  stroke-linecap:round;stroke-linejoin:round}
@media (hover:hover) and (pointer:fine){.fedit:hover{color:var(--hot);border-color:var(--line2)}}
.activityClose svg{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:2.2;stroke-linecap:round}
.newFolder{flex:0 0 auto;gap:7px;font-size:12.5px;padding:7px 13px;min-height:0}
/* Nothing to organise yet, or nobody to organise it for. A signed-out visitor
   was shown "new folder" on the landing page, and it answered with a 401. */
body.signedOut .libBar,body.libEmpty .libBar{display:none}

/* the per-video menu */
.card .dots{position:absolute;z-index:4;top:8px;inset-inline-end:8px;
  width:32px;height:32px;border-radius:10px;display:grid;place-items:center;
  background:rgba(13,11,10,.72);border:1px solid var(--line2);
  color:var(--cream);opacity:0;backdrop-filter:blur(6px);
  transition:opacity .15s ease,background-color .15s ease,border-color .15s ease}
.card .dots svg{width:16px;height:16px;fill:currentColor}
.card:hover .dots,.card .dots:focus-visible{opacity:1}
/* On touch there is no hover to reveal it, so it is simply always there —
   and bigger. 32px is under every tap-target guideline there is (Apple says
   44, WCAG 2.5.5 says 44, the suite here asks for 34), and this is the only
   way to delete a project from a phone. */
body.touch .card .dots{opacity:1;width:40px;height:40px;border-radius:12px}
body.touch .card .dots svg{width:18px;height:18px}
.card .dots:hover{background:var(--raised);border-color:var(--hot)}

.empty{text-align:center;color:var(--faint);font-size:14px;padding:40px 20px;
  line-height:1.8}

/* ---------- the site footer ---------- */
.siteFoot{margin-top:46px;padding:20px 0 8px;border-top:1px solid var(--line);
  display:flex;flex-wrap:wrap;align-items:center;gap:6px 18px;
  font-size:12.5px;color:var(--faint)}
.siteFoot a{color:var(--dim);text-decoration:none}
.siteFoot a:hover{color:var(--cream);text-decoration:underline}
.siteFoot .footNote{margin-inline-start:auto;color:var(--faint)}
@media(max-width:620px){
  .siteFoot{gap:6px 14px}
  .siteFoot .footNote{margin-inline-start:0;flex-basis:100%;line-height:1.6}
}

/* ---------- accessibility ---------- */
/* Present to a screen reader, absent to everyone else. clip-path rather
   than display:none, which would remove it from the accessibility tree
   along with everything else. */
.srOnly{position:absolute;width:1px;height:1px;margin:-1px;padding:0;
  overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}
/* The player only shows its key hint once it has keyboard focus, so it
   costs a mouse user nothing. */
.player:focus-visible{outline:2px solid var(--hot);outline-offset:3px}
.keyHint{position:absolute;z-index:6;inset-inline:0;bottom:8px;margin-inline:auto;
  width:max-content;max-width:92%;padding:6px 12px;border-radius:12px;
  background:rgba(13,11,10,.88);border:1px solid var(--line2);
  color:var(--cream);font-size:11.5px;line-height:1.45;text-align:center;
  white-space:normal;text-wrap:balance;pointer-events:none;
  opacity:0;transition:opacity .15s}
.player:focus-visible .keyHint{opacity:1}
@media(max-width:520px){.keyHint{font-size:10.5px;padding:5px 9px}}
/* Visible only when it has focus, which is the whole point: it is the first
   thing a keyboard reaches and invisible to everybody else. */
.skipLink{position:absolute;inset-inline-start:-9999px;top:8px;z-index:100;
  padding:11px 18px;border-radius:12px;background:var(--hot);color:#1A0E06;
  font-weight:700;font-size:14px;text-decoration:none}
.skipLink:focus{inset-inline-start:12px}
/* Second in the stack, so both are reachable without one covering the other. */
.skipPlayer:focus{top:56px}

/* A back button that reads as one. It used to be a text link whose label was
   hidden with font-size:0 on a phone, leaving an arrow with no accessible
   name — so a screen reader announced "button" and nothing else. */
.backBtn{display:inline-flex;align-items:center;gap:6px;padding:8px 14px 8px 11px;
  border-radius:12px;font-size:13.5px;font-weight:600;color:var(--cream);
  background:var(--raised);border:1px solid var(--line2);transition:.16s}
.backBtn svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:2.4;
  stroke-linecap:round;stroke-linejoin:round;flex:0 0 auto;
  /* the chevron points back, whichever way the page runs */
  transform:scaleX(1)}
.backBtn:hover{border-color:var(--hot);background:var(--high)}
.backBtn:active{transform:translateY(1px)}

/* Keyboard focus had no visible target at all: every control here is a
   bare <button> or a div, and the UA outline is suppressed by the resets
   above. :focus-visible keeps it off for mouse clicks. */
:focus-visible{outline:2px solid var(--hot);outline-offset:2px;border-radius:4px}

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.01ms!important;animation-iteration-count:1!important;
    transition-duration:.01ms!important;scroll-behavior:auto!important}
  .pill .dot{animation:none;opacity:1}
}

/* ------------------------------------------------- inside the Premiere panel
 *
 * The same studio, in a frame about 400px wide docked next to a timeline.
 * What goes is only what the PANEL already provides or what makes no sense
 * there: its own brand row (the panel has one), the library and admin links
 * (the panel is about one clip), the sign-in button (the panel owns sign-in),
 * and the footer. The editor itself is untouched — that is the entire point
 * of loading the real thing rather than rebuilding a smaller one.
 */
.inPanel .nav,
.inPanel .siteFoot,
.inPanel #navLib,
.inPanel #navAdmin,
.inPanel #navSignIn,
.inPanel #quotaBar{display:none!important}

/* The nav is gone, so nothing should still be reserving room for it. */
.inPanel{--navH:0px}

.inPanel body{background:var(--ink)}
.inPanel .wrap{padding-inline:10px;padding-top:10px}
.inPanel .stageWrap{top:6px;max-height:calc(100 * var(--vh) - 20px)}

/* A docked panel is narrow enough that the desktop two-column layout would
   put the controls off-screen. The phone layout is the right one here, and it
   is already tested — so it is reused rather than reinvented. */
@media (max-width:520px){
  .inPanel .wrap{padding-inline:8px}
}

/* The captions panel's own toolbar: undo, redo, and the emoji suggester. */
.capBar{display:flex;align-items:center;gap:6px;margin-bottom:9px}
.capBarSep{width:1px;align-self:stretch;background:var(--line2);margin:2px 4px}
.capBar .ic-btn:disabled{opacity:.3;cursor:default}
.capBar .ic-btn:disabled:hover{border-color:var(--line2);background:var(--raised)}
.capBar .btn.sm{margin-inline-start:auto}

/* A line somebody typed rather than one that was transcribed. Marked, because
   "why is this word not highlighting with the audio" has exactly one answer
   and the UI should give it before the question is asked. */
.wd.k-text{border-bottom:1.5px dashed var(--sun);opacity:.95}

/* Native tooltips are the right tool for the icon buttons — they are slow,
   which is correct for a hint, and they never cover what you are pointing at
   on a phone because a phone has no hover. But they wrap badly when long, so
   the long ones get room. */
.ic-btn{position:relative}

/* Inside the Premiere panel there is nothing to download.
 *
 * The editor's deliverable is captions ON THEIR TIMELINE, which the panel's
 * own button does — so "produce and download a video" was an action with no
 * meaning here, sitting in the most prominent spot on the screen. The status
 * strip stays: it is how you know the transcript finished. */
.inPanel #exportBox,
.inPanel #exportExtra{display:none!important}
.inPanel .pinned{padding-bottom:4px}

/* ---- the docked panel is a FRAME, not a page ----------------------------
 *
 * A web page is as tall as its content and the window scrolls to see the
 * rest. A panel docked in Premiere is the opposite: the frame is whatever
 * the editor dragged it to — 1900x500 one minute, 380x900 the next — and
 * everything has to live inside it.
 *
 * Treating it as a page produced exactly what you would expect. At a wide,
 * short dock the page came out 1364px long inside a 500px frame: the player
 * on one side, the style cards on the other, and the transcript — the thing
 * an editor is here to read — 860px below the fold behind a scroll that
 * nobody could find. Reported as "I could not scroll inside the captions".
 *
 * So: two panes, each scrolling on its own, filling the frame exactly. The
 * picture stays on screen while you correct the words against it, which on a
 * 500px dock is the whole difference between usable and not. */
.inPanel,.inPanel body{height:100%;overflow:hidden}
.inPanel #editor{height:100%;max-width:none;padding:0;overflow:hidden}
/* The panel almost always opens straight into a project, but not always —
   a dead session drops it here. Body overflow is hidden, so this view has
   to carry its own scroll or it is a page you cannot reach the bottom of. */
.inPanel #library{height:100%;overflow-y:auto}
.inPanel .editor{display:flex;flex-direction:row;align-items:stretch;
  height:100%;min-height:0;gap:12px;padding:10px}
.inPanel .panel{order:0;flex:1 1 auto;min-width:0;min-height:0;
  overflow-y:auto;overflow-x:hidden;gap:12px;padding-inline-end:4px}
.inPanel .stageWrap{order:1;flex:0 0 clamp(200px,32%,420px);
  position:static;max-height:none;overflow:visible;padding:0;
  display:flex;flex-direction:column;align-items:center;min-height:0}

/* The picture takes the height it is given and the width follows from the
   ratio. Height is the scarce axis in a dock, and sizing off vh — which is
   the whole Premiere WINDOW, not this frame — is how a 460px player ended
   up inside a 380px panel. */
.inPanel .stageWrap .player,
.inPanel .stageWrap.sized .player{flex:1 1 auto;min-height:0;
  width:auto;height:auto;max-width:100%;
  aspect-ratio:var(--ar,.5625);margin-inline:auto}
.inPanel .player video,.inPanel .player img.poster{
  width:100%;height:100%;object-fit:contain}
.inPanel .transport{flex:0 0 auto;align-self:stretch;margin-top:8px}
/* The dock decides how big the picture is; there is nothing left for a grip
   to do, and it was eating 27px of a short frame to do it. */
.inPanel .sizeGrip{display:none}
.inPanel .pinned{position:sticky;bottom:0;margin-top:auto}

/* ONE scroller in the pane, not two.
 *
 * On the web the transcript is its own scroll box — min(56vh, 620px) — so a
 * long list does not push the export button off a tall page. In a dock that
 * rule turns vicious: 56% of a 520px frame is 291 pixels, and the transcript
 * of a four-minute film is 8176. So an editor scrolling the panel hit a
 * 291px window with its own invisible scrollbar, holding twenty-eight
 * screenfuls of captions, nested inside the pane they thought they were
 * scrolling. Reported, exactly, as "I had no way to scroll inside the
 * captions themselves".
 *
 * The pane already scrolls. The list should simply be as long as it is. */
.inPanel .rows{max-height:none;overflow:visible;margin:0;padding:0}

/* A narrow dock has no room for two columns, so it stacks — picture on top,
   everything else scrolling underneath it.
 *
 * And when it stacks, the SCROLLER has to move up with it.
 *
 * Side by side, the picture is its own column and the words are theirs, so
 * the pane that scrolls is the one your cursor is over. Stacked, the picture
 * sits ON TOP of the words — and it is not inside the scroller, and body,
 * #editor and .editor are all overflow:hidden above it. Probed at six points
 * down a 420x567 dock: the top three (y=60, 140, 220 — the video and the
 * transport under it) had NO scrollable ancestor at all. Not a slow scroll,
 * not a small one: the wheel did nothing. That is 45% of the panel, the part
 * your cursor naturally rests on, dead.
 *
 * It got worse the moment the captions were made, because that is when the
 * delivery bar appears and takes another 95px off the bottom — the same
 * complaint, "I cannot scroll, especially after adding the captions".
 *
 * So stacked, .editor itself scrolls and the picture sticks to the top of
 * it. Same two things on screen at once, no dead half. */
@media (max-width:700px){
  .inPanel .editor{flex-direction:column;gap:8px;padding:8px;
    overflow-y:auto;overflow-x:hidden}
  .inPanel .stageWrap{order:0;flex:0 0 auto;width:100%;max-height:46%;
    position:sticky;top:0;z-index:6;background:var(--ink);padding-bottom:6px}
  .inPanel .panel{order:1;flex:0 0 auto;width:100%;
    overflow:visible;min-height:0;padding-inline-end:0}
}

/* Two and a bit cards visible is enough to say there are more — but never
   at the cost of a card too small to read. Below about 150px the heaviest
   preset's outline closes the letters up, and a card you cannot read is not
   a choice, it is a coin toss. The floor wins; the percentage only decides
   how many fit once there is room. */
/* Wide enough that the caption on it is the caption, at its own size, with
   nothing cut off the ends. Below about 240px the frame's width does not
   give the letters enough pixels to be judged by — and the answer to that is
   a wider card, not a magnified fragment of the line. */
/* A card is the shape of the film now, so its WIDTH follows from a height
   that fits a panel rather than the other way round. 9:16 at 300px tall is
   169px wide, and six of those sit in a strip without scrolling on a wide
   dock. */
.inPanel .style{flex:0 0 auto;width:auto;min-width:0}
.inPanel .style .prev{height:300px;width:auto}
.inPanel .style .cap{padding:6px 8px}
.inPanel .style .cap b{font-size:12px}
.inPanel .style .cap span{font-size:10.5px}
.inPanel .styleHead h2{font-size:16px}
.inPanel .styleNav{width:34px;height:34px}
.inPanel .styleNav svg{width:16px;height:16px}

/* The Hebrew-keyboard switch that sits with a caption field.
 *
 * Only ever rendered inside the Premiere panel, where the host does not
 * produce Hebrew characters at all and the letter has to be made from the
 * physical key. See hebrewTyping. */
.heToggle{position:absolute;inset-inline-end:14px;top:14px;
  width:26px;height:26px;border-radius:7px;border:1px solid var(--line);
  background:var(--raised);color:var(--cream);font-size:13px;font-weight:700;
  cursor:pointer;line-height:1;display:grid;place-items:center}
.heToggle[aria-pressed="true"]{border-color:var(--hot);color:var(--hot)}
.heToggle:focus-visible{outline:2px solid var(--hot);outline-offset:2px}

/* "Fullscreen" inside the Premiere panel: the panel, filled.
 *
 * The Fullscreen API half-works in that frame — the element grows to the
 * iframe rather than the display, and document.fullscreenElement stays empty
 * so nothing can turn it off again. This is ours, so the way out always
 * works: the button, Escape, or the backdrop. */
.inPanel.panelFull .editor{overflow:hidden}
.inPanel.panelFull .panel{display:none}
.inPanel.panelFull .stageWrap{position:fixed;inset:0;z-index:200;
  flex:1 1 auto;max-height:none;width:auto;padding:10px;
  background:var(--ink);display:flex;flex-direction:column;align-items:center}
.inPanel.panelFull .stageWrap .player{flex:1 1 auto;min-height:0;
  width:auto;height:auto;max-width:100%;aspect-ratio:var(--ar,.5625)}
.inPanel.panelFull .transport{flex:0 0 auto;align-self:stretch}
.inPanel.panelFull .sizeGrip{display:none}

/* ---- the AI layer ----------------------------------------------------
 *
 * A correction preview, three hook options, and a publishing pack. All of
 * it renders inside the existing tool panels rather than in a place of its
 * own, because "correct the captions" belongs with the captions and a hook
 * belongs with the hooks — a separate AI tab would mean leaving the thing
 * you are working on to use a tool that acts on it. */
.aiPreview{margin-top:10px;border:1px solid var(--line);border-radius:12px;
  padding:11px;background:var(--surface)}
.aiHead{display:flex;align-items:baseline;gap:9px;margin-bottom:8px;
  flex-wrap:wrap}
.aiRows{display:flex;flex-direction:column;gap:5px;max-height:320px;
  overflow-y:auto}
.aiRow{display:flex;align-items:flex-start;gap:8px;padding:6px 7px;
  border-radius:9px;cursor:pointer;line-height:1.5}
.aiRow:hover{background:var(--raised)}
.aiRow.off{opacity:.62;cursor:default}
.aiRow input{margin-top:3px;flex:0 0 auto}
.aiTxt{display:flex;flex-direction:column;gap:1px;min-width:0;font-size:12.5px}
.aiTxt s{color:var(--faint);text-decoration-color:var(--faint)}
.aiTxt i{color:var(--mint);font-style:normal;font-weight:600}

.aiHooks{display:flex;flex-direction:column;gap:6px;margin-top:9px}
.aiHook{position:relative;text-align:start;padding:10px 11px;border-radius:11px;
  border:1px solid var(--line);background:var(--surface);color:inherit;
  cursor:pointer;font-size:13.5px;font-weight:600;line-height:1.45}
.aiHook:hover{border-color:var(--hot)}
.aiHook .rec{position:absolute;inset-inline-end:9px;top:-8px;font-size:10px;
  font-weight:700;padding:2px 7px;border-radius:999px;background:var(--hot);
  color:#fff}
/* Which of the three ways in this hook is. Quiet — the sentence is the thing
   being chosen; the label only stops the three reading as one idea retried. */
.aiHook .k{display:block;margin-top:5px;font-size:10.5px;font-weight:700;
  letter-spacing:.02em;color:var(--dim);text-transform:none}
.aiHook .k:empty{display:none}
/* The engine picker. Admin only, and it sits under the drop zone because
   that is where the decision is made — before the file goes up, not after. */
/* It was a dashed, full-width box right under a dashed, full-width drop
   zone, so it read as a second place to drop a video: a decision the
   product promises nobody has to make, drawn as loudly as the one action it
   does ask for. It is a quiet row now, labelled as the admin tool it is. */
.engine{display:flex;gap:6px 10px;align-items:center;flex-wrap:wrap;
  margin-top:10px;padding:0 6px}
.engine .lbl{font-size:12px;font-weight:600;color:var(--dim)}
.engine .lbl .who{font-weight:500;color:var(--faint);margin-inline-start:4px;
  padding:1px 7px;border-radius:99px;border:1px solid var(--line)}
.engine .seg{padding:3px;gap:3px;border-radius:10px;background:transparent}
.engine .seg button{flex:0 0 auto;padding:4px 11px;border-radius:8px;
  font-size:12px;white-space:nowrap}
.engine .note{margin:0;font-size:11.5px;color:var(--faint)}
/* What the comparison actually measured. */
.engineReport{display:flex;gap:12px;flex-wrap:wrap;margin-top:9px;
  padding:9px 11px;border:1px solid var(--line);border-radius:11px;
  background:var(--surface);font-size:12px}
.engineReport b{font-weight:700}
.engineReport span{color:var(--dim)}
/* The punctuation switch, above the rows it changes. */
.aiOpt{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin:8px 0 10px;
  padding:8px 10px;border:1px solid var(--line);border-radius:10px;
  background:var(--surface);cursor:pointer;font-size:12.5px}
.aiOpt>span:first-of-type{font-weight:600}
/* Why a row cannot be applied, ON the row. It was a title attribute, and
   nobody hovers anything in a Premiere dock. */
.aiRow .why{display:block;margin-top:3px;font-size:11px;font-style:normal;
  color:var(--dim);line-height:1.45}
.aiRow .why:empty{display:none}
/* The tappable caption palette. Panel only — on the web the native colour
   picker is better than any grid we could draw. */
.capChips{display:flex;flex-wrap:wrap;gap:5px;margin-top:7px}
.capChips .chip{width:22px;height:22px;border-radius:7px;border:1px solid var(--line2);
  cursor:pointer;padding:0}
.capChips .chip.on{outline:2px solid var(--hot);outline-offset:1px}
.aiPub textarea{width:100%;resize:vertical;font:inherit;line-height:1.6}
/* The promise the caption has to keep: what is still left to see. */
.aiWhy{margin:0 0 10px;padding:8px 10px;border-radius:10px;
  background:var(--surface);border:1px solid var(--line);line-height:1.55}
.aiWhy b{color:var(--hot)}

/* Not open to everyone yet. Says what is coming rather than showing a
   disabled button, which reads as broken. */
/* Previous generations. Folded, because the current one is the answer and
   the older ones are a safety net — but reachable, which they were not. */
.aiHistory{margin:10px 0 2px;border-top:1px solid var(--line);padding-top:8px}
.aiHistory>summary{cursor:pointer;color:var(--faint);font-size:12.5px;
  list-style:none;padding:3px 0}
.aiHistory>summary::-webkit-details-marker{display:none}
.aiHistory>summary:hover{color:var(--ink)}
.aiHistoryBody{display:flex;flex-direction:column;gap:4px;margin-top:6px;
  max-height:260px;overflow-y:auto;color:var(--faint);font-size:12.5px}
.aiHistoryRow{display:flex;flex-direction:column;gap:2px;align-items:flex-start;
  text-align:start;width:100%;background:none;border:0;cursor:pointer;
  padding:7px 8px;border-radius:9px;color:inherit;font:inherit}
.aiHistoryRow:hover{background:var(--raised)}
.aiHistoryRow .w{font-size:11.5px;color:var(--faint)}
.aiHistoryRow .t{color:var(--ink);font-size:12.5px;line-height:1.5;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;
  line-clamp:2;overflow:hidden}

.aiSoon{display:flex;flex-direction:column;gap:3px;padding:12px 13px;
  border:1px dashed var(--line2);border-radius:12px;background:var(--surface)}
.aiSoon b{color:var(--hot);font-size:13px}
.aiSoon span{color:var(--dim);font-size:12px;line-height:1.55}

/* The picture decides the card's size: height is what a strip can afford,
   width is whatever the film's shape makes of it. */
.style .prev{height:330px;width:auto}
@media (max-width:700px){ .style .prev{height:250px} }

/* The on-screen Hebrew keyboard. Only ever rendered inside the Premiere
   panel, and only when asked for — see hebrewPadFor. */
.hePad{margin-top:8px;display:flex;flex-direction:column;gap:4px}
.heRow{display:flex;gap:4px;justify-content:center}
.heRow button{flex:1 1 0;min-width:0;height:30px;border-radius:7px;
  border:1px solid var(--line);background:var(--raised);color:var(--cream);
  font-size:15px;font-weight:600;cursor:pointer;padding:0}
.heRow button:hover{border-color:var(--hot);color:var(--hot)}
.heRow button.wide{flex:2 1 0;font-size:12px}

/* ================================================================ reveal
   The finished clip, the first time. See playReveal() in studio.js for the
   rules it keeps. Every beat is a static state on [data-beat]; the motion
   between them is the only thing .frozen takes away. */
body.revealOpen{overflow:hidden}
#reveal{position:fixed;inset:0;z-index:80;display:grid;place-items:center;
  padding:calc(24px + env(safe-area-inset-top)) 24px calc(24px + env(safe-area-inset-bottom));
  overflow:hidden;color:var(--cream);outline:none;
  opacity:0;transition:opacity .26s var(--ease-out)}
#reveal.in{opacity:1}
/* The light is the clip itself: its own frame, out of focus, behind it. It
   is a different colour for every video and never a glow. */
.rvLight{position:absolute;inset:-12%;background:center/cover no-repeat;
  filter:blur(56px) saturate(1.6) brightness(.74);transform:scale(1.08)}
.rvScrim{position:absolute;inset:0;
  background:radial-gradient(110% 85% at 50% 48%,rgba(13,11,10,.34),rgba(13,11,10,.9))}

.rvGrid{position:relative;width:min(900px,100%);display:grid;
  grid-template-columns:minmax(0,1fr) auto;gap:clamp(28px,4.5vw,64px);align-items:center}
.rvText{display:flex;flex-direction:column;gap:22px;min-width:0}
/* A beat every 400ms, each entering in 380ms: one lands just before the
   next begins, so the rhythm is felt rather than counted. The 4px blur is
   what keeps a line of text from reading as two states crossfading. */
.rvBeat{opacity:0;transform:translateY(10px);filter:blur(4px);
  transition:opacity .38s var(--ease-out),transform .38s var(--ease-out),filter .38s var(--ease-out)}
.rvK{display:block;font-size:15px;font-weight:600;color:var(--dim);margin-bottom:8px}
.rvLine{margin:0;font-family:"Secular One","Rubik",sans-serif;
  font-size:clamp(30px,3.5vw,50px);line-height:1.16;letter-spacing:-.01em;
  text-wrap:balance;max-width:15ch}
.rvLine:empty{display:none}
.rvFacts{display:inline-flex;align-items:center;gap:12px;font-size:19px;font-weight:600;
  font-variant-numeric:tabular-nums}
.rvFacts i{width:4px;height:4px;border-radius:50%;background:var(--faint)}
.rvCut{display:block;margin-top:4px;font-size:14px;color:var(--dim)}
.rvStyle{font-size:19px;color:var(--dim)}
.rvStyle b{color:var(--cream);font-weight:700}

.rvSlot{display:grid;place-items:center}
.rvCard{position:relative;height:min(68vh,620px);aspect-ratio:9/16;
  border-radius:22px;overflow:hidden;background:#100D0B;
  border:1px solid rgba(247,239,230,.16);
  box-shadow:0 30px 80px rgba(0,0,0,.62),0 8px 22px rgba(0,0,0,.4);
  opacity:0;transform:translateY(18px) scale(.95);
  transition:opacity .6s var(--ease-out),transform .6s var(--ease-out)}
.rvCard video{width:100%;height:100%;object-fit:cover;display:block}
.rvSound{position:absolute;top:10px;inset-inline-start:10px;width:40px;height:40px;
  border-radius:12px;display:grid;place-items:center;color:var(--cream);
  background:rgba(13,11,10,.72);border:1px solid var(--line2)}
.rvSound svg{width:20px;height:20px;fill:none;stroke:currentColor;stroke-width:1.9;
  stroke-linecap:round;stroke-linejoin:round}
.rvSound .on,.rvSound.on .off{display:none}
.rvSound.on .on{display:inline}
.rvSound:hover{border-color:var(--hot)}

.rvActions{display:flex;flex-direction:column;gap:14px;opacity:0;transform:translateY(8px);
  transition:opacity .3s var(--ease-out),transform .3s var(--ease-out);
  pointer-events:none}
.rvBtns{display:flex;flex-wrap:wrap;gap:10px}
.rvDl{margin:0;display:flex;align-items:center;gap:8px;font-size:14px;color:#A6EBCC}
.rvDl svg{width:18px;height:18px;fill:none;stroke:var(--mint);stroke-width:2.4;
  stroke-linecap:round;stroke-linejoin:round}
.rvLink{padding:0;color:var(--dim);font-size:13px;text-decoration:underline;
  text-underline-offset:3px}
.rvLink:hover{color:var(--cream)}
.rvSkip{position:absolute;bottom:calc(18px + env(safe-area-inset-bottom));inset-inline:0;
  margin:0;text-align:center;font-size:12px;color:var(--faint);
  transition:opacity .2s}

/* the beats */
#reveal:is([data-beat="1"],[data-beat="2"],[data-beat="3"],[data-beat="4"]) .rv1,
#reveal:is([data-beat="2"],[data-beat="3"],[data-beat="4"]) .rv2,
#reveal:is([data-beat="3"],[data-beat="4"]) .rv3{opacity:1;transform:none;filter:none}
/* The card is in from the start, with the veil, not on the last beat. */
#reveal.in .rvCard,#reveal.frozen .rvCard{opacity:1;transform:none}
#reveal.ended .rvActions{opacity:1;transform:none;pointer-events:auto;transition-delay:.12s}
#reveal.ended .rvSkip{opacity:0}

/* A skip lands in the end state within 150ms, whatever was mid-flight. */
#reveal.skipped *{transition-duration:.15s!important;transition-delay:0s!important}
#reveal.frozen,#reveal.frozen *{transition:none!important}
#reveal.frozen{opacity:1}
#reveal.calm .rvBeat,#reveal.calm .rvCard,#reveal.calm .rvActions{transform:none}
#reveal.calm,#reveal.calm *{transition-property:opacity!important;transition-duration:.18s!important;
  transition-delay:0s!important}

/* Leaving. Faster than arriving, and to where the clip will live. */
/* Leaving is about 20% faster than arriving (card 600 -> 480ms, veil
   260 -> 210ms): the system is responding now, not presenting. The card
   travels across the screen to the player, so that move is ease-in-out. The
   veil holds until the card is nearly home, then goes. */
#reveal.leaving{opacity:0;pointer-events:none;transition:opacity .21s ease .27s}
#reveal.leaving .rvText,#reveal.leaving .rvSkip{opacity:0;transition:opacity .15s ease}
#reveal.leaving .rvCard{transform:var(--home,scale(.96));
  transition:transform .48s var(--ease-in-out)}

/* Every export after the first: the result lands in the player. */
.player.landing{animation:land .25s var(--ease-out)}
@keyframes land{from{opacity:.5;transform:scale(.96)}}

@media (max-width:760px){
  #reveal{place-items:start center;overflow-y:auto}
  .rvGrid{grid-template-columns:1fr;gap:18px;justify-items:center;text-align:center;
    padding-bottom:40px}
  /* The text column dissolves into the grid, so the order can be: what
     it is, the clip, then what to do with it. */
  .rvText{display:contents}
  .rvBeat{order:0}
  .rvSlot{order:1}
  .rvLine{font-size:clamp(22px,6.4vw,30px);max-width:22ch}
  .rvFacts,.rvStyle{font-size:15.5px}
  .rvCard{height:min(56dvh,520px)}
  .rvActions{order:2;align-items:center;width:100%}
  .rvBtns{justify-content:center}
  .rvSkip{position:static;margin-top:6px}
}
@media (prefers-reduced-motion:reduce){
  .player.landing{animation:none}
}

/* ---------- tooltips ----------
   Drawn by the app: in Hebrew direction, in the palette, and able to hold a
   sentence. Neutral elevation, no glow. 125ms, from 97%, toward the control
   it explains; instant while one is already open. */
#tip{position:fixed;z-index:90;max-width:min(300px,calc(100vw - 16px));
  padding:8px 11px;border-radius:10px;background:#241D18;color:var(--cream);
  border:1px solid var(--line2);box-shadow:0 10px 26px rgba(0,0,0,.5);
  font-size:12.5px;line-height:1.55;font-weight:500;text-align:start;
  direction:rtl;pointer-events:auto;opacity:0;transform:scale(.97);
  transform-origin:50% 100%;visibility:hidden;
  transition:opacity .125s var(--ease-out),transform .125s var(--ease-out),visibility 0s .125s}
#tip[data-side="bottom"]{transform-origin:50% 0}
#tip.on{opacity:1;transform:none;visibility:visible;transition-delay:0s}
#tip.instant{transition-duration:0s}
@media (hover:none){#tip{display:none}}
@media (prefers-reduced-motion:reduce){#tip{transform:none}}

/* ---------- the ready page: what a premium account lands on ----------
   Same veil, same card, plus the two things the pack produced: the line
   saying the transcript was corrected, and the texts for every network. */
#reveal.isLive .rvCard video{object-fit:contain;background:#100D0B}
.rvLiveCap{position:absolute;inset:0;overflow:hidden;pointer-events:none;display:none}
#reveal.isLive .rvLiveCap{display:block}
.rvZoom{position:absolute;top:0;left:0;transform-origin:top left;direction:ltr}
.rvFix{display:flex;align-items:center;gap:10px;flex-wrap:wrap;font-size:14px;color:var(--dim)}
.rvFixText{color:var(--cream)}
.rvPack{position:relative;width:min(900px,100%);margin:18px auto 0;
  padding:16px 18px 18px;border-radius:18px;background:rgba(23,19,15,.92);
  border:1px solid var(--line2);box-shadow:0 18px 44px rgba(0,0,0,.45);
  opacity:0;transform:translateY(10px);
  transition:opacity .3s var(--ease-out),transform .3s var(--ease-out)}
#reveal.ended .rvPack{opacity:1;transform:none;transition-delay:.18s}
.rvPackHead{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;margin-bottom:10px}
.rvPackHead b{font-size:15px}
.rvTitleLine{margin:0 0 10px;font-family:"Secular One","Rubik",sans-serif;font-size:19px}
.rvTabs{margin-bottom:10px;flex-wrap:wrap}
.rvTabs button{flex:0 0 auto;padding:6px 12px;font-size:12.5px;white-space:nowrap}
.rvPackText{width:100%;resize:vertical;font:inherit;font-size:13.5px;line-height:1.7;
  color:var(--cream);background:var(--ink);border:1px solid var(--line);
  border-radius:12px;padding:11px 13px}
.rvPackText:focus-visible{outline:2px solid var(--hot);outline-offset:1px}
.rvPackFoot{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-top:10px}
.rvWhy{flex:1;min-width:180px;margin:0}
/* With the pack on it the page is taller than the screen: it scrolls, and
   the veil stays put behind it. The card gives up some height so the texts
   are on screen too — they are half of what this page is for. */
#reveal:has(.rvPack){place-items:start center;overflow-y:auto}
#reveal:has(.rvPack) .rvCard{height:min(52vh,470px)}
#reveal:has(.rvPack) .rvGrid{margin-top:auto}
#reveal:has(.rvPack) .rvSkip{position:static;margin-top:14px}
@media (max-width:760px){
  .rvPack{margin-top:14px;padding:14px}
  .rvTabs button{padding:6px 10px;font-size:12px}
}

/* The starred style: the one every new video opens in. */
.styleStar{display:inline-flex;align-items:center;gap:7px;padding:7px 12px;
  border-radius:99px;font-size:12.5px;font-weight:600;color:var(--dim);
  background:var(--surface);border:1px solid var(--line);
  transition:color .15s ease,border-color .15s ease,background-color .15s ease}
.styleStar svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:1.9;
  stroke-linejoin:round}
.styleStar.on{color:var(--sun);border-color:rgba(255,201,60,.45);background:rgba(255,201,60,.1)}
.styleStar.on svg{fill:var(--sun);stroke:var(--sun)}
@media (hover:hover) and (pointer:fine){.styleStar:hover{color:var(--cream);border-color:var(--line2)}}
.style .starTag{position:absolute;top:8px;inset-inline-start:8px;z-index:3;
  width:22px;height:22px;border-radius:50%;display:grid;place-items:center;
  background:var(--sun);color:var(--ink)}
.style .starTag svg{width:13px;height:13px;fill:currentColor;stroke:currentColor;stroke-width:1.4}
.style.on .starTag{inset-inline-start:36px}
.styleFoot{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-top:10px}
.styleFoot .note{flex:1;min-width:200px;margin:0}
/* The door to the Lazy Editor. Same weight as the other nav links; on a
   narrow screen the label goes and the icon stays, like the admin link. */
#navLazy{display:inline-flex;align-items:center;gap:6px}
#navLazy svg{width:17px;height:17px;flex:0 0 auto}
@media(max-width:700px){
  #navLazy span{position:absolute;width:1px;height:1px;overflow:hidden;
    clip-path:inset(50%);white-space:nowrap}
  #navLazy{padding:7px 9px}
}
/* A reframed clip: the crop box carries the output's shape, and the picture
   inside it is blown up and offset to show exactly what will be exported.
   With no reframing the box is not in the layout at all. */
.cropBox{display:contents}
/* The player is width:auto and sizes itself from the picture inside it, so a
   crop box asking for 100% of it collapsed both to nothing. Cropped, the
   player takes the column's width and fitCrop() gives the box exact pixels —
   which also keeps the shape right when a max-height clamps the player. */
.player.cropped{overflow:hidden;width:100%}
.player.cropped .cropBox{display:block;position:relative;margin-inline:auto;
  overflow:hidden;background:#000}

/* How this transcript was made. One quiet line; the numbers behind it live
   in its title, where they cost nothing to ignore. It used to be five
   labelled figures across the top of the editor — all true, none of them
   why anybody opened the page. */
#engineReport .engineLine{color:var(--dim);font-size:12.5px;
  border:1px solid var(--line);border-radius:99px;padding:3px 11px;
  display:inline-block;cursor:default}
