314 lines
9.2 KiB
CSS
314 lines
9.2 KiB
CSS
/* ──────────────────────────────────────────────────────────────────────────
|
||
* 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;
|
||
color: var(--text-muted);
|
||
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;
|
||
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: stretch;
|
||
gap: 0.25rem;
|
||
padding: 0;
|
||
background: transparent;
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-sm);
|
||
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 {
|
||
flex: 1;
|
||
font-size: 12px;
|
||
color: var(--text-primary);
|
||
font-weight: 400;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
min-width: 0;
|
||
}
|
||
|
||
.layers-item.selected .layers-item-name {
|
||
color: var(--accent);
|
||
font-weight: 600;
|
||
}
|
||
|
||
/* 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;
|
||
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);
|
||
}
|