Skip to content

Commit 3673e64

Browse files
committed
Video record layout fixes
1 parent 7370e21 commit 3673e64

File tree

1 file changed

+97
-45
lines changed

1 file changed

+97
-45
lines changed

src/components/GitSequencer.jsx

Lines changed: 97 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,6 @@ const GitSequencer = () => {
166166
const scale = 3;
167167
const CELL_SIZE = 10 * scale;
168168
const GAP = 3 * scale;
169-
const PADDING = 8 * scale; // 0.5rem on mobile
170169

171170
// Full HD portrait (1080x1920)
172171
const canvasWidth = 1080;
@@ -175,29 +174,41 @@ const GitSequencer = () => {
175174
if (canvas.width !== canvasWidth) canvas.width = canvasWidth;
176175
if (canvas.height !== canvasHeight) canvas.height = canvasHeight;
177176

178-
// Exact mobile CSS spacing (base 16px, scaled 3x)
179-
// .header-fieldset: padding 0.75rem 1rem, margin-bottom 1rem
180-
// .header-content: gap 1rem
181-
// .command-section: margin-bottom 1.5rem
182-
// .status-msg: margin-top 0.5rem
183-
// .graph-section: margin-bottom 2rem
177+
// Mobile CSS layout (consistent spacing throughout)
178+
// All content uses same horizontal padding from canvas edge
179+
const contentPadding = 16 * scale; // 1rem - consistent for all elements
180+
const contentWidth = canvasWidth - contentPadding * 2;
184181

182+
// Fieldset internal padding (matches mobile .header-fieldset padding)
185183
const fieldsetPaddingX = 16 * scale; // 1rem
186-
const fieldsetPaddingY = 12 * scale; // 0.75rem
187-
const fieldsetMarginBottom = 16 * scale; // 1rem
188-
const headerContentGap = 16 * scale; // 1rem
184+
const fieldsetPaddingY = 16 * scale; // 0.75rem -> increased for breathing room
185+
186+
// Vertical spacing (matches mobile CSS)
187+
const headerContentGap = 16 * scale; // 1rem gap between ASCII and subtitle
188+
const fieldsetMarginBottom = 32 * scale; // spacing after header
189189
const statusMarginTop = 8 * scale; // 0.5rem
190-
const graphMarginTop = 24 * scale; // 1.5rem (command-section margin-bottom)
190+
const graphMarginTop = 24 * scale; // 1.5rem
191+
192+
// Font sizes
193+
const asciiFontSize = 9 * scale;
194+
const subtitleFontSize = 14 * scale;
195+
const commandFontSize = 15 * scale;
196+
const statusFontSize = 14 * scale;
197+
const legendTitleSize = 16 * scale;
198+
const legendVersionSize = 12 * scale;
191199

192200
// Calculate fieldset content height
193-
const asciiHeight = 4 * 9 * scale; // 4 lines at ~9px each
194-
const subtitleHeight = 14 * scale;
201+
const asciiLineHeight = asciiFontSize * 1.2;
202+
const asciiHeight = 4 * asciiLineHeight;
203+
const subtitleLineHeight = subtitleFontSize * 1.3;
204+
const subtitleLines = 2; // "Turn your GitHub contributions" + "into music"
205+
const subtitleHeight = subtitleLines * subtitleLineHeight;
195206
const fieldsetContentHeight = asciiHeight + headerContentGap + subtitleHeight;
196207
const fieldsetHeight = fieldsetPaddingY * 2 + fieldsetContentHeight;
197208

198209
// Calculate total content height for vertical centering
199-
const commandLineHeight = 15 * scale;
200-
const statusLineHeight = 14 * scale;
210+
const commandLineHeight = commandFontSize;
211+
const statusLineHeight = statusFontSize;
201212
const gridHeight = 7 * (CELL_SIZE + GAP);
202213

203214
const totalContentHeight =
@@ -219,79 +230,107 @@ const GitSequencer = () => {
219230
let currentY = offsetY;
220231

221232
// ===== FIELDSET HEADER =====
222-
const fieldsetWidth = canvasWidth - PADDING * 2;
233+
const fieldsetX = contentPadding;
234+
const fieldsetWidth = contentWidth;
223235

224236
// Fieldset border
225237
ctx.strokeStyle = colors.accent;
226238
ctx.lineWidth = scale;
227239
ctx.beginPath();
228-
ctx.roundRect(PADDING, currentY, fieldsetWidth, fieldsetHeight, 4 * scale);
240+
ctx.roundRect(fieldsetX, currentY, fieldsetWidth, fieldsetHeight, 4 * scale);
229241
ctx.stroke();
230242

231-
// Legend background
243+
// Measure legend text widths dynamically
244+
const titleFont = `bold ${legendTitleSize}px monospace`;
245+
const versionFont = `${legendVersionSize}px monospace`;
246+
ctx.font = titleFont;
247+
const titleText = 'GitHub Music';
248+
const titleWidth = ctx.measureText(titleText).width;
249+
ctx.font = versionFont;
250+
const versionText = 'v1.0.0';
251+
const versionWidth = ctx.measureText(versionText).width;
252+
const legendGap = 8 * scale;
253+
const legendPaddingH = 8 * scale;
254+
const totalLegendWidth = titleWidth + legendGap + versionWidth + legendPaddingH * 2;
255+
256+
// Legend position (inside fieldset border, with margin from left edge)
257+
const legendX = fieldsetX + fieldsetPaddingX;
258+
const legendY = currentY - legendVersionSize / 2;
259+
260+
// Legend background (covers border)
232261
ctx.fillStyle = colors.bg;
233-
ctx.fillRect(PADDING + 8 * scale, currentY - 8 * scale, 145 * scale, 16 * scale);
262+
ctx.fillRect(legendX - legendPaddingH, legendY - legendVersionSize / 2, totalLegendWidth, legendTitleSize + 4 * scale);
234263

235264
// Legend text: "GitHub Music v1.0.0"
236265
ctx.textAlign = 'left';
266+
ctx.textBaseline = 'middle';
237267
ctx.fillStyle = colors.accent;
238-
ctx.font = `bold ${16 * scale}px monospace`; // 1rem on mobile
239-
ctx.fillText('GitHub Music', PADDING + 12 * scale, currentY + 4 * scale);
268+
ctx.font = titleFont;
269+
ctx.fillText(titleText, legendX, currentY);
240270
ctx.fillStyle = colors.textDim;
241-
ctx.font = `${12 * scale}px monospace`;
242-
ctx.fillText('v1.0.0', PADDING + 130 * scale, currentY + 4 * scale);
271+
ctx.font = versionFont;
272+
ctx.fillText(versionText, legendX + titleWidth + legendGap, currentY);
273+
ctx.textBaseline = 'alphabetic';
243274

244-
// ASCII art (centered in fieldset) - font-size: 0.55rem = ~9px
245-
const asciiStartY = currentY + fieldsetPaddingY + 10 * scale;
275+
// ASCII art (centered within fieldset content area)
276+
const fieldsetContentTop = currentY + fieldsetPaddingY;
246277
ctx.fillStyle = colors.accent;
247-
ctx.font = `${9 * scale}px monospace`;
278+
ctx.font = `${asciiFontSize}px monospace`;
248279
ctx.textAlign = 'center';
280+
ctx.textBaseline = 'top';
249281
ctx.globalAlpha = 0.8;
250282
const asciiLines = [
251283
' ♫ ♪',
252284
' ▄ █ ▄ █ ▄ █',
253285
' █ █ █ █ █ █',
254286
' ▀ ▀ ▀ ▀ ▀ ▀'
255287
];
288+
const fieldsetCenterX = fieldsetX + fieldsetWidth / 2;
256289
asciiLines.forEach((line, i) => {
257-
ctx.fillText(line, canvasWidth / 2, asciiStartY + i * 9 * scale);
290+
ctx.fillText(line, fieldsetCenterX, fieldsetContentTop + i * asciiLineHeight);
258291
});
259292
ctx.globalAlpha = 1;
260293

261-
// Subtitle (centered) - font-size: 0.85rem = ~14px
262-
const subtitleY = asciiStartY + asciiHeight + headerContentGap;
294+
// Subtitle (centered within fieldset, wrapped to two lines)
295+
const subtitleY = fieldsetContentTop + asciiHeight + headerContentGap;
263296
ctx.fillStyle = colors.accent;
264-
ctx.font = `bold ${14 * scale}px monospace`;
297+
ctx.font = `bold ${subtitleFontSize}px monospace`;
265298
ctx.textAlign = 'center';
266-
ctx.fillText('Turn your GitHub contributions into music', canvasWidth / 2, subtitleY);
299+
ctx.textBaseline = 'top';
300+
ctx.fillText('Turn your GitHub contributions', fieldsetCenterX, subtitleY);
301+
ctx.fillText('into music', fieldsetCenterX, subtitleY + subtitleLineHeight);
302+
ctx.textBaseline = 'alphabetic';
267303

268304
currentY += fieldsetHeight + fieldsetMarginBottom;
269305

270-
// ===== COMMAND LINE (left-aligned) - font-size: 15px =====
306+
// ===== COMMAND LINE (left-aligned with content padding) =====
271307
ctx.textAlign = 'left';
272308
ctx.fillStyle = colors.accentCyan;
273-
ctx.font = `${15 * scale}px monospace`;
274-
ctx.fillText('$', PADDING + fieldsetPaddingX, currentY);
309+
ctx.font = `${commandFontSize}px monospace`;
310+
const cmdX = contentPadding;
311+
ctx.fillText('$', cmdX, currentY);
275312

276313
ctx.fillStyle = colors.accentYellow;
277-
ctx.fillText('git-music fetch', PADDING + fieldsetPaddingX + 18 * scale, currentY);
314+
const promptWidth = ctx.measureText('$ ').width;
315+
ctx.fillText('git-music fetch', cmdX + promptWidth, currentY);
278316

279317
ctx.fillStyle = colors.textBright;
280-
ctx.fillText(username, PADDING + fieldsetPaddingX + 170 * scale, currentY);
318+
const cmdWidth = ctx.measureText('git-music fetch ').width;
319+
ctx.fillText(username, cmdX + promptWidth + cmdWidth, currentY);
281320

282321
currentY += commandLineHeight + statusMarginTop;
283322

284-
// ===== STATUS MESSAGE (left-aligned) - font-size: 14px, margin-top: 0.5rem =====
323+
// ===== STATUS MESSAGE (left-aligned with content padding) =====
285324
ctx.fillStyle = colors.success;
286-
ctx.font = `${14 * scale}px monospace`;
325+
ctx.font = `${statusFontSize}px monospace`;
287326
ctx.textAlign = 'left';
288-
ctx.fillText(`✓ loaded ${data.weeks.length} weeks`, PADDING + fieldsetPaddingX, currentY);
327+
ctx.fillText(`✓ loaded ${data.weeks.length} weeks`, contentPadding, currentY);
289328

290329
currentY += statusLineHeight + graphMarginTop;
291330

292-
// ===== CONTRIBUTION GRID WITH SCROLL =====
331+
// ===== CONTRIBUTION GRID =====
293332
const gridTotalWidth = data.weeks.length * (CELL_SIZE + GAP);
294-
const visibleWidth = canvasWidth - (PADDING + fieldsetPaddingX) * 2;
333+
const visibleWidth = contentWidth;
295334

296335
// Calculate scroll offset to center active column
297336
let scrollOffset = 0;
@@ -304,15 +343,15 @@ const GitSequencer = () => {
304343
// Clip region for grid
305344
ctx.save();
306345
ctx.beginPath();
307-
ctx.rect(PADDING + fieldsetPaddingX, currentY, visibleWidth, gridHeight + 5 * scale);
346+
ctx.rect(contentPadding, currentY, visibleWidth, gridHeight + 5 * scale);
308347
ctx.clip();
309348

310349
// Draw Grid with scroll offset
311350
data.weeks.forEach((week, wIndex) => {
312-
const x = PADDING + fieldsetPaddingX + wIndex * (CELL_SIZE + GAP) - scrollOffset;
351+
const x = contentPadding + wIndex * (CELL_SIZE + GAP) - scrollOffset;
313352

314353
// Skip if outside visible area
315-
if (x + CELL_SIZE < PADDING + fieldsetPaddingX || x > canvasWidth - PADDING - fieldsetPaddingX) return;
354+
if (x + CELL_SIZE < contentPadding || x > canvasWidth - contentPadding) return;
316355

317356
week.days.forEach((day, dIndex) => {
318357
const y = currentY + dIndex * (CELL_SIZE + GAP);
@@ -453,6 +492,16 @@ const GitSequencer = () => {
453492
});
454493
};
455494

495+
// Screenshot export for testing layout
496+
const handleScreenshot = () => {
497+
const canvas = canvasRef.current;
498+
if (!canvas) return;
499+
const link = document.createElement('a');
500+
link.download = `git-music-preview-${username || 'test'}.png`;
501+
link.href = canvas.toDataURL('image/png');
502+
link.click();
503+
};
504+
456505
// Load user from URL on mount
457506
useEffect(() => {
458507
const params = new URLSearchParams(window.location.search);
@@ -480,6 +529,9 @@ const GitSequencer = () => {
480529
case 'KeyS':
481530
if (data) handleShare();
482531
break;
532+
case 'KeyP':
533+
if (data) handleScreenshot();
534+
break;
483535
case 'Escape':
484536
if (isPlaying) stop();
485537
if (isRecording) handleExport();
@@ -488,7 +540,7 @@ const GitSequencer = () => {
488540
};
489541
window.addEventListener('keydown', handleKeyDown);
490542
return () => window.removeEventListener('keydown', handleKeyDown);
491-
}, [handleTogglePlay, data, isPlaying, isRecording, stop, handleExport, handleShare]);
543+
}, [handleTogglePlay, data, isPlaying, isRecording, stop, handleExport, handleShare, handleScreenshot]);
492544

493545
return (
494546
<div className="terminal-window">

0 commit comments

Comments
 (0)