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
176 changes: 3 additions & 173 deletions web/src/app/pattern-lab/LabPanels.module.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* ── Layered lab: dock header + panel chrome ─────────────────────────────────
The dock with its header and menus, and the chrome every panel shares. A
panel's own classes go in panels/<Panel>.module.css, as Layers' did on
2026-10-25; the Preview, Pixel, Ramp and Knobs rules below are still here.
panel's own classes go in panels/<Panel>.module.css: Layers' went there on
2026-10-25, and Preview's, Pixel's, Ramp's and Knobs' the same day.
Modal styles are in PatternLab.module.css. */

.dockShell {
Expand Down Expand Up @@ -436,107 +436,7 @@
color: #f7f3ea;
}

/* ── Preview panel ── */

.previewBody {
flex: 1 1 auto;
min-height: 0;
display: flex;
flex-direction: column;
gap: 8px;
padding: 10px;
overflow: auto;
}

.previewCanvasBox {
flex: 1 1 auto;
min-height: 120px;
display: flex;
align-items: center;
justify-content: center;
min-width: 0;
overflow: hidden;
}

.previewFrame {
background: #060505;
border: 1px solid #171512;
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.04);
max-width: 100%;
max-height: 100%;
padding: 10px;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
}

.previewFrame canvas {
image-rendering: pixelated;
width: 100%;
height: 100%;
object-fit: contain;
}

/* Camera view frames arrive at output resolution — smooth them down, the
way the export will actually look, instead of nearest-neighbor grit. */
.previewFrame canvas.cameraCanvas {
image-rendering: auto;
}

@media (hover: none) {
/* A finger has no Alt key, right button or middle button. On a 390×844
phone this line wrapped the Pixel status bar to 51px, which left the
picture a 372×151 viewport and fitted it at 1×. Without it the viewport
is 372×177, and 2× needs 168. */
.pixelMouseHint {
display: none;
}
}

/* ── Pixel editor panel ── */

.pixelViewport {
flex: 1 1 auto;
min-height: 0;
overflow: auto;
display: flex;
background: #eee8d8;
padding: 16px;
}

.pixelStage {
position: relative;
flex: 0 0 auto;
/* auto margins center when small AND keep every edge scrollable when zoomed */
margin: auto;
background: repeating-conic-gradient(#c9c2b1 0% 25%, #b5ad9a 0% 50%) 0 0 / 12px 12px;
box-shadow: 0 0 0 1px rgba(23, 21, 18, 0.5);
}

.pixelStage canvas {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
image-rendering: pixelated;
display: block;
}

.pixelStatus {
display: flex;
align-items: center;
gap: 10px;
padding: 4px 8px;
border-top: 1px solid rgba(23, 21, 18, 0.12);
font-family: var(--font-inter), system-ui, sans-serif;
font-size: 11px;
color: rgba(23, 21, 18, 0.6);
text-transform: none;
flex: 0 0 auto;
flex-wrap: wrap;
}

/* Color inputs in the Pixel, Text and Graphic Export bars. */
.colorWell {
width: 26px;
height: 22px;
Expand All @@ -546,71 +446,6 @@
cursor: pointer;
}

.swatchRow {
display: inline-flex;
gap: 3px;
}

.swatch {
width: 16px;
height: 16px;
border: 1px solid rgba(23, 21, 18, 0.3);
padding: 0;
cursor: pointer;
background: none;
}

.panelBar .swatch {
width: 22px;
height: 22px;
min-height: 22px;
padding: 0;
border-radius: 3px;
}

.toolSep {
width: 1px;
align-self: stretch;
background: rgba(23, 21, 18, 0.15);
margin: 0 2px;
}

/* ── Ramp panel (dock variant) ── */

.rampDock {
container: ramp-controls / inline-size;
padding: 10px;
display: flex;
flex-direction: column;
gap: 8px;
}

.rampAlphaRow {
display: flex;
align-items: center;
gap: 8px;
font-family: var(--font-inter), system-ui, sans-serif;
font-size: 11px;
text-transform: none;
color: rgba(23, 21, 18, 0.65);
}

.rampAlphaRow input[type="range"] {
flex: 1 1 auto;
min-width: 0;
accent-color: #171512;
}

.rampAlphaValue {
width: 36px;
text-align: right;
}

/* Knobs panel: dock padding around the shared .controls block */
.knobsDock {
padding: 10px;
}

/* Compact scrollbars inside panels */
.panel ::-webkit-scrollbar {
width: 10px;
Expand All @@ -629,8 +464,3 @@
outline: 2px solid #9b4827;
outline-offset: 2px;
}

/* Scrolling keeps the ramp and controls at their intended height. */
.rampDock > * {
flex-shrink: 0;
}
29 changes: 25 additions & 4 deletions web/src/app/pattern-lab/labCss.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,13 +172,13 @@ describe("Layers and Knobs on a touch screen", () => {
});

describe("Pixel status line on a touch screen", () => {
const dock = css("LabPanels.module.css");
const own = css("panels/PixelPanel.module.css");
const pixel = css("panels/PixelPanel.tsx");

it("drops the mouse gestures, and nothing else", () => {
expect(mediaRule(dock, "(hover: none)", ".pixelMouseHint")).toMatch(/display: none;/);
expect(pixel).toContain("<span className={dock.pixelMouseHint}>alt-click picks · right-drag erases · middle-drag pans</span>");
expect(mediaRule(dock, "(hover: none)", ".pixelStatus")).toBe("");
expect(mediaRule(own, "(hover: none)", ".pixelMouseHint")).toMatch(/display: none;/);
expect(pixel).toContain("<span className={local.pixelMouseHint}>alt-click picks · right-drag erases · middle-drag pans</span>");
expect(mediaRule(own, "(hover: none)", ".pixelStatus")).toBe("");
});
});

Expand Down Expand Up @@ -258,3 +258,24 @@ describe("PatternLab.module.css", () => {
expect(alone).toEqual([]);
});
});

describe("LabPanels.module.css", () => {
// The dock and the chrome the panels share. Until 25 October 2026 it also
// held 20 rules that one panel file read, for Preview's body and frame,
// Pixel's viewport and toolbars, Color Ramp's body and Knobs' padding. A
// class one file under panels/ reads belongs in that panel's stylesheet.
// The shell's header is the dock, so a class only the shell reads stays.
it("holds no class that only one panel file reads", () => {
const read = readers(path.resolve(__dirname, "LabPanels.module.css"));
expect(read).not.toBeNull();
// Only names the file defines: registry.tsx also reads `def.dock.ref`.
const defined = new Set([...bareCss("LabPanels.module.css").matchAll(/\.(-?[_a-zA-Z][\w-]*)/g)].map((m) => m[1]));
const panels = path.join(__dirname, "panels") + path.sep;
const alone = [...(read ?? [])]
.filter(([cls, from]) => defined.has(cls) && from.size === 1 && [...from].every((file) => file.startsWith(panels)))
.map(([cls]) => cls)
.sort();
expect(defined.size).toBeGreaterThan(0);
expect(alone).toEqual([]);
});
});
8 changes: 7 additions & 1 deletion web/src/app/pattern-lab/panels/KnobsPanel.module.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* ── KnobsPanel: its own classes ─────────────────────────────────────────
Moved out of PatternLab.module.css on 2026-09; only this file uses them. */
Moved out of PatternLab.module.css on 2026-09, and knobsDock out of
LabPanels.module.css on 2026-10-25; only this file uses them. */

.controls {
container: knob-controls / inline-size;
Expand All @@ -8,6 +9,11 @@
margin-top: 2px;
}

/* The dock's padding around .controls. */
.knobsDock {
padding: 10px;
}

/* Two lines per knob: name · value · push on top, then a full-width slider
flanked by its min/max fields — long enough to tune precisely. */
.knobLine {
Expand Down
2 changes: 1 addition & 1 deletion web/src/app/pattern-lab/panels/KnobsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ export default function KnobsPanel() {
</label>
</div>
<div className={dock.panelBody}>
<div className={`${local.controls} ${dock.knobsDock}`}>
<div className={`${local.controls} ${local.knobsDock}`}>
{knobs.map((value, index) => (
<div key={index} className={local.knobLine}>
<div className={local.knobHead}>
Expand Down
89 changes: 89 additions & 0 deletions web/src/app/pattern-lab/panels/PixelPanel.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/* ── PixelPanel: its own classes ─────────────────────────────────────────
Moved out of LabPanels.module.css on 2026-10-25. PixelPanel.tsx and its
toolbars (pixel/PixelToolbars.tsx) are the only files that use them. */

.pixelViewport {
flex: 1 1 auto;
min-height: 0;
overflow: auto;
display: flex;
background: #eee8d8;
padding: 16px;
}

.pixelStage {
position: relative;
flex: 0 0 auto;
/* auto margins center when small AND keep every edge scrollable when zoomed */
margin: auto;
background: repeating-conic-gradient(#c9c2b1 0% 25%, #b5ad9a 0% 50%) 0 0 / 12px 12px;
box-shadow: 0 0 0 1px rgba(23, 21, 18, 0.5);
}

.pixelStage canvas {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
image-rendering: pixelated;
display: block;
}

.pixelStatus {
display: flex;
align-items: center;
gap: 10px;
padding: 4px 8px;
border-top: 1px solid rgba(23, 21, 18, 0.12);
font-family: var(--font-inter), system-ui, sans-serif;
font-size: 11px;
color: rgba(23, 21, 18, 0.6);
text-transform: none;
flex: 0 0 auto;
flex-wrap: wrap;
}

@media (hover: none) {
/* A finger has no Alt key, right button or middle button. On a 390×844
phone this line wrapped the Pixel status bar to 51px, which left the
picture a 372×151 viewport and fitted it at 1×. Without it the viewport
is 372×177, and 2× needs 168. */
.pixelMouseHint {
display: none;
}
}

/* ── Toolbars ── */

.swatchRow {
display: inline-flex;
gap: 3px;
}

.swatch {
width: 16px;
height: 16px;
border: 1px solid rgba(23, 21, 18, 0.3);
padding: 0;
cursor: pointer;
background: none;
}

/* Was `.panelBar .swatch`, when this sat in the same file as `.panelBar`.
Every swatch is in the row, and the row is in a panel bar. Two classes
beat `.panelBar button` in LabPanels.module.css whichever file the
bundle puts first. */
.swatchRow .swatch {
width: 22px;
height: 22px;
min-height: 22px;
padding: 0;
border-radius: 3px;
}

.toolSep {
width: 1px;
align-self: stretch;
background: rgba(23, 21, 18, 0.15);
margin: 0 2px;
}
Loading
Loading