feat(sacred-valley): hybrid free/snap canvas + blank & blackflame cards (2.8.0)

Replace masonry grid with an absolute-positioned 12-col canvas: drag to
move, corner to resize, per-card free/overlap toggle (Alt = no-snap).
Geometry persisted (migration 027: dashboard_layout.geom + extras).
Two new addable decorative cards: blank spacer + animated blackflame
(canvas particle flame). Old layout auto-migrates by flow-placement.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
root
2026-06-09 22:33:45 +10:00
parent e8f655ed27
commit 600057582e
11 changed files with 552 additions and 144 deletions

View File

@@ -382,14 +382,9 @@ ul.plain li:last-child { border-bottom: none; }
/* reserved for a future agent-output phase — unused now:
--hue-dross: #ff4f2e; --hue-yerin: #c45a4a; --hue-orthos: #6fa86a; */
}
#sv-cards { display: grid; grid-template-columns: repeat(12, 1fr); gap: 16px; align-items: start; grid-auto-rows: 8px; grid-auto-flow: row dense; }
.sv-card { grid-column: span 6; } /* fallback; svCard sets an inline span (112) */
@media (max-width: 700px) { #sv-cards { grid-template-columns: 1fr; } .sv-card { grid-column: 1 / -1 !important; } }
.sv-ed-span { display: inline-flex; align-items: center; gap: 3px; }
.sv-ed-step { width: 18px; height: 20px; border: 1px solid var(--border); background: transparent; color: var(--muted);
border-radius: 3px; font-size: 14px; line-height: 1; cursor: pointer; padding: 0; }
.sv-ed-step:hover { color: var(--accent); border-color: var(--accent-dim); }
.sv-span-val { font-family: var(--font-mono); font-size: 12px; color: var(--text); min-width: 14px; text-align: center; }
/* Hybrid canvas: cards are absolutely placed (JS sets left/top/width/height in
12-col grid units); the board grows to fit its content. See sacred_valley.js. */
#sv-cards { position: relative; width: 100%; min-height: 200px; }
.sv-card {
position: relative; border: 1px solid #2c242a; border-radius: 10px; padding: 16px 18px;
@@ -407,8 +402,11 @@ ul.plain li:last-child { border-bottom: none; }
border-color: #4a2c28; transform: translateY(-2px);
box-shadow: 0 8px 28px rgba(0,0,0,.45), inset 0 0 46px rgba(255,79,46,.06), 0 0 0 1px rgba(255,79,46,.10);
}
.sv-card.dragging { opacity: .5; }
.sv-card.drag-over { border-color: var(--accent); }
.sv-card.dragging { transition: none; box-shadow: 0 16px 44px -12px #000, 0 0 0 1px var(--accent-dim); }
.sv-card.free { box-shadow: 0 0 0 1px var(--accent-dim), 0 10px 30px -12px #000; }
#sv-cards.editing .sv-card { transition: none; }
#sv-cards.editing .sv-card:hover { transform: none; }
.sv-card-body { height: 100%; overflow: auto; }
.sv-card-title {
font-family: var(--font-display); font-size: 13px; letter-spacing: .16em; text-transform: uppercase;
color: var(--text); padding-bottom: 7px; margin-bottom: 12px;
@@ -626,18 +624,38 @@ body.drawer-open #scrim { opacity: 1; pointer-events: auto; }
background: rgba(10,10,14,.88); border: 1px solid var(--border); border-radius: 6px; padding: 3px 5px; }
#sv-cards.editing .sv-card-edit { display: flex; }
#sv-cards.editing .sv-card { outline: 1px dashed var(--accent-dim); }
.sv-grip { cursor: grab; color: var(--muted); font-size: 14px; padding: 0 2px; user-select: none; }
.sv-grip { cursor: grab; color: var(--muted); font-size: 14px; padding: 0 2px; user-select: none; touch-action: none; }
.sv-grip:active { cursor: grabbing; }
.sv-ed-sizes { display: flex; gap: 2px; }
.sv-ed-size, .sv-ed-hide { width: 20px; height: 20px; border: 1px solid var(--border); background: transparent;
border-radius: 3px; font-size: 11px; cursor: pointer; padding: 0; line-height: 1; }
.sv-ed-size { color: var(--muted); }
.sv-ed-size:hover { color: var(--accent); border-color: var(--accent-dim); }
.sv-card[data-size="s"] .sv-ed-size[data-s="s"],
.sv-card[data-size="m"] .sv-ed-size[data-s="m"],
.sv-card[data-size="l"] .sv-ed-size[data-s="l"] { background: var(--accent-dim); color: var(--text); border-color: var(--accent); }
.sv-ed-hide { color: var(--bad); font-size: 12px; }
.sv-ed-free, .sv-ed-hide { width: 20px; height: 20px; border: 1px solid var(--border); background: transparent;
border-radius: 3px; font-size: 12px; cursor: pointer; padding: 0; line-height: 1; }
.sv-ed-free { color: var(--muted); }
.sv-ed-free:hover { color: var(--accent); border-color: var(--accent-dim); }
.sv-card.free .sv-ed-free { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }
.sv-ed-hide { color: var(--bad); }
.sv-ed-hide:hover { background: var(--bad); color: var(--bg); }
/* resize handle — bottom-right corner, edit mode only */
.sv-resize { display: none; position: absolute; right: 3px; bottom: 3px; width: 16px; height: 16px; z-index: 4;
cursor: nwse-resize; touch-action: none; border-radius: 0 0 9px 0;
background: linear-gradient(135deg, transparent 45%, var(--muted) 45% 55%, transparent 55%,
transparent 62%, var(--muted) 62% 72%, transparent 72%); opacity: .6; }
.sv-resize:hover { opacity: 1; }
#sv-cards.editing .sv-resize { display: block; }
.sv-tray-hint { font-family: var(--font-ui); font-size: 11px; margin-left: 6px; }
/* decorative cards (blank spacer / blackflame) — no chrome padding, full-bleed body */
.sv-card-decor { padding: 0; overflow: hidden; }
.sv-card-decor .sv-card-body { position: absolute; inset: 0; padding: 0; overflow: hidden; }
.sv-card-decor:hover { transform: none; }
.sv-blank { width: 100%; height: 100%;
background-image: repeating-linear-gradient(45deg, rgba(255,255,255,.025) 0 9px, transparent 9px 18px); }
.sv-flame-canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; pointer-events: none; }
.sv-flame-crest { position: absolute; top: 46%; left: 50%; width: 22%; aspect-ratio: 1; transform: translate(-50%,-50%);
border-radius: 50%; pointer-events: none;
background: radial-gradient(circle at 50% 45%, #000 38%, #0a0a10 60%, transparent 72%);
box-shadow: 0 0 26px 10px #000, 0 0 60px 18px rgba(255,79,46,.13); }
.sv-flame-label { position: absolute; left: 0; right: 0; bottom: 14px; text-align: center; pointer-events: none;
font-family: var(--font-display); letter-spacing: .42em; text-indent: .42em; text-transform: uppercase;
font-size: 14px; color: #f4ece9; text-shadow: 0 0 18px rgba(255,79,46,.55), 0 0 4px #000; }
#sv-tray { flex-wrap: wrap; align-items: center; gap: 8px; margin: 2px 0 18px; padding: 10px 12px;
border: 1px dashed var(--border); border-radius: 8px; }