phase: 01_runnable_service_scaffold
**Phase 01 final verification pass — all criteria green**
- Verified (nothing left to implement; all 4 task files already in `complete/`): toolchain lock, typed `Settings`+`.env.example`, debugpy bootstrap, app factory, CLI (`--host/--port/--reload/--levels-dir`, `python -m undercooked`), static shell + `/static`, security headers, no-CDN/doc guards, `validate.sh`.
- `uv sync --extra dev` → exit 0; `uv.lock` present (untracked → harness commits it with the phase).
- `uv run undercooked --host 127.0.0.1 --port 8000` → `curl /healthz` = 200 `{"status":"ok","version":"0.1.0","rooms":0}` (superset of the criterion's body — now documented in README); `curl /` = 200 shell HTML; static css/js 200; CSP/nosniff/no-referrer/DENY on 200, 404 and 405 too.
- `DEBUGPY=1`: port 5678 is held on this machine by the user's own podman dev-container debugpy adapter — **no live change made/killed**; verified via `DEBUGPY=1 DEBUGPY_PORT=5679` → listener opens, `/healthz` still 200; the occupied-port case degraded gracefully as designed (warning, server serves). Bad config → one line + exit 2 ✓.
- `bash .agents/validate.sh` (root **and** `.agents/`) → exit 0: `ruff check .` 0 errors, `pyright` 0 errors, levels-CLI notice-skip (matches README prose), **284 passed** full suite incl. 10 Playwright e2e, coverage **100.00%** (floor 90), repo hygiene clean. `uv run pytest -m e2e` → 10 passed.
- Docs fixed: README Status → phase 01 complete + next pending 02 (was pointing at `todo/01…`), documented real `/healthz` body, precedence now "CLI flags > env > .env > default", added the PLAN §13-promised **How to play** section (honest about shell-only today), Layout rows marked what exists vs phases 02/07. AGENTS.md: recorded the `data:`-URL exemption in the no-CDN rule (test allows it) and matched the session-token rule to the browser's WebCrypto/base64url implementation. Also corrected a stale "session-wide chromium" docstring in `tests/e2e/conftest.py` (fixtures are function-scoped).
- Deviations: no container build run (needs an image pull; not a phase criterion — `Containerfile`/`compose.yaml` reviewed and consistent with `docs/DEPLOYMENT.md`); `HEAD /` is 405 (routes are GET-only by design; health checks use GET). No git index/history commands run.
- Next pending phase: `02_level_content_and_validation`.
This commit is contained in:
@@ -0,0 +1,246 @@
|
||||
/* Undercooked — base stylesheet (phase 01 shell).
|
||||
*
|
||||
* Rules that later phases must keep:
|
||||
* - every byte is same-origin: no @import, no web fonts, no remote images (A12);
|
||||
* - the palette, spacing and type scale exist only as custom properties below;
|
||||
* - 44x44 minimum hit targets and >= 4.5:1 text contrast (PLAN.md phase 14);
|
||||
* - the layout is fluid from a 360px phone to a 4K desktop without breakpoints;
|
||||
* - motion is decorative: prefers-reduced-motion turns all of it off.
|
||||
*/
|
||||
|
||||
:root {
|
||||
/* Palette — warm kitchen, dark by default. Text pairs are >= 4.5:1. */
|
||||
--uc-bg: #1a1713;
|
||||
--uc-surface: #241f1a;
|
||||
--uc-surface-2: #2f2820;
|
||||
--uc-line: #857259; /* 3.1:1 on every surface: UI edges are visible (WCAG 1.4.11) */
|
||||
--uc-ink: #f6f1e7; /* 13.9:1 on --uc-bg */
|
||||
--uc-ink-muted: #cfc5b5; /* 8.6:1 on --uc-bg */
|
||||
--uc-accent: #ff9f1c; /* 7.6:1 on --uc-bg */
|
||||
--uc-accent-ink: #1a1713; /* 7.6:1 on --uc-accent */
|
||||
--uc-danger: #ff8a80; /* 7.4:1 on --uc-bg */
|
||||
--uc-focus: #ffe066;
|
||||
|
||||
/* Fluid type scale: 360px phone -> 3840px desktop, no breakpoints needed. */
|
||||
--uc-step--1: clamp(0.78rem, 0.74rem + 0.2vw, 0.92rem);
|
||||
--uc-step-0: clamp(1rem, 0.95rem + 0.3vw, 1.2rem);
|
||||
--uc-step-1: clamp(1.25rem, 1.15rem + 0.6vw, 1.7rem);
|
||||
--uc-step-2: clamp(1.7rem, 1.4rem + 1.6vw, 3rem);
|
||||
|
||||
--uc-space-1: 0.5rem;
|
||||
--uc-space-2: 0.75rem;
|
||||
--uc-space-3: 1.25rem;
|
||||
--uc-space-4: 2rem;
|
||||
|
||||
--uc-radius: 0.65rem;
|
||||
--uc-hit: 44px; /* WCAG 2.5.5 minimum target size */
|
||||
--uc-content: 74rem;
|
||||
--uc-font: ui-sans-serif, system-ui, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
--uc-font-ui: ui-rounded, "Segoe UI Variable Display", var(--uc-font);
|
||||
--uc-ease: cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100svh;
|
||||
display: grid;
|
||||
grid-template-rows: 1fr auto;
|
||||
gap: var(--uc-space-3);
|
||||
padding: max(var(--uc-space-3), env(safe-area-inset-top))
|
||||
max(var(--uc-space-2), env(safe-area-inset-right))
|
||||
max(var(--uc-space-3), env(safe-area-inset-bottom))
|
||||
max(var(--uc-space-2), env(safe-area-inset-left));
|
||||
background: radial-gradient(120% 90% at 50% -10%, var(--uc-surface-2), var(--uc-bg) 60%);
|
||||
color: var(--uc-ink);
|
||||
font-family: var(--uc-font);
|
||||
font-size: var(--uc-step-0);
|
||||
line-height: 1.45;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
.shell,
|
||||
.colophon {
|
||||
width: 100%;
|
||||
max-width: var(--uc-content);
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.masthead {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.wordmark {
|
||||
margin: 0;
|
||||
font-family: var(--uc-font-ui);
|
||||
font-size: var(--uc-step-2);
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.01em;
|
||||
color: var(--uc-accent);
|
||||
}
|
||||
|
||||
.tagline,
|
||||
.hint,
|
||||
.colophon p {
|
||||
margin: var(--uc-space-1) 0 0;
|
||||
color: var(--uc-ink-muted);
|
||||
font-size: var(--uc-step--1);
|
||||
}
|
||||
|
||||
.panel {
|
||||
display: grid;
|
||||
gap: var(--uc-space-2);
|
||||
margin: var(--uc-space-3) auto 0;
|
||||
padding: var(--uc-space-3);
|
||||
max-width: 34rem;
|
||||
background: var(--uc-surface);
|
||||
border: 1px solid var(--uc-line);
|
||||
border-radius: var(--uc-radius);
|
||||
}
|
||||
|
||||
.panel h2 {
|
||||
margin: 0;
|
||||
font-family: var(--uc-font-ui);
|
||||
font-size: var(--uc-step-1);
|
||||
}
|
||||
|
||||
.join-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--uc-space-2);
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: var(--uc-space-1);
|
||||
font-size: var(--uc-step--1);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: var(--uc-ink-muted);
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
flex: 1 1 8.5rem;
|
||||
/* An <input>'s intrinsic width is its default 20 characters, which would widen
|
||||
the whole fluid layout on a 360px phone. The value is 5 characters. */
|
||||
min-width: 0;
|
||||
min-height: var(--uc-hit);
|
||||
padding: 0 var(--uc-space-2);
|
||||
background: var(--uc-bg);
|
||||
color: var(--uc-ink);
|
||||
border: 1px solid var(--uc-line);
|
||||
border-radius: var(--uc-radius);
|
||||
font: inherit;
|
||||
font-size: var(--uc-step-1);
|
||||
letter-spacing: 0.35em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
input[type="text"]::placeholder {
|
||||
color: #8d8272; /* 4.6:1 on --uc-bg: placeholder text is real text */
|
||||
letter-spacing: 0.35em;
|
||||
}
|
||||
|
||||
.btn {
|
||||
min-height: var(--uc-hit);
|
||||
min-width: var(--uc-hit);
|
||||
padding: 0 var(--uc-space-3);
|
||||
background: var(--uc-surface-2);
|
||||
color: var(--uc-ink);
|
||||
border: 1px solid var(--uc-line);
|
||||
border-radius: var(--uc-radius);
|
||||
font: inherit;
|
||||
font-size: var(--uc-step-0);
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
transform 120ms var(--uc-ease),
|
||||
background-color 120ms var(--uc-ease);
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: #3b3226;
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--uc-accent);
|
||||
color: var(--uc-accent-ink);
|
||||
border-color: var(--uc-accent);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #ffb445;
|
||||
}
|
||||
|
||||
/* Visible focus for keyboard and low-vision users; never remove this. */
|
||||
:where(a, button, input, [tabindex]):focus-visible {
|
||||
outline: 3px solid var(--uc-focus);
|
||||
outline-offset: 2px;
|
||||
border-radius: var(--uc-radius);
|
||||
}
|
||||
|
||||
.status {
|
||||
min-height: calc(var(--uc-hit) - 8px);
|
||||
padding: var(--uc-space-2);
|
||||
background: var(--uc-bg);
|
||||
border: 1px dashed var(--uc-line);
|
||||
border-radius: var(--uc-radius);
|
||||
font-size: var(--uc-step--1);
|
||||
}
|
||||
|
||||
.status[data-kind="error"] {
|
||||
color: var(--uc-danger);
|
||||
border-color: var(--uc-danger);
|
||||
}
|
||||
|
||||
#kcanvas {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
aspect-ratio: 16 / 10;
|
||||
background: #0f0d0b;
|
||||
border: 1px solid var(--uc-line);
|
||||
border-radius: var(--uc-radius);
|
||||
}
|
||||
|
||||
#kcanvas[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: ui-monospace, "Cascadia Mono", "Segoe UI Mono", Menlo, monospace;
|
||||
font-size: 0.95em;
|
||||
color: var(--uc-accent);
|
||||
}
|
||||
|
||||
.colophon {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
animation-duration: 0.001ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.001ms !important;
|
||||
scroll-behavior: auto !important;
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user