diff --git a/src/room/InCallView.module.css b/src/room/InCallView.module.css
index 310cf47..04228aa 100644
--- a/src/room/InCallView.module.css
+++ b/src/room/InCallView.module.css
@@ -20,9 +20,10 @@ limitations under the License.
   flex-direction: column;
   overflow: hidden;
   min-height: 100%;
-  position: fixed;
   height: 100%;
   width: 100%;
+  --footerPadding: 8px;
+  --footerHeight: calc(50px + 2 * var(--footerPadding));
 }
 
 .centerMessage {
@@ -39,12 +40,27 @@ limitations under the License.
 }
 
 .footer {
-  position: relative;
-  flex-shrink: 0;
+  position: absolute;
+  left: 0;
+  bottom: 0;
+  width: 100%;
   display: flex;
   justify-content: center;
   align-items: center;
-  height: calc(50px + 2 * 8px);
+  padding: var(--footerPadding) 0;
+  /* TODO: Un-hardcode these colors */
+  background: linear-gradient(
+    360deg,
+    #15191e 0%,
+    rgba(21, 25, 30, 0.9) 37%,
+    rgba(21, 25, 30, 0.8) 49.68%,
+    rgba(21, 25, 30, 0.7) 56.68%,
+    rgba(21, 25, 30, 0.427397) 72.92%,
+    rgba(21, 25, 30, 0.257534) 81.06%,
+    rgba(21, 25, 30, 0.136986) 87.29%,
+    rgba(21, 25, 30, 0.0658079) 92.4%,
+    rgba(21, 25, 30, 0) 100%
+  );
 }
 
 .footer > * {
@@ -75,13 +91,13 @@ limitations under the License.
 }
 
 @media (min-height: 300px) {
-  .footer {
-    height: calc(50px + 2 * 24px);
+  .inRoom {
+    --footerPadding: 24px;
   }
 }
 
 @media (min-width: 800px) {
-  .footer {
-    height: calc(50px + 2 * 32px);
+  .inRoom {
+    --footerPadding: 32px;
   }
 }
diff --git a/src/video-grid/NewVideoGrid.module.css b/src/video-grid/NewVideoGrid.module.css
index 7d0a896..e6f64ba 100644
--- a/src/video-grid/NewVideoGrid.module.css
+++ b/src/video-grid/NewVideoGrid.module.css
@@ -2,20 +2,31 @@
   contain: strict;
   position: relative;
   flex-grow: 1;
-  padding: 0 22px;
+  padding: 0 20px;
   overflow-y: auto;
   overflow-x: hidden;
 }
 
 .slotGrid {
-  contain: strict;
   position: relative;
   display: grid;
-  grid-auto-rows: 183px;
-  column-gap: 18px;
-  row-gap: 21px;
+  grid-auto-rows: 163px;
+  gap: 8px;
+  padding-bottom: var(--footerHeight);
 }
 
 .slot {
   contain: strict;
 }
+
+@media (min-width: 800px) {
+  .grid {
+    padding: 0 22px;
+  }
+
+  .slotGrid {
+    grid-auto-rows: 183px;
+    column-gap: 18px;
+    row-gap: 21px;
+  }
+}
diff --git a/src/video-grid/NewVideoGrid.tsx b/src/video-grid/NewVideoGrid.tsx
index f4b5811..b4d13fc 100644
--- a/src/video-grid/NewVideoGrid.tsx
+++ b/src/video-grid/NewVideoGrid.tsx
@@ -290,7 +290,9 @@ const cycleTileSize = (tileId: string, g: Grid): Grid => {
   const fromEnd = areaEnd(from, fromWidth, fromHeight, g);
 
   const [toWidth, toHeight] =
-    fromWidth === 1 && fromHeight === 1 ? [3, 2] : [1, 1];
+    fromWidth === 1 && fromHeight === 1
+      ? [Math.min(3, Math.max(2, g.columns - 1)), 2]
+      : [1, 1];
   const newRows = Math.max(
     0,
     Math.ceil((toWidth * toHeight - fromWidth * fromHeight) / g.columns)
@@ -308,7 +310,7 @@ const cycleTileSize = (tileId: string, g: Grid): Grid => {
   const nextScanLocations = new Set<number>([from]);
   const scanColumnOffset = Math.floor((toWidth - 1) / 2);
   const scanRowOffset = Math.floor((toHeight - 1) / 2);
-  const rows = row(g.cells.length - 1, g) + 1
+  const rows = row(g.cells.length - 1, g) + 1;
   let to: number | null = null;
 
   const displaceable = (c: Cell | undefined, i: number): boolean =>
@@ -320,7 +322,7 @@ const cycleTileSize = (tileId: string, g: Grid): Grid => {
     const start = scanLocation - scanColumnOffset - g.columns * scanRowOffset;
     const end = areaEnd(start, candidateWidth, candidateHeight, g);
     const startColumn = column(start, g);
-    const startRow = row(start, g)
+    const startRow = row(start, g);
     const endColumn = column(end, g);
 
     if (
@@ -424,8 +426,30 @@ export const NewVideoGrid: FC<Props> = ({
     return rects;
   }, [items, slotGridGeneration, slotGrid, gridBounds]);
 
-  const [grid, setGrid] = useReactiveState<Grid>(
-    (prevGrid = { generation: 0, columns: 6, cells: [] }) => {
+  const [columns] = useReactiveState<number | null>(
+    // Since grid resizing isn't implemented yet, pick a column count on mount
+    // and stick to it
+    (prevColumns) =>
+      prevColumns !== undefined && prevColumns !== null
+        ? prevColumns
+        : // The grid bounds might not be known yet
+        gridBounds.width === 0
+        ? null
+        : Math.max(2, Math.floor(gridBounds.width * 0.0045)),
+    [gridBounds]
+  );
+
+  const [grid, setGrid] = useReactiveState<Grid | null>(
+    (prevGrid = null) => {
+      if (prevGrid === null) {
+        // We can't do anything if the column count isn't known yet
+        if (columns === null) {
+          return null;
+        } else {
+          prevGrid = { generation: slotGridGeneration, columns, cells: [] };
+        }
+      }
+
       // Step 1: Update tiles that still exist, and remove tiles that have left
       // the grid
       const itemsById = new Map(items.map((i) => [i.id, i]));
@@ -462,18 +486,20 @@ export const NewVideoGrid: FC<Props> = ({
 
       return grid3;
     },
-    [items]
+    [items, columns]
   );
 
   const [tiles] = useReactiveState<Tile[]>(
     (prevTiles) => {
       // If React hasn't yet rendered the current generation of the layout, skip
       // the update, because grid and slotRects will be out of sync
-      if (slotGridGeneration !== grid.generation) return prevTiles ?? [];
+      if (slotGridGeneration !== grid?.generation) return prevTiles ?? [];
 
       const slotCells = grid.cells.filter((c) => c?.slot) as Cell[];
-      const tileRects = new Map<TileDescriptor, Rect>(zipWith(slotCells, slotRects, (cell, rect) => [cell.item, rect]))
-      return items.map(item => ({ ...tileRects.get(item)!, item }))
+      const tileRects = new Map<TileDescriptor, Rect>(
+        zipWith(slotCells, slotRects, (cell, rect) => [cell.item, rect])
+      );
+      return items.map((item) => ({ ...tileRects.get(item)!, item }));
     },
     [slotRects, grid, slotGridGeneration]
   );
@@ -516,7 +542,7 @@ export const NewVideoGrid: FC<Props> = ({
   ) as unknown as [TransitionFn<Tile, TileSpring>, SpringRef<TileSpring>];
 
   const slotGridStyle = useMemo(() => {
-    const columnCount = 6;
+    if (grid === null) return {};
 
     const areas = new Array<(number | null)[]>(
       Math.ceil(grid.cells.length / grid.columns)
@@ -548,9 +574,9 @@ export const NewVideoGrid: FC<Props> = ({
               .join(" ")}'`
         )
         .join(" "),
-      gridTemplateColumns: `repeat(${columnCount}, 1fr)`,
+      gridTemplateColumns: `repeat(${columns}, 1fr)`,
     };
-  }, [grid]);
+  }, [grid, columns]);
 
   const animateDraggedTile = (endOfGesture: boolean) => {
     const { tileId, tileX, tileY, cursorX, cursorY } = dragState.current!;
@@ -598,8 +624,8 @@ export const NewVideoGrid: FC<Props> = ({
     );
     if (overTile !== undefined && overTile.item.id !== tileId) {
       setGrid((g) => ({
-        ...g,
-        cells: g.cells.map((c) => {
+        ...g!,
+        cells: g!.cells.map((c) => {
           if (c?.item === overTile.item) return { ...c, item: tile.item };
           if (c?.item === tile.item) return { ...c, item: overTile.item };
           return c;
@@ -618,7 +644,7 @@ export const NewVideoGrid: FC<Props> = ({
     }: Parameters<Handler<"drag", EventTypes["drag"]>>[0]
   ) => {
     if (tap) {
-      setGrid((g) => cycleTileSize(tileId, g));
+      setGrid((g) => cycleTileSize(tileId, g!));
     } else {
       const tileSpring = springRef.current
         .find((c) => (c.item as Tile).item.id === tileId)!
@@ -671,8 +697,8 @@ export const NewVideoGrid: FC<Props> = ({
     return slots;
   }, [items.length]);
 
-  // Render nothing if the bounds are not yet known
-  if (gridBounds.width === 0) {
+  // Render nothing if the grid has yet to be generated
+  if (grid === null) {
     return <div ref={gridRef} className={styles.grid} />;
   }
 
diff --git a/src/video-grid/VideoTile.module.css b/src/video-grid/VideoTile.module.css
index 543e4fa..ce44d22 100644
--- a/src/video-grid/VideoTile.module.css
+++ b/src/video-grid/VideoTile.module.css
@@ -20,7 +20,7 @@ limitations under the License.
   top: 0;
   width: var(--tileWidth);
   height: var(--tileHeight);
-  border-radius: 20px;
+  border-radius: 8px;
   overflow: hidden;
   cursor: pointer;
 
@@ -174,3 +174,9 @@ limitations under the License.
   max-width: 360px;
   border-radius: 20px;
 }
+
+@media (min-width: 800px) {
+  .videoTile {
+    border-radius: 20px;
+  }
+}