/* roast-battle.css, the stylesheet for laugh.so/roast-battle.
   Tokens mirror evaluator.css and benchmark-board.css so the pages read as one
   site. Two departures, both deliberate: the stage is set on near-black, because
   a comedy-club scene surrounded by cream reads as a screenshot rather than a
   stage; and the shell is wider than the reading pages, because the video is the
   product and this page has almost no prose to keep narrow.
   Light-only. Dark mode was unshipped 2026-08-12; do not re-add a
   prefers-color-scheme dark block, [data-theme] variants, or a theme toggle
   without a specific request. */
:root {
  --paper:#fffcf5;
  --card:#fffefb;
  --card-2:#fbf8f2;
  --ink:#201c1a;
  --ink-2:#4a4340;
  --muted:#706863;
  /* Darker than the #938a84 the other pages use, for the small print on paper
     (the footer). The composer's placeholder no longer uses it: that sits on
     the dark stage now and carries its own value where it is set. */
  --faint:#776e69;
  --line:#e2ddd6;
  --accent:#a92665;
  /* The site's subscribe-button gradient, same stops as benchmark-board.css. */
  --grad:linear-gradient(135deg,#f6b97a 0%,#f4a8b8 50%,#e86aa6 100%);
  /* Yellow into pink, from colours the page already owns: the loadbar's gold
     into --grad's deep stop. Asked for by name (Killian, 2026-08-18) for the
     word "Interactive". The how-it-works numbers use --grad instead, to match
     the subscribe button exactly. */
  --grad-hot:linear-gradient(135deg,#ffc857 0%,#e86aa6 100%);
  --prov:#8a6d1d;
  --prov-bg:#fdf7e5;
  --prov-line:#e6d8ad;
  --stage:#191413;
  /* The one black on this page: the edge around the video-plus-composer rig and
     the fill of the button that spends money. Same value in both, so the box and
     its control read as the same object. */
  --edge:#161211;
  --serif:"Iowan Old Style","Palatino Linotype",Palatino,"Source Serif 4",Charter,Georgia,ui-serif,serif;
  --sans:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  --shadow-lift:0 8px 30px rgba(61,45,35,.07);

  /* Same four-step spacing scale as /evaluator. Every vertical gap on this page
     is one of these; nothing else is allowed. */
  --tight:8px;
  --close:16px;
  --group:40px;
  --page:64px;
}

* { box-sizing:border-box; }
body {
  margin:0; background:var(--paper); color:var(--ink);
  font-family:var(--sans); font-size:15px; line-height:1.55;
  -webkit-font-smoothing:antialiased;
}
a { color:inherit; }
.shell { width:min(1040px, calc(100% - 36px)); margin:0 auto; }
.sr-only {
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0;
}

/* ---------------------------------------------------------- masthead ---- */
.masthead { border-bottom:1px solid var(--line); background:var(--paper); position:sticky; top:0; z-index:40; }
.masthead .shell { width:min(1340px, calc(100% - 36px)); min-height:58px; display:flex; align-items:center; justify-content:space-between; gap:20px; }
.wordmark { display:inline-flex; align-items:center; gap:9px; font-family:var(--serif); font-size:1.16rem; letter-spacing:-.01em; text-decoration:none; color:var(--ink); }
.wordmark .mark { width:20px; height:20px; border-radius:5px; display:block; }

/* Site nav, lifted from benchmark-board.css so every indexed page carries the
   same one. */
.mast-nav { display:flex; align-items:center; gap:2px; flex-wrap:nowrap; }
.mast-nav a {
  padding:6px 11px; border-radius:7px; text-decoration:none;
  color:var(--muted); font-size:.855rem; font-weight:500;
}
.mast-nav a:hover { color:var(--ink); background:var(--card-2); }
.mast-nav a.current { color:var(--ink); background:var(--card-2); font-weight:600; }
.mast-nav .mast-cta {
  padding:5px 12px; border-radius:999px; background:var(--grad);
  color:#fff; font-weight:650; border:0;
}
.mast-nav a.mast-cta:hover { background:var(--grad); color:#fff; opacity:.92; }

/* Tight to the masthead so the title and video arrive in the first viewport. */
main { padding:var(--group) 0 var(--page); }

h1.page-title {
  margin:0 0 var(--tight); font-family:var(--serif); font-size:2.25rem;
  line-height:1.08; font-weight:400; letter-spacing:-.025em;
}
/* One word of the title in the yellow-pink gradient, clipped to the type. The
   gradient ends on the pink before the glyphs get pale enough to sink into the
   cream paper. */
.page-title .grad-word {
  background:var(--grad-hot);
  -webkit-background-clip:text; background-clip:text;
  color:transparent;
}

/* The one sentence of explanation the page carries, directly under the title so
   it is read before the box. In the site's sans, not the serif: the serif is
   what made the composer's prose read as an article instead of an interface. */
.page-sub { margin:0 0 var(--close); color:var(--ink-2); max-width:68ch; }

/* --------------------------------------------------------------- rig ---- */
/* Video and composer welded into one bordered object. The corners are rounded
   on the two children rather than clipped by an overflow:hidden here, at 16px
   minus the 2px edge so the curves sit concentric. Both halves are dark now,
   so the rig's own ground matches the edge: a cream sliver in the corner
   radii would read as a broken seam. */
.rig {
  border:2px solid var(--edge); border-radius:16px;
  background:var(--edge); box-shadow:var(--shadow-lift);
  margin:0 auto;
}
.frame {
  position:relative; background:var(--stage); overflow:hidden;
  border-radius:14px 14px 0 0;
  /* Reserve the 16:9 box before the video's metadata arrives, so the composer
     below it does not jump down the page on load. */
  aspect-ratio:16 / 9;
}
.hero { display:block; width:100%; height:100%; object-fit:contain; background:var(--stage); }
/* The `hidden` attribute is only `display:none` from the user-agent sheet, so
   the explicit display above outranks it. Without this the page kept showing a
   dead player, poster visible and nothing behind it, whenever a cut was missing. */
.hero[hidden] { display:none; }
.frame-pending {
  position:absolute; inset:auto 0 0 0; margin:0; padding:10px 14px;
  background:rgba(25,20,19,.86); color:#f3ece6; font-size:.84rem;
}
/* A freshly stitched cut replacing the one that was playing. Brief, and only
   the once, because an animation that fired on every poll would be a strobe. The ring
   is inset because the frame is clipped by its own overflow now. */
.frame.swapped { animation:flash .5s ease-out; }
@keyframes flash { from { box-shadow:inset 0 0 0 3px var(--accent); } to { box-shadow:none; } }

/* ---------------------------------------------------------- composer ---- */
.ask { margin-bottom:var(--group); }
/* On the same near-black as the video above it, so the rig is one dark object:
   a screen with its controls under it. The field itself sits a shade lighter
   than that stage: flush with it, the prose read as caption copy rather than
   somewhere to type (Killian, 2026-08-18). No border and no heading; the rig
   carries the edge, and the light button is the one thing that pops. */
.composer { border-radius:0 0 14px 14px; padding:12px; background:var(--stage); display:grid; gap:10px; }
.composer textarea {
  width:100%; border:none; background:rgba(255,255,255,.07); color:#f6f1e8;
  font-family:var(--serif); font-size:1.06rem; line-height:1.5;
  border-radius:10px; padding:10px 14px; resize:vertical; min-height:62px;
  outline:none;
}
/* The placeholder is the instruction people copy, so it has to clear the 4.5:1
   AA floor. #9a938c measures about 5:1 against the lightened field. */
.composer textarea::placeholder { color:#9a938c; }
.composer-foot { display:flex; align-items:center; justify-content:space-between; gap:var(--close); }

/* The cast, as four bare marks. The words moved to the how-it-works steps; the
   full claim survives on each chip's tooltip and image alt. The discs match
   the composer behind them with a white rim (Killian, 2026-08-18): the light
   discs read as buttons on the dark stage. The black OpenAI and xAI marks are
   served from /logos/white so they survive without the light ground. */
.cast { display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
.cast .chip {
  display:inline-flex; align-items:center; justify-content:center;
  width:28px; height:28px; flex:none;
  border:1px solid #fff; background:var(--stage);
  border-radius:999px;
}
.cast img { width:15px; height:15px; display:block; flex:none; }

/* Light on the dark composer. As the one pale block on the stage it reads as
   the control that does the thing, which is what the solid-black version was
   for back when the composer was cream. */
button.act {
  border:none; border-radius:10px; padding:11px 22px; font:inherit; font-weight:650;
  font-size:.95rem; cursor:pointer; background:var(--paper); color:var(--ink); flex:none;
}
button.act:hover { background:#fff; }
button.act:disabled { opacity:.45; cursor:not-allowed; }
button:focus-visible, a:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }
/* Focus stays on the field, quietly: a shade brighter and a thin neutral line.
   The earlier pink halo around the whole rig read as a decorative border, not
   as focus, and was cut on request (Killian, 2026-08-18). */
.composer textarea:focus-visible {
  background:rgba(255,255,255,.11);
  outline:1px solid rgba(255,255,255,.35); outline-offset:0;
}

/* One line under the rig for status and failure both. The .err class dresses it
   as the refusal box when a submission fails; bare, it is the quiet status line
   and holds its height so the page does not jump when a message arrives. */
.quote { margin:var(--close) 0 0; font-size:.85rem; color:var(--muted); min-height:1.3em; }
.quote.err {
  padding:11px 14px; border-radius:10px; font-size:.88rem;
  background:var(--prov-bg); border:1px solid var(--prov-line); color:var(--prov);
}

/* ------------------------------------------------- the overlay banner ---- */
/* One slot along the top of the video, shared by the loader and the history
   strip; working() in the script guarantees only one of them is showing. It
   overlays rather than stacking under the frame so the announcement sits on
   the thing it is announcing. The frame's overflow:hidden clips its corners. */
.banner {
  position:absolute; top:0; left:0; right:0; z-index:6;
  background:rgba(20,15,13,.9);
  -webkit-backdrop-filter:blur(6px); backdrop-filter:blur(6px);
}
.banner[hidden] { display:none; }

/* ----------------------------------------------------------- working ---- */
.working { text-align:center; padding:10px 16px 11px; }
.loadbar { max-width:440px; height:2px; margin:0 auto; border-radius:2px; background:rgba(255,255,255,.22); overflow:hidden; }
/* Width is set from the real clip count, so the bar means something. It used to
   animate to 93% over 25 seconds regardless of what was happening, which on a
   three-minute render was a bar that finished long before the job did. Gold
   rather than the site accent: on the dark banner the accent pink measures
   under 3:1, and the gold is already the page's "new cut" colour. */
.loadbar span {
  display:block; height:100%; width:0; border-radius:2px; background:#ffc857;
  transition:width .5s ease-out;
}
/* The writing stage has nothing countable yet, so it sweeps rather than lying
   about a position. */
.loadbar.indeterminate span { width:34%; animation:sweep 1.4s ease-in-out infinite; }
@keyframes sweep {
  0%   { transform:translateX(-110%); }
  100% { transform:translateX(320%); }
}
.working p { margin:7px 0 0; color:#d9d2cb; font-size:.8rem; }

/* ------------------------------------------------------- how it works ---- */
/* Three headlines stacked in one column, each behind a gradient number. The
   per-step explanations and the three-across grid were cut on request
   (Killian, 2026-08-18); the who-writes-whom claim lives on the cast chips'
   tooltips and alt text now. */
.how { margin-top:var(--group); }
.how-title {
  margin:0 0 var(--close); font-family:var(--serif); font-size:1.35rem;
  line-height:1.15; font-weight:500; letter-spacing:-.015em;
}
.steps {
  list-style:none; counter-reset:step; margin:0; padding:0;
  display:grid; gap:var(--close);
}
.steps li {
  counter-increment:step; position:relative; padding-left:34px;
  font-size:.95rem; color:var(--ink);
}
.steps li::before {
  content:counter(step); position:absolute; left:0; top:0;
  width:22px; height:22px; border-radius:999px;
  display:grid; place-items:center;
  /* The subscribe button's own dress, asked for by name (Killian, 2026-08-18):
     the site gradient with white numerals, same as the mast-cta. The numbers
     stay bold; the items around them do not. */
  background:var(--grad); color:#fff; font-size:.72rem; font-weight:650; line-height:1;
}

.site-foot { margin-top:var(--page); padding-top:var(--close); border-top:1px solid var(--line); color:var(--faint); font-size:.82rem; }
.site-foot a { color:inherit; }

/* Same two steps benchmark-board.css uses: the off-site links go first, then
   everything except where you are and the one call to action. */
@media (max-width:980px) {
  .mast-nav a[href^="https://midcomic.com"],
  .mast-nav a[href^="https://pair.laugh.so"] { display:none; }
}
@media (max-width:720px) {
  .mast-nav a:not(.current):not(.mast-cta) { display:none; }
  .mast-nav .mast-cta { padding:5px 12px; font-size:.82rem; }
}

/* Keep the whole rig on screen without scrolling, by narrowing it rather than by
   capping the video's height. Capping the height makes the video's width shrink
   to hold 16:9, so it pulls away from the border and leaves a strip of card down
   one side; capping the rig narrows both halves together and they stay flush.
   The 375px is the room the masthead, page title, and composer need.

   Only above 700px of viewport height. Below that the sum does not fit whatever
   we do, because the composer alone is about 180px, and applying the cap anyway
   shrinks the rig to a stamp floating in a wide page. A phone in landscape is
   better served by a full-width video and a scroll. */
@media (min-height:700px) {
  .rig { max-width:calc((100vh - 375px) * 16 / 9); }
}

@media (max-width:640px) {
  h1.page-title { font-size:1.85rem; }
  /* The placeholder is the instruction, and on a phone it wraps to three lines;
     the desktop 62px shows two and clips the example mid-sentence. Three line
     boxes plus the field's 20px of vertical padding, in lh so a type-size
     change cannot quietly reopen the gap. Three holds down to 320px wide;
     the responsive spec pours the placeholder in as a value and measures. */
  .composer textarea { min-height:calc(3lh + 20px); }
  .composer-foot { flex-direction:column; align-items:stretch; gap:var(--close); }
  button.act { width:100%; }
  .cast { justify-content:center; }
  /* The page is three things on a phone, so it does not need desktop air
     between them. */
  main { padding:var(--close) 0 var(--group); }
  .ask { margin-bottom:var(--close); }
  .site-foot { margin-top:var(--group); }
}

/* The narrowest screens still in use. The masthead is the only row on the page
   with three things competing for width, and at 320px both the wordmark and the
   current nav item wrap to two lines unless they are held on one. */
@media (max-width:430px) {
  .shell, .masthead .shell { width:calc(100% - 24px); }
  .masthead .shell { gap:8px; }
  .wordmark { font-size:1.02rem; gap:7px; }
  .mast-nav a { padding:6px 8px; font-size:.8rem; }
  .mast-nav .mast-cta { padding:5px 10px; font-size:.8rem; }
  .composer textarea { padding-left:12px; padding-right:12px; }
}

/* At 320px, the wordmark and call to action need the full row. The current-page
   link is redundant here, so hide it instead of allowing horizontal scroll. */
@media (max-width:400px) {
  .masthead .shell { gap:6px; }
  .wordmark { font-size:.95rem; }
  .mast-nav a.current { display:none; }
  .mast-nav .mast-cta { padding:4px 9px; font-size:.74rem; }
}
.wordmark, .mast-nav a { white-space:nowrap; }

@media (prefers-reduced-motion:reduce) {
  .loadbar.indeterminate span { animation:none; width:40%; }
  .loadbar span { transition:none; }
  .frame.swapped { animation:none; }
}

/* --- generated-video history ---------------------------------------------- */
/*
   Two complaints, one strip. A finished cut used to arrive with no announcement,
   so it read as though the prompt had been ignored; and once a visitor had made
   a cut there was no way back to what came before, the opening film included.
   This names what is playing and lets you walk the whole list. It lives in the
   overlay banner slot on the video, which the loader borrows while a render
   runs.
*/
.history {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding: .45rem .55rem;
}
.history[hidden] { display: none; }

.hist-arrow {
  flex: none;
  width: 2rem;
  height: 2rem;
  border: 1px solid #3a3a3a;
  border-radius: 999px;
  background: #1b1b1b;
  color: #f6f1e8;
  font: 400 1.1rem/1 ui-sans-serif, system-ui, sans-serif;
  cursor: pointer;
}
.hist-arrow:hover:not(:disabled) { background: #262626; }
.hist-arrow:disabled { opacity: .3; cursor: default; }

.hist-body { flex: 1 1 auto; display: grid; gap: .12rem; min-width: 0; text-align: center; }
.hist-head { display: flex; align-items: center; justify-content: center; gap: .4rem; }

.hist-count {
  color: #8d8681;
  font: 500 .7rem/1 ui-sans-serif, system-ui, sans-serif;
  letter-spacing: .04em;
  text-transform: uppercase;
}

.hist-prompt {
  color: #f6f1e8;
  font: 600 .85rem/1.35 ui-sans-serif, system-ui, sans-serif;
  overflow-wrap: anywhere;
  /* The banner sits on the video, so a 400-character prompt must not grow it
     into a curtain. Two lines, then an ellipsis. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.hist-writers { color: #9a938c; font: 400 .74rem/1.35 ui-sans-serif, system-ui, sans-serif; }

/* Two slow pulses on the frame when a cut arrives, so the eye is pulled to the
   video and not only to the text under it. It settles rather than looping: a
   permanent animation stops meaning "new" within about ten seconds. */
.frame.fresh-ring { animation: freshRing 1.15s ease-out 2; }
@keyframes freshRing {
  0%   { box-shadow: 0 0 0 0 rgba(255, 200, 87, .85); }
  100% { box-shadow: 0 0 0 14px rgba(255, 200, 87, 0); }
}

@media (prefers-reduced-motion: reduce) {
  .frame.fresh-ring { animation: none; }
}

/* --- the script, revealed while it renders --------------------------------- */
/*
   A minute and a half of bar is a long time to look at nothing. The lines are
   written before the video is drawn, so they exist well before the cut does, and
   showing them turns dead waiting into reading the jokes. It is also the most
   descriptive progress there is: you can see exactly how far the scene has got.
*/
.script {
  list-style: none;
  margin: var(--close) 0 0;
  padding: 0;
  display: grid;
  gap: .5rem;
  text-align: left;
}
.script[hidden] { display: none; }

.script li {
  display: grid;
  gap: .1rem;
  padding: .5rem .65rem;
  border-left: 2px solid #d8d2cb;
  animation: scriptIn .3s ease-out;
}

.script .who {
  font: 700 .68rem/1.2 ui-sans-serif, system-ui, sans-serif;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: #8a827a;
}
.script .said { font: 400 .88rem/1.4 ui-sans-serif, system-ui, sans-serif; color: #2b2621; }
.script .by { font: 400 .7rem/1.3 ui-sans-serif, system-ui, sans-serif; color: #a09890; }

@keyframes scriptIn { from { opacity: 0; transform: translateY(3px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .script li { animation: none; } }
