Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion challenges/birdsbirdbirds/challenge.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
{
"id": "draw-bird",
"title": "Draw your bird",
"hint": "Draw a bird on the page and press Save.",
"hint": "Draw a bird on the page and press Save my bird.",
"done_when": "The file bird.png exists in the workspace."
},
{
Expand Down
19 changes: 18 additions & 1 deletion challenges/birdsbirdbirds/page/app.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ const toSkyButton = document.getElementById('to-sky-button')
const drawAgainButton = document.getElementById('draw-again-button')
const sky = document.getElementById('sky')

// The starting prompt is written once, in index.html, and read back here.
const INITIAL_PROMPT = statusLine.textContent
const DRAWN_PROMPT = 'Looking good! Press Save my bird when it’s done.'
const EMPTY_CANVAS_ERROR = 'The canvas is empty. Draw your bird first!'

const pen = { colour: COLOURS[0].value, width: PEN_SIZES[1].width, erasing: false }
const stroke = { pointerId: undefined, lastPoint: undefined }
const undoSnapshots = []
Expand Down Expand Up @@ -191,6 +196,17 @@ function endStroke(event) {

stroke.pointerId = undefined
stroke.lastPoint = undefined
promptToSave()
}

/**
* Swaps the initial prompt, or the empty-canvas error, for a nudge to save
* once there is a drawing. Any other message (Undone, Cleared, Saved, a save
* failure) is left alone, since it reports something the attendee just did.
*/
function promptToSave() {
const showsPreDrawingMessage = statusLine.textContent === INITIAL_PROMPT || statusLine.textContent === EMPTY_CANVAS_ERROR
if (showsPreDrawingMessage && !isCanvasBlank()) setStatus(DRAWN_PROMPT)
}

/** Clears the canvas to transparent, undoably. */
Expand Down Expand Up @@ -219,7 +235,7 @@ function canvasToPng() {

/** Saves the drawing to the workspace as bird.png and reports how it went. */
async function saveBird() {
if (isCanvasBlank()) return setStatus('The canvas is empty. Draw your bird first!', 'error')
if (isCanvasBlank()) return setStatus(EMPTY_CANVAS_ERROR, 'error')

saveButton.disabled = true
setStatus('Saving…')
Expand Down Expand Up @@ -281,6 +297,7 @@ function showDraw() {
skyMode.hidden = true
drawMode.hidden = false
sky.replaceChildren()
setStatus(isCanvasBlank() ? INITIAL_PROMPT : DRAWN_PROMPT)
toSkyButton.hidden = flight.entry === undefined
}

Expand Down
49 changes: 27 additions & 22 deletions challenges/birdsbirdbirds/page/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,39 +18,44 @@ <h1>Draw your bird</h1>
</header>

<div class="draw-layout">
<div class="toolbar" role="toolbar" aria-label="Drawing tools">
<fieldset class="tool-group">
<legend>Colour</legend>
<div class="swatches" id="swatches"></div>
</fieldset>
<div class="side-column">
<div class="toolbar" role="toolbar" aria-label="Drawing tools">
<fieldset class="tool-group">
<legend>Colour</legend>
<div class="swatches" id="swatches"></div>
</fieldset>

<fieldset class="tool-group">
<legend>Pen size</legend>
<div class="sizes" id="sizes"></div>
</fieldset>
<fieldset class="tool-group">
<legend>Pen size</legend>
<div class="sizes" id="sizes"></div>
</fieldset>

<fieldset class="tool-group">
<legend>Tool</legend>
<div class="tools">
<button type="button" class="tool-button" id="pen-button" aria-pressed="true">Pen</button>
<button type="button" class="tool-button" id="eraser-button" aria-pressed="false">Eraser</button>
<fieldset class="tool-group">
<legend>Tool</legend>
<div class="tools">
<button type="button" class="tool-button" id="pen-button" aria-pressed="true">Pen</button>
<button type="button" class="tool-button" id="eraser-button" aria-pressed="false">Eraser</button>
</div>
</fieldset>

<div class="tool-group actions">
<button type="button" class="action-button" id="undo-button" title="Undo (Ctrl+Z or Cmd+Z)" disabled>Undo</button>
<button type="button" class="action-button" id="clear-button">Clear</button>
</div>
</fieldset>
</div>

<div class="tool-group actions">
<button type="button" class="action-button" id="undo-button" title="Undo (Ctrl+Z or Cmd+Z)" disabled>Undo</button>
<button type="button" class="action-button" id="clear-button">Clear</button>
<div class="save-area">
<button type="button" class="save-button" id="save-button">Save my bird</button>
<p class="status" id="status" role="status" aria-live="polite">Pick a colour and start drawing.</p>
<p class="waiting" id="waiting-note" hidden>Waiting for Pi's flight plan…</p>
<button type="button" class="link-button" id="to-sky-button" hidden>Back to the sky</button>
</div>
</div>

<div class="canvas-column">
<div class="canvas-frame">
<canvas id="drawing" width="512" height="512" aria-label="Drawing canvas"></canvas>
</div>
<button type="button" class="save-button" id="save-button">Save my bird</button>
<p class="status" id="status" role="status" aria-live="polite">Pick a colour and start drawing.</p>
<p class="waiting" id="waiting-note" hidden>Waiting for Pi's flight plan…</p>
<button type="button" class="link-button" id="to-sky-button" hidden>Back to the sky</button>
</div>
</div>
</main>
Expand Down
116 changes: 82 additions & 34 deletions challenges/birdsbirdbirds/page/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -44,39 +44,51 @@ button:disabled {
opacity: 0.45;
}

/* The booth station's Chrome is about 1000x597, so the whole drawing page,
Save button included, must fit one screen. The floor keeps shorter or
narrower windows usable by letting the page scroll instead of squashing. */
#draw-mode {
max-width: 1000px;
display: grid;
grid-template-rows: auto minmax(0, 1fr);
max-width: 1280px;
height: max(100dvh, 560px);
margin: 0 auto;
padding: 24px 20px 40px;
padding: 10px 20px 14px;
}

.draw-header h1 {
margin: 0 0 6px;
font-size: 2.4rem;
margin: 0 0 2px;
font-size: 1.7rem;
line-height: 1.15;
}

.lead {
margin: 0 0 20px;
font-size: 1.15rem;
margin: 0 0 8px;
font-size: 1rem;
}

.draw-layout {
display: flex;
flex-wrap: wrap;
gap: 24px;
align-items: flex-start;
min-height: 0;
}

.side-column {
display: flex;
flex-direction: column;
gap: 8px;
flex: 0 0 280px;
}

.toolbar {
display: flex;
flex-direction: column;
gap: 16px;
flex: 0 0 220px;
gap: 8px;
}

.tool-group {
margin: 0;
padding: 12px;
padding: 4px 8px 8px;
border: 2px solid var(--slide-text);
border-radius: 14px;
background: #fff;
Expand All @@ -91,28 +103,29 @@ button:disabled {
.sizes,
.tools {
display: flex;
flex-wrap: wrap;
gap: 8px;
gap: 4px;
}

.swatch {
width: 52px;
height: 52px;
flex: 1;
min-width: 0;
aspect-ratio: 1;
max-height: 44px;
border: 3px solid #fff;
border-radius: 50%;
box-shadow: 0 0 0 2px var(--slide-text);
}

.swatch[aria-pressed="true"] {
box-shadow: 0 0 0 4px var(--slide-text), 0 0 0 7px var(--slide-lime);
box-shadow: 0 0 0 3px var(--slide-text), 0 0 0 5px var(--slide-lime);
}

.size {
display: flex;
flex: 1;
align-items: center;
justify-content: center;
width: 56px;
height: 56px;
height: 44px;
border: 2px solid var(--slide-text);
border-radius: 12px;
background: #fff;
Expand All @@ -132,12 +145,12 @@ button:disabled {
.tool-button,
.action-button {
flex: 1;
min-height: 52px;
padding: 8px 14px;
min-height: 44px;
padding: 4px 14px;
border: 2px solid var(--slide-text);
border-radius: 12px;
background: #fff;
font-size: 1.1rem;
font-size: 1.05rem;
font-weight: 700;
}

Expand All @@ -146,16 +159,27 @@ button:disabled {
gap: 8px;
}

.canvas-column {
/* Beside the tools, so Save is never below the fold. */
.save-area {
display: flex;
flex-direction: column;
align-items: center;
gap: 14px;
flex: 1 1 512px;
gap: 6px;
align-items: stretch;
}

/* Sized from the space left over, so the square canvas never overflows. */
.canvas-column {
display: flex;
flex: 1;
align-items: flex-start;
justify-content: center;
min-width: 0;
container-type: size;
}

.canvas-frame {
width: min(512px, 100%);
/* Room for the frame's 6px offset shadow. */
width: min(100cqw - 6px, 100cqh - 6px, 640px);
aspect-ratio: 1;
border: 3px solid var(--slide-text);
border-radius: 16px;
Expand All @@ -168,6 +192,29 @@ button:disabled {
background-size: 32px 32px;
}

/* Too narrow for tools beside the canvas: draw first, tools below, and let the
page scroll. The canvas column needs a definite height for its container
query sizing. */
@media (max-width: 40rem) {
#draw-mode {
height: auto;
}

.draw-layout {
flex-direction: column;
}

.canvas-column {
flex: none;
order: -1;
height: min(100vw - 40px, 640px);
}

.side-column {
flex-basis: auto;
}
}

#drawing {
display: block;
width: 100%;
Expand All @@ -181,14 +228,13 @@ button:disabled {
}

.save-button {
min-width: 260px;
min-height: 64px;
padding: 12px 32px;
min-height: 56px;
padding: 8px 16px;
border: 3px solid var(--slide-text);
border-radius: 16px;
background: var(--slide-lime);
box-shadow: 4px 4px 0 var(--slide-text);
font-size: 1.5rem;
font-size: 1.4rem;
font-weight: 800;
}

Expand All @@ -198,9 +244,9 @@ button:disabled {
}

.status {
min-height: 1.5em;
min-height: 2.6em;
margin: 0;
font-size: 1.2rem;
font-size: 1rem;
font-weight: 600;
text-align: center;
}
Expand All @@ -215,14 +261,16 @@ button:disabled {

.waiting {
margin: 0;
font-size: 1rem;
font-size: 0.9rem;
text-align: center;
opacity: 0.7;
}

.link-button {
padding: 2px;
border: none;
background: none;
font-size: 1.1rem;
font-size: 1rem;
font-weight: 700;
text-decoration: underline;
}
Expand Down
2 changes: 1 addition & 1 deletion challenges/birdsbirdbirds/server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ function sendSavedBird(response, workspace) {
body = readWorkspaceFile(path.join(workspace, 'bird.png'), MAX_PNG_BYTES)
} catch (error) {
if (error.code === 'ENOENT') return sendText(response, 404, 'no bird.png yet')
if (error instanceof RefusedFileError) return sendText(response, 422, `${error.message}; press Save on the drawing page again`)
if (error instanceof RefusedFileError) return sendText(response, 422, `${error.message}; press Save my bird on the drawing page again`)
throw error
}

Expand Down
Loading
Loading