Major update for v1 and tests

This commit is contained in:
khalid@traclabs.com
2026-05-23 03:28:58 -05:00
parent 628a6765f4
commit b1fb6fa3aa
1748 changed files with 27723 additions and 1854 deletions

View File

@@ -1,3 +1,15 @@
/* ──────────────────────────────────────────────────────────────────────────
* LayersPanel — Pawfectly Yours
*
* Class names match the rewritten LayersPanel.jsx (S3 + S24):
* • Each row is a real <button> for keyboard access (S24).
* • A ✓ chip + ring communicates selection without relying on color (S24).
* • Drag handle (⋮⋮) per row enables HTML5 DnD reorder (S3).
* • Bulk-delete affordance appears in the titlebar when ≥2 are selected (S3).
* • Drop indicator (.layers-drop-indicator) is the pink insertion line that
* appears above or below the hovered row during a drag.
* ────────────────────────────────────────────────────────────────────────── */
.layers-empty {
padding: 1rem;
text-align: center;
@@ -5,38 +17,214 @@
font-size: 12px;
}
/* Titlebar — title on the left, bulk-delete on the right. The bulk-delete
* only renders when 2+ rows are selected; in single-select state the bar
* is just the title and looks identical to before. */
.layers-titlebar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.75rem;
gap: 0.5rem;
}
.layers-title {
margin: 0 0 0.75rem 0;
margin: 0;
font-size: 12px;
font-weight: 600;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.4px;
}
.layers-bulk-delete {
appearance: none;
background: transparent;
border: 1px solid #fecaca;
color: #b91c1c;
font-size: 11px;
font-weight: 700;
padding: 0.3rem 0.6rem;
border-radius: var(--radius-pill);
cursor: pointer;
transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}
.layers-bulk-delete:hover {
background: #fef2f2;
border-color: #fca5a5;
color: #991b1b;
}
.layers-list {
display: flex;
flex-direction: column;
gap: 4px;
list-style: none;
padding: 0;
margin: 0;
}
/* Row wrapper: container for the row plus optional drop-indicator lines
* above and below it. Using a wrapper rather than positioning the
* indicator inside the row makes it easier to render before/after the
* row's bbox without margin tricks. */
.layers-row-wrap {
position: relative;
display: flex;
flex-direction: column;
gap: 2px;
}
/* The row itself — a flex row holding [delete] [main button] [grip]. It's
* a div (not a button) because the row contains two distinct interactive
* elements; the row-level click target is .layers-item-main. */
.layers-item {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.75rem;
align-items: stretch;
gap: 0.25rem;
padding: 0;
background: transparent;
border: 1px solid var(--border);
border-radius: var(--radius-sm);
cursor: pointer;
transition: background 0.12s ease, border-color 0.12s ease, opacity 0.12s ease;
}
.layers-item.selected {
background: var(--accent-bg);
border-color: var(--accent);
/* Visible ring around selected rows so colorblind users can see
* selection without depending on the pink fill alone. (S24.) */
box-shadow: 0 0 0 2px rgba(236, 72, 153, 0.18);
}
/* While being dragged, dim the row so the user can see what's happening
* and so the drop indicator lines below stand out by contrast. */
.layers-item.is-dragging {
opacity: 0.4;
}
/* Drag grip — vertical dots glyph on the row's right edge. Decorative for
* keyboard users (tabIndex=-1 in the JSX); actually pickable for mouse
* via HTML5 DnD on the parent .layers-item. */
.layers-item-grip {
display: inline-flex;
align-items: center;
justify-content: center;
width: 18px;
flex-shrink: 0;
color: var(--text-muted);
font-size: 11px;
letter-spacing: -2px;
cursor: grab;
user-select: none;
}
.layers-item.is-dragging .layers-item-grip {
cursor: grabbing;
}
/* Main row button — wraps the check chip, icon, and name. Reset native
* button styling so it fits the row visually but inherits keyboard
* accessibility from the underlying <button>. */
.layers-item-main {
flex: 1;
display: inline-flex;
align-items: center;
gap: 0.5rem;
appearance: none;
background: transparent;
border: none;
padding: 0.5rem 0.5rem;
font: inherit;
text-align: left;
cursor: pointer;
color: inherit;
border-radius: var(--radius-sm);
min-width: 0; /* allow ellipsis truncation inside flex */
}
.layers-item-main:focus-visible {
outline: 2px solid var(--brand-pink);
outline-offset: -2px;
}
/* Selection ✓ chip — rendered as a small circle on the left of selected
* rows. Empty (no glyph, no border) when the row isn't selected so the
* left edge of unselected rows reads cleanly. (S24.) */
.layers-item-check {
display: inline-flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
font-size: 11px;
font-weight: 700;
color: transparent;
flex-shrink: 0;
border-radius: 50%;
transition: background 0.12s ease, color 0.12s ease;
}
.layers-item-check.is-on {
background: var(--brand-pink);
color: #fff;
}
/* Icon slot — fixed 24×24 box on the left of the row's main button.
* Holds one of three things depending on element type:
* - <img> for image and sticker layers (a mini preview of the actual
* pixel content; see renderIcon in LayersPanel.jsx)
* - <svg> for text layers (a T glyph matching the Text tab nav icon)
* - <span> fallback dot for layers we don't have a special icon for
*
* `overflow: hidden` + rounded corners give image thumbnails a polished
* tile look; the soft tint background ensures transparent stickers still
* read against the row. */
.layers-item-icon {
width: 24px;
height: 24px;
flex-shrink: 0;
display: inline-flex;
align-items: center;
justify-content: center;
overflow: hidden;
border-radius: 4px;
background: var(--brand-pink-tint);
color: var(--text-secondary);
}
/* Image / sticker thumbnail. object-fit: contain keeps the artwork's
* aspect ratio without cropping inside the square slot. */
.layers-item-icon-img {
width: 100%;
height: 100%;
object-fit: contain;
display: block;
pointer-events: none;
user-select: none;
}
/* Text-layer 'T' icon. Sized slightly smaller than the slot so it doesn't
* touch the rounded edges; inherits currentColor from the row text so it
* inverts cleanly when the row is selected. */
.layers-item-icon-svg {
width: 16px;
height: 16px;
}
/* Fallback dot for any element type without a custom icon. */
.layers-item-icon-fallback {
font-size: 14px;
line-height: 1;
color: var(--text-muted);
}
/* When the row is selected, switch the icon slot's tint to read against
* the pink selection background — a translucent white tile that lets the
* thumbnail content show through and lifts the SVG T icon's contrast. */
.layers-item.selected .layers-item-icon {
background: rgba(255, 255, 255, 0.65);
}
.layers-item-name {
@@ -47,23 +235,79 @@
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
}
.layers-item-name.selected {
.layers-item.selected .layers-item-name {
color: var(--accent);
font-weight: 600;
}
.layers-item-delete {
width: 24px;
height: 24px;
border: none;
border-radius: var(--radius-sm);
/* Per-row action buttons (Change 4). Sit on the RIGHT side of each
* row as siblings of .layers-item-main (the row's main click target),
* stacked horizontally: [main] [duplicate] [lock] [delete] [grip].
*
* Sizing matches the previous .layers-item-delete (28×row-height) so
* the row maintains its original visual weight; each button is just
* a touch slimmer than the main click target so the three icons
* don't dominate the row. Color states:
* • idle: muted grey, same as old delete X
* • hover: brand-pink tint
* • active (lock when locked): brand-pink fill + white icon —
* gives the user instant feedback that the toggle is on
* • delete hover: red, retaining the destructive-action colour
* signal from the previous design
*/
.layers-item-action {
width: 28px;
flex-shrink: 0;
appearance: none;
background: transparent;
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
color: var(--text-muted);
border-radius: var(--radius-sm);
transition: background 0.12s ease, color 0.12s ease;
}
.layers-item-action:hover {
background: var(--brand-pink-soft);
color: var(--brand-pink-strong);
}
.layers-item-action:focus-visible {
outline: 2px solid var(--brand-pink);
outline-offset: -2px;
}
.layers-item-action.is-active {
background: var(--brand-pink-soft);
color: var(--brand-pink-strong);
}
.layers-item-action.is-active:hover {
background: var(--brand-pink);
color: #fff;
}
/* Delete variant — same base + destructive-red hover so the user
* gets the same colour-coded warning the previous bare ✕ button had. */
.layers-item-action--delete:hover {
background: #fef2f2;
color: #b91c1c;
}
/* Drop indicator — a thin pink line that appears above or below the
* hovered row during a drag, communicating where the dragged row will
* land if dropped. Rendered as a sibling of the row inside the row
* wrapper so its position is unambiguous. (S3.) */
.layers-drop-indicator {
height: 2px;
background: var(--brand-pink);
border-radius: 1px;
margin: 0 4px;
box-shadow: 0 0 0 2px rgba(236, 72, 153, 0.18);
}