/* catalog.css — the work catalog and the draggable rail. =========================================================================== A separate stylesheet rather than more of app.css: these are two new, self-contained surfaces, and keeping them out of an 8,600-line file makes them reviewable. Every colour here is one of app.css's existing tokens, so both themes and the light/dark switch work with nothing added. =========================================================================== */ /* ═══════════════════════════════════════════════════════ draggable rail ══ */ .rail-split { position: fixed; top: var(--bench-strip-h, 56px); bottom: 0; /* Centred ON the boundary, so the grab area straddles the line rather than sitting to one side of it and feeling offset. */ left: calc(var(--bench-rail, 392px) - 5px); width: 11px; z-index: 36; cursor: col-resize; display: flex; align-items: center; justify-content: center; background: transparent; /* No transition on `left`: it tracks the pointer, and easing a drag makes the handle lag behind the cursor. */ } .rail-split[hidden] { display: none; } /* Visible AT REST, and this is the whole point of the control. The first version started at opacity 0 and only appeared on hover. Measured in the browser: `getComputedStyle(.rail-split-grip).opacity === "0"` with the split open and the pointer elsewhere — so the boundary between the conversation and the bench looked exactly like the fixed 1px border it used to be, and nothing on screen said it could move. A handle you have to already know about is not a handle. So it shows: a short grip on the seam, in --line-bold — one step up from the .cockpit border-right it sits on, present enough to read as a grab point, quiet enough not to become a third vertical rule in the layout. It grows and takes ink on hover, focus and drag, which is where the affordance should be loud. */ .rail-split-grip { width: 3px; height: 34px; border-radius: 3px; background: var(--line-bold); opacity: 1; transition: opacity var(--t-fast, .12s) var(--ease, ease), height var(--t-fast, .12s) var(--ease, ease), background var(--t-fast, .12s) var(--ease, ease); } .rail-split:hover .rail-split-grip, .rail-split:focus-visible .rail-split-grip, body.is-railsplitting .rail-split-grip { height: 46px; background: var(--ink-soft); } @media (prefers-reduced-motion: reduce) { .rail-split-grip { transition: none; } } .rail-split:focus-visible { outline: 2px solid var(--ink-strong); outline-offset: -2px; } /* While dragging, kill text selection and stop the pointer being captured by anything underneath — including the Mol* canvas, which otherwise starts rotating the model the moment the cursor crosses it. */ body.is-railsplitting { user-select: none; cursor: col-resize; } body.is-railsplitting .workspace, body.is-railsplitting .cockpit { pointer-events: none; } @media (max-width: 900px) { .rail-split { display: none; } } /* ═════════════════════════════════════════════════════════════ catalog ══ */ .cat { position: fixed; inset: 0; z-index: 90; display: flex; align-items: center; justify-content: center; padding: 32px 20px; } .cat[hidden] { display: none; } body.cat-open { overflow: hidden; } .cat-backdrop { position: absolute; inset: 0; background: rgba(6, 8, 12, 0.55); backdrop-filter: blur(2px); animation: catFade 160ms var(--ease, ease); } @keyframes catFade { from { opacity: 0; } to { opacity: 1; } } .cat-panel { position: relative; width: min(1080px, 100%); max-height: min(820px, 100%); display: flex; flex-direction: column; background: var(--bg-card); border: 1px solid var(--line-strong); border-radius: var(--r-4, 6px); box-shadow: 0 24px 64px rgba(0, 0, 0, .34); overflow: hidden; animation: catRise 200ms var(--ease, ease); } @keyframes catRise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } } @media (prefers-reduced-motion: reduce) { .cat-backdrop, .cat-panel { animation: none; } } .cat-head { display: flex; align-items: center; gap: 12px; padding: 16px 18px 12px; border-bottom: 1px solid var(--line); flex: 0 0 auto; } .cat-title { margin: 0; font-size: 16px; font-weight: 600; letter-spacing: -0.01em; color: var(--ink-strong); } .cat-x { margin-left: auto; background: none; border: none; cursor: pointer; font-size: 22px; line-height: 1; color: var(--ink-faint); padding: 2px 8px; font-family: inherit; } .cat-x:hover { color: var(--ink-strong); } .cat-controls { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; padding: 12px 18px; border-bottom: 1px solid var(--line); flex: 0 0 auto; } .cat-q { flex: 1 1 260px; min-width: 0; padding: 8px 12px; font-size: 13.5px; font-family: inherit; color: var(--ink); background: var(--bg-app); border: 1px solid var(--line-strong); border-radius: 999px; } .cat-q::placeholder { color: var(--ink-faint); } .cat-q:focus { outline: none; border-color: var(--ink-soft); } .cat-seg { display: flex; gap: 2px; flex: 0 0 auto; } .cat-segb { padding: 6px 13px; font-size: 12px; font-family: inherit; cursor: pointer; background: none; border: 1px solid transparent; border-radius: 999px; color: var(--ink-soft); } .cat-segb:hover { color: var(--ink-strong); } .cat-segb.is-on { color: var(--ink-strong); border-color: var(--line-strong); background: var(--bg-app); } .cat-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 4px 18px 20px; } .cat-body:focus { outline: none; } .cat-sec { margin: 18px 0 10px; font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.13em; text-transform: uppercase; color: var(--ink-faint); font-weight: 500; display: flex; align-items: baseline; gap: 7px; } .cat-sec span { color: var(--ink-disabled); } .cat-grid { display: grid; gap: 10px; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); /* Equal-height rows: chip counts differ per run, and ragged card bottoms make a grid read as broken rather than varied. */ grid-auto-rows: 1fr; } .cat-empty { color: var(--ink-faint); font-size: 13.5px; padding: 40px 4px; text-align: center; } /* ── card ───────────────────────────────────────────────────────────────── */ .cat-card { display: grid; grid-template-columns: 104px 1fr; /* 1fr on the content row, auto on the actions row: with equal-height grid rows this pins Rename/Delete to the bottom edge of every card, so the buttons line up across the row however many chips a run carries. */ grid-template-rows: 1fr auto; grid-template-areas: "thumb main" "acts acts"; gap: 12px; padding: 12px; border: 1px solid var(--line); border-radius: var(--r-4, 6px); background: var(--bg-app); position: relative; transition: border-color var(--t-fast, .12s) var(--ease, ease); } .cat-card:hover { border-color: var(--line-strong); } .cat-card.is-sel { border-color: var(--ink-soft); } .cat-card.is-current { border-color: var(--ink-soft); } .cat-card.is-current::before { content: "Open now"; position: absolute; top: -1px; right: 10px; font-family: var(--font-mono); font-size: 9px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--bg-app); background: var(--ink-soft); padding: 1px 6px; border-radius: 0 0 3px 3px; } .cat-card.is-busy { opacity: .55; pointer-events: none; } /* ── the fold ───────────────────────────────────────────────────────────── */ .cat-thumb { grid-area: thumb; position: relative; width: 104px; height: 84px; border-radius: 4px; background: var(--bg-subtle); border: 1px solid var(--line); display: flex; align-items: center; justify-content: center; overflow: hidden; flex: none; } .cat-canvas { width: 100%; height: 100%; display: block; } .cat-thumb-msg { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; text-align: center; padding: 6px; font-family: var(--font-mono); font-size: 8.5px; line-height: 1.35; letter-spacing: 0.04em; color: var(--ink-disabled); } /* The accession is the receipt: it says WHICH model this is, so the picture is checkable rather than decorative. */ .cat-acc { position: absolute; left: 0; right: 0; bottom: 0; font-family: var(--font-mono); font-size: 8px; letter-spacing: 0.06em; color: var(--ink-faint); background: color-mix(in srgb, var(--bg-subtle) 84%, transparent); padding: 1px 4px; text-align: center; } .cat-thumb[data-sc="idle"] .cat-canvas, .cat-thumb[data-sc="loading"] .cat-canvas { opacity: 0; } .cat-thumb[data-sc="ok"] .cat-thumb-msg { display: none; } .cat-thumb[data-sc="loading"] .cat-thumb-msg::after { content: "Folding…"; } .cat-thumb[data-sc="error"] .cat-thumb-msg::after { content: "Structure unavailable"; } .cat-thumb[data-sc="ok"] .cat-acc { display: block; } .cat-thumb[data-sc="none"] .cat-acc { display: none; } /* A construct has no fold to show, so its tile carries a mark instead of a picture. Dropping the border is the point: a 1px frame around a small glyph on an empty ground is the shape of a broken image, and that is what it was being read as. Without the frame it reads as an icon beside a title, which is what it is. */ .cat-thumb--cx { color: var(--ink-faint); border-color: transparent; background: var(--bg-subtle); } .cat-thumb--cx svg { width: 34px; height: 34px; } .cat-card--cx:hover .cat-thumb--cx { color: var(--ink-soft); } /* ── body ───────────────────────────────────────────────────────────────── */ .cat-main { grid-area: main; min-width: 0; display: flex; flex-direction: column; gap: 5px; } .cat-open { background: none; border: none; padding: 0; text-align: left; cursor: pointer; font-family: inherit; color: var(--ink-strong); min-width: 0; } .cat-name { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; font-size: 13.5px; font-weight: 600; line-height: 1.35; letter-spacing: -0.005em; } .cat-open:hover .cat-name { text-decoration: underline; text-underline-offset: 2px; } .cat-open:focus-visible { outline: 2px solid var(--ink-strong); outline-offset: 2px; border-radius: 2px; } .cat-flag { align-self: flex-start; font-family: var(--font-mono); font-size: 9px; letter-spacing: 0.09em; text-transform: uppercase; color: var(--warning, var(--ink-strong)); border: 1px solid currentColor; border-radius: 3px; padding: 1px 5px; } .cat-chips { display: flex; flex-wrap: wrap; gap: 4px; } .cat-chip { font-family: var(--font-mono); font-size: 9.5px; letter-spacing: 0.04em; padding: 2px 6px; border-radius: 3px; background: var(--bg-subtle); color: var(--ink-soft); border: 1px solid var(--line); } .cat-chip.is-key { color: var(--ink-strong); border-color: var(--line-strong); } .cat-chip.is-soft { color: var(--ink-faint); background: none; } .cat-meta { font-family: var(--font-mono); font-size: 10px; color: var(--ink-faint); letter-spacing: 0.02em; } .cat-note { margin: 2px 0 0; font-size: 11.5px; color: var(--danger); } /* ── actions ────────────────────────────────────────────────────────────── */ .cat-acts { grid-area: acts; display: flex; gap: 6px; justify-content: flex-end; padding-top: 8px; margin-top: 2px; border-top: 1px solid var(--line); } .cat-act { font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.06em; padding: 4px 10px; cursor: pointer; background: none; color: var(--ink-soft); border: 1px solid var(--line-strong); border-radius: 999px; } .cat-act:hover { color: var(--ink-strong); border-color: var(--ink-soft); } .cat-act.is-danger { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 40%, var(--line)); } .cat-act.is-danger:hover { border-color: var(--danger); } .cat-act:focus-visible { outline: 2px solid var(--ink-strong); outline-offset: 1px; } .cat-rename { display: flex; gap: 6px; align-items: center; } .cat-rename-i { flex: 1 1 auto; min-width: 0; padding: 5px 9px; font-size: 12.5px; font-family: inherit; color: var(--ink); background: var(--bg-card); border: 1px solid var(--line-strong); border-radius: 4px; } .cat-rename-i:focus { outline: none; border-color: var(--ink-soft); } .cat-confirm { grid-column: 1 / -1; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 8px; padding: 9px 10px; border: 1px solid color-mix(in srgb, var(--danger) 34%, var(--line)); border-radius: 4px; font-size: 12px; color: var(--ink); } .cat-confirm span { flex: 1 1 180px; min-width: 0; } @media (max-width: 620px) { .cat { padding: 0; } .cat-panel { max-height: 100%; height: 100%; border-radius: 0; border: none; } .cat-grid { grid-template-columns: 1fr; } }