* feat(history): concept heading and summary The Prompt history page takes the approved mock's heading and summary surface (docs/concepts/workspace/app.js renderHistory a591-598, #249 F1): - Heading description is "Prompt history · one positive, one shared gallery"; the actions stay "← Prompt library", "Edit metadata" and button.send "Send to ComfyUI" (aria-label "Send prompt pair to ComfyUI: <title>"), on one row at 1600. Send is disabled only while the selected pair's roles are loading (D24). - The summary is article.surface > span.chip category, the positive and selected negative prompt blocks, then .card-meta "N tracked runs · N images · N negative variants". Tracked runs come from the recorded history window's recorded_run_count (hidden until a read completes, kept while a refresh is in flight); images and variants from the saved positive's image_count and variant_count. - The summary's copy/send actions are named after the prompt, as in the concept ("Copy positive prompt: <title>"); mountPromptBlocks gains setTitle so an in-place metadata edit renames them without a remount. - mountRecordedHistory gains an optional onRecordedCount callback. Selector mapping: - .library-surface (history summary) -> .library-history article.surface - .library-surface .library-chip -> article.surface > span.chip - "Copy|Send <role> prompt[ to ComfyUI]" in "Saved prompt texts" -> "...: <title>" - heading p.muted "One positive prompt. Reusable negative variants. One shared gallery." -> "Prompt history · one positive, one shared gallery" - Unchanged: region "Saved prompt texts", .library-variants, .library-shared-gallery, .recorded-history (still .library-surface). Tests: workspace-history "history heading actions stay on one row at 1600 and are enabled after load" and "summary meta uses recorded runs" (both failed before), a prompt-blocks setTitle unit, and the mapped selectors in workspace-visual, workspace-library-editor and the canonical comparison. * feat(history): concept negative variants panel The history's Negative variants panel is the concept's (app.js a598, style.css 1059-1154): section.surface.timeline-panel with the panel heading (h2, p.small "Choose a red dot to change the negative and filter this gallery."), row actions Compare negatives (aria-pressed; its section is Task F3), + Add negative and the approved Show archived; a .timeline of .positive-lane and .variant-lane rows (--lane-offset 38 + 47i) with the "−" .variant-dot, "<name> · N runs" and a .run-dot when the pair's run_count > 1; and .timeline-lower with the legend and All images for this positive. D26: All images for this positive opens the concept dialog with the embedded, bounded (Load more) media gallery instead of filtering the shared gallery. The run dot opens Recorded run for the pair's newest recorded execution, else a Repeat run dialog with the pair's newest linked image. Backing data: - library variants carry prompt_pairs.run_count; - GET /prompt_manager/library/{positive_id}/history/pairs/{pair_id}/newest reads the newest run in the positive's exact raw-text scope whose current-epoch saved-pair hint still resolves to that pair (the get_history_run hint rule), inside the fenced history snapshot. - workspace-dialogs gains openContentDialog (onClose releases the body). - mountRecordedHistory returns openRun. D45 (new deviations): - a dot is named "Select negative <title>" when no other loaded lane has that title, else "Select negative variant N", so names stay unique; - the bounded "N of M saved negative variants" count stays a visually hidden status; "Saved combinations, not an execution timeline." is gone; - archived lanes (#235) are concept lanes marked .library-variant-archived at .6 emphasis with their Restore button; coarse dots keep a 47px target; the heading, legend row and All images button wrap at 390px and 200% zoom. Retired CSS: .library-branches, .library-positive-lane, .library-variant-lane, .library-variant-dot, .library-variants, .library-panel-heading, .library-variant-footer, .library-variant-summary. Selector mapping: - .library-variants -> .library-history .timeline-panel (region "Saved negative variants" unchanged) - .library-branches -> .timeline; .library-positive-lane -> .positive-lane - .library-variant-lane -> .variant-lane; .library-variant-dot -> .variant-dot - .library-variant-archived and .library-variant-restore unchanged - .library-variant-footer / button:last-child -> .timeline-lower / button - .library-variants > p.muted -> .timeline-panel .panel-heading .small - "Select negative variant N" -> "Select negative <title>" for titled pairs (history, native library, inspector, visual and real-host fixtures); untitled pairs unchanged - lane text "· N linked image(s)" -> "· N run(s)" - All images for this positive: aria-pressed filter -> dialog "All images for this positive"; tests that used it for the whole gallery select the lane linking the image, or read the dialog - variant panel row actions gain Compare negatives first Tests: new workspace-history "variants panel matches the concept", "run dot appears only for pairs with more than one run and opens its run", "all images dialog lists every image for the positive"; new workspace-library-editor "show archived and restore still work" (all four failed before the change). Python: variants carry run_count, newest pair run (DB and route). Unit: content dialog. The history fixture counts two canonical pairs and links one image to Clean geometry. * feat(history): compare negative variants Compare negatives now opens the concept's comparison (app.js a599-616, style.css 1155-1179) as a section.surface right after the timeline panel: h2 "Compare negative variants", p "Baseline compared with <name>. Choose another red dot to change the comparison.", a .compare-grid of two .compare-item negative blocks (baseline, selected), p.small "Added terms", then span.diff-added "+ term" per added term or p.small "No added terms.". Choosing another red dot updates the same section in place; pressing Compare negatives again removes it. D25: the baseline is the oldest active variant; with no selection the selected side falls back to it, as the concept's v does. The pure addedTerms(base, selected) in history-state.mjs is the concept's rule: comma split, trim, drop empty terms, exact case-sensitive difference that keeps the selected order and repeats. D46 (new deviations): - lanes are newest first, so the baseline is the lowest-id active variant, not the first lane; while the variant page has more, the oldest is not loaded and the section says "Load more negative variants to reach the baseline." instead of guessing; - the two blocks are named "Baseline" and "Selected" (regions and copy/send actions), so no name repeats on the page; - Compare negatives is disabled when there is no active variant. Selector mapping: none changed; the section is new (.library-history .timeline-panel + section.surface). Tests: unit "added terms split, trim and exact-diff like the concept"; workspace-history "compare toggles, follows the selected dot and stacks at 390" and "compare waits for the page holding the oldest active negative" (all failed before the change). * test(history): guard the newest-pair route and record D45/D46 TESTING.md lists the Prompt history page's spec decisions (D24-D26) and its new deviations D45 and D46 beside D23 and D40-D44, notes under D46 that production opens on the newest lane while the concept opens on its baseline, adds GET /prompt_manager/library/{positive_id}/history/pairs/ {pair_id}/newest to the history route list with its guards, and documents the detail variants' run_count. Tests: the newest-pair read skips a stale reference_epoch row, stops at 16 stale-hint candidates (17 newer stale rows hide an older valid run and report {run: null}), and rejects a negative-only or deleted positive; the route answers 404 for an ineligible positive, 503 storage-unavailable for storage failures without leaking details, and 409 history-changed for a restore during the snapshot. * fix(history): run-dot touch target, unique dot names and dialog cleanup - D45: under pointer: coarse the 12px run dot gets a 44px ::after target, and while a run dot shows the lane text wraps within 139px before it. - D45: dot names compare trimmed titles and never use a title that reads like a fallback "variant N", so names stay unique. - All images and Repeat run share one dialog-scope slot: each open replaces the previous scope instead of adding a cleanup per open. - A run-dot lookup that settles after the view went stale opens nothing. - Retire the unused .library-main .library-chip rule and the stale "Saved combinations" comparison note. Tests (workspace-history, Chromium and touch): the coarse run-dot target and lane width; a failed newest lookup shows the toast and Repeat run; Recorded run that cannot open (recorded history failed) falls back to Repeat run; an archived pair's Repeat run reads archive=archived; Compare negatives is disabled with no active variant; dot names stay unique. * fix(workspace): one inert handle for aborted history and prompt-block mounts An aborted parent no longer gets a separate never-called stub per method: mountRecordedHistory returns its hoisted openRun (it answers false before reading later state), and mountPromptBlocks returns one shared no-op for update, setState, setTitle and dispose. This keeps the history.mjs and prompt-blocks.mjs function-coverage floors (98) without lowering them. Tests: test:coverage (Chromium and touch, 922 workflows) passes for all 61 files; it failed before on history.mjs 94.44 and prompt-blocks.mjs 93.75 function coverage.
230 lines
19 KiB
CSS
230 lines
19 KiB
CSS
/* Production overlay on the verbatim concept base (concept.css, D1 of #249). Only production-only states and recorded
|
||
deviations live here; a rule on a selector the concept also styles carries its decision marker. */
|
||
|
||
/* Production-only shell states. */
|
||
main:focus-visible { outline: 2px solid var(--ca-focus); outline-offset: 4px; }
|
||
/* The page title takes focus after a save or route change (concept app.js 1168); a keyboard user sees where it went. */
|
||
#page-title:focus { outline: none; }
|
||
#page-title:focus-visible { outline: 2px solid var(--ca-focus); outline-offset: 4px; }
|
||
/* D28: the sidebar shows no connection state, so its dot is neutral instead of the concept's demo green. */
|
||
.connection i { background: var(--ca-muted); }
|
||
/* D31: bounded rendering reads the grid's real width, so at extreme zoom the one-column shell must not grow to the
|
||
sidebar's min-content (the concept's `1fr`); the column is capped at the viewport as production's shell was. */
|
||
@media (max-width: 950px) {
|
||
.app-shell { grid-template-columns: minmax(0, 1fr); /* D31 */ }
|
||
}
|
||
@media (forced-colors: active) {
|
||
nav a[aria-current="page"] { border: 2px solid Highlight; color: LinkText; }
|
||
}
|
||
|
||
/* Overview: real library statistics and the legacy tool cards inside a concept `.surface`. */
|
||
.stat-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; margin: 0 0 26px; }
|
||
.stat { padding: 22px; background: var(--ca-surface); border: 1px solid var(--line); border-radius: 7px; }
|
||
.stat dt { color: var(--ca-muted); font-size: 12px; }
|
||
.stat dd { margin: 8px 0 0; font-size: 32px; font-weight: 600; }
|
||
.overview-stats > [role="status"], .overview-stats > [role="alert"], .empty-state { padding: 18px 0; color: var(--ca-muted); }
|
||
.overview-stats > [role="alert"] { color: var(--ca-error-text); }
|
||
.overview-stats > button { margin-bottom: 24px; }
|
||
.tool-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 1fr)); gap: 12px; margin-top: 22px; }
|
||
.tool-card { border: 1px solid var(--line); background: var(--ca-field); border-radius: 8px; padding: 20px; display: flex; flex-direction: column; gap: 10px; }
|
||
.tool-card p { flex: 1; margin: 0 0 12px; }
|
||
.workspace-link { display: inline-flex; align-items: center; min-height: 44px; padding: 8px 0; font-size: 12px; overflow-wrap: anywhere; }
|
||
.handoff .workspace-link { margin-top: 12px; }
|
||
@media (max-width: 700px) {
|
||
.stat-grid { gap: 8px; }
|
||
.stat { padding: 16px; }
|
||
.stat dd { font-size: 26px; }
|
||
}
|
||
|
||
/* Tags & categories (#245): real vocabulary has unbounded names and bounded lists, which the concept's short samples
|
||
never show. Proven by the 390px layout and long-name cases in workspace-vocabulary.spec.mjs. */
|
||
.vocabulary-main :where(.two-col, .surface, .table-wrap, .operation > div) { min-width: 0; }
|
||
.vocabulary-main .operation h3 { overflow-wrap: anywhere; }
|
||
/* Long tag names wrap inside the Tag cell so the Actions column keeps its intrinsic width; on two-column screens the
|
||
minimum keeps the concept's longest sample ("architecture", about 5em) on one line. */
|
||
.vocabulary-main td:first-child { overflow-wrap: anywhere; min-width: calc(6em + 20px); }
|
||
/* Tags & categories (#245 decision 5): bounded lists continue with Load more; never a scroll anchor, so appended rows push the end away. */
|
||
.vocabulary-main .vocabulary-continuation { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; padding-block: 15px 0; overflow-anchor: none; }
|
||
.vocabulary-main .vocabulary-continuation p { margin: 0; flex: 1 1 200px; font-size: 12px; color: var(--ca-muted); }
|
||
.vocabulary-main .vocabulary-continuation .vocabulary-error { color: var(--ca-error-text); }
|
||
@media (max-width: 700px) {
|
||
/* One column: the Tag column has no fixed minimum and Actions asks only for its stacked width (the concept's 390px
|
||
rows stack Browse over Rename / merge), so a wider fallback font wraps the name instead of hiding an action. */
|
||
.vocabulary-main td:first-child { min-width: 0; }
|
||
.vocabulary-main td .row-actions { width: min-content; margin-inline-start: auto; }
|
||
}
|
||
|
||
/* Images (#249 C2). Production-only continuation states the concept never shows: a failed page and a load in flight. */
|
||
.infinite-continuation [role="alert"] { color: var(--ca-error-text); }
|
||
.infinite-continuation button[aria-disabled="true"] { opacity: .55; cursor: wait; }
|
||
/* #249 D14: the scope chip names real sources and folders, which can be far longer than "All output folders". */
|
||
.filter-row span.chip { max-width: 100%; min-width: 0; overflow-wrap: anywhere; }
|
||
/* #249 D32: at extreme zoom the 390px shell is narrower than the concept's three layout buttons; they wrap instead of
|
||
scrolling the page sideways (they never wrap while they fit). */
|
||
.segmented { flex-wrap: wrap; max-width: 100%; }
|
||
/* #249 D32: likewise the continuation's text and Load more wrap instead of pushing the button out of the column. */
|
||
.infinite-continuation { flex-wrap: wrap; }
|
||
|
||
/* #249 D18: the concept zooms a hovered card image (1.03 over 0.2s; its reduced-motion rule makes that instant). A touch
|
||
device has no real hover, so a tap must not leave the zoom stuck. */
|
||
@media (hover: none) { .image-open:hover img { transform: none; /* D18 */ } }
|
||
|
||
/* Images cards (#249 C3). #249 D3: bounded rendering mounts only the visible cards and places each one itself, in every
|
||
layout (the concept places only masonry cards), so the grid reserves the collection's height and every card is
|
||
absolute. Columns and gap still come from the concept's `.image-grid` tracks. */
|
||
.image-grid { position: relative; /* D3 */ }
|
||
.image-grid > .media-card { position: absolute; }
|
||
/* #249 D3: a list row's info keeps its own height so it can be measured; centred, it looks as the stretched concept row.
|
||
#249 D31: it may also shrink below a long nowrap title (the h3 ellipsis then applies, as in the grid). */
|
||
.list-grid .media-info { align-self: center; min-width: 0; /* D3, D31 */ }
|
||
/* #249 D31: at extreme zoom a list row is narrower than the concept's 160px art; the art yields so the info stays inside. */
|
||
.list-grid .media-art { width: min(160px, 45%); /* D31 */ }
|
||
/* #249 D31: at extreme zoom the subtitle wraps inside its column instead of running under the Inspect button. */
|
||
.media-info p { overflow-wrap: anywhere; /* D31 */ }
|
||
/* #249 D31: below the narrowest bounded card (120px, reached only at extreme zoom; images.mjs marks the grid) the info
|
||
row stacks its text above Inspect, so neither the title nor the subtitle collapses to nothing beside the button. */
|
||
.narrow-cards .media-info { flex-wrap: wrap; }
|
||
.narrow-cards .media-info > div { flex: 1 0 100%; }
|
||
/* A list row beside its art keeps room for Inspect. */
|
||
.narrow-cards.list-grid .media-art { width: 40%; }
|
||
.narrow-cards.list-grid .media-info { padding-inline: 4px; }
|
||
|
||
/* Legacy video media (the mock does not drop it) takes the concept's image rules in each layout. */
|
||
.image-open video { display: block; width: 100%; height: auto; aspect-ratio: 1.33; object-fit: cover; }
|
||
.masonry-grid .image-open video { aspect-ratio: var(--media-ratio); object-fit: contain; }
|
||
.list-grid .image-open video { height: 95px; aspect-ratio: auto; }
|
||
|
||
/* Images viewer (#249 C6): the concept's `#viewer-dialog` shell (s1008–1058, ≤700 s1456–1474). #249 D34: real media,
|
||
titles and states that the concept's one demo image never shows. */
|
||
/* #249 D34: a real title can wrap (long linked titles and filenames). Above 700px the dialog stacks its header and the
|
||
concept's `calc(100% - 72px)` layout in a column, so a taller header shrinks the layout instead of pushing the toolbar
|
||
out of view; a one-line title gives the concept's boxes. */
|
||
@media (min-width: 701px) {
|
||
.ca-tools #viewer-dialog[open] { display: flex; flex-direction: column; /* D34 */ }
|
||
}
|
||
/* #249 D34: under 200% page zoom the concept's 94dvh outgrows the viewport; the dialog keeps within it. */
|
||
.ca-tools #viewer-dialog { max-height: min(94dvh, 94%); /* D34 */ }
|
||
#viewer-dialog .dialog-header > div { min-width: 0; }
|
||
#viewer-title { overflow-wrap: anywhere; /* D34 */ }
|
||
#viewer-dialog, #viewer-dialog::backdrop { overscroll-behavior: contain; }
|
||
#viewer-details { overscroll-behavior: contain; /* D34 */ }
|
||
/* #249 D34: Viewer.js's stylesheet styles every `.viewer-toolbar` (`position: absolute; bottom: 0; left: 0; right: 0`),
|
||
which lifts the concept's toolbar out of `.viewer-left` to span the whole dialog over the details' last lines. It stays
|
||
in the column under the stage, where the concept's `.viewer-left` flex column and border place it. */
|
||
#viewer-toolbar { position: static; /* D34 */ }
|
||
/* #249 D34: Viewer.js's own toolbar stays hidden (`toolbar: false`); the concept's `.viewer-toolbar` rule would show it. */
|
||
.viewer-container .viewer-hide { display: none; /* D34 */ }
|
||
/* #249 D34: the stage is focusable for arrow-key navigation; a video plays with native controls, an image without
|
||
Viewer.js shows itself (the concept hides it under the viewer), and a load failure covers the stage with Retry. */
|
||
#viewer-stage:focus-visible { outline: 2px solid var(--ca-focus); outline-offset: -2px; }
|
||
#viewer-stage > :is(video, img:not([hidden])) { position: absolute; inset: 0; display: block; width: 100%; height: 100%; object-fit: contain; visibility: visible; /* D34 */ }
|
||
.media-failure { position: absolute; inset: 0; z-index: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 20px; background: var(--ca-field); text-align: center; font-size: 12px; }
|
||
/* #249 D34: Download stays the original file's link, drawn as the concept's toolbar button. */
|
||
#viewer-toolbar > .media-download { display: inline-flex; align-items: center; justify-content: center; gap: 6px; min-height: 32px; padding: 4px 10px; border: 1px solid var(--ca-border); border-radius: 5px; background: var(--ca-control); color: var(--ca-text); font-size: 12px; white-space: nowrap; /* D34 */ }
|
||
#viewer-toolbar > .media-download:hover:not([aria-disabled="true"]) { background: var(--ca-hover); }
|
||
#viewer-toolbar > [aria-disabled="true"] { opacity: .55; cursor: not-allowed; }
|
||
/* #249 D34: after Next → the position status and the unavailability reasons, each on its own centred line. */
|
||
#viewer-toolbar > p { flex-basis: 100%; margin: 0; text-align: center; font-size: 11px; line-height: 1.6; overflow-wrap: anywhere; }
|
||
@media (pointer: coarse) {
|
||
/* #249 D34: coarse pointers keep 40px square viewer targets (the concept's "−" and "+" are narrower). */
|
||
#viewer-dialog :is(.dialog-header > button, #viewer-toolbar > button, #viewer-toolbar > a) { min-width: 40px; min-height: 40px; /* D34 */ }
|
||
/* #249 D34: the details' disclosure summaries and "Download original" keep 40px targets too (the concept's coarse
|
||
rule covers buttons only); the summary keeps its disclosure marker. */
|
||
#viewer-details summary { box-sizing: border-box; min-block-size: 40px; padding-block: 10px; /* D34 */ }
|
||
#viewer-details a { display: inline-flex; align-items: center; min-block-size: 40px; /* D34 */ }
|
||
}
|
||
@media (max-width: 700px) {
|
||
#viewer-toolbar > .media-download { font-size: 10px; /* D34: as the concept's `.viewer-toolbar button` */ }
|
||
}
|
||
|
||
/* Interim page guards: concept element rules (`.ca-tools dialog`, `pre`) now reach production dialogs and prompt text
|
||
that keep their own design until their page PR rebuilds them in concept markup. */
|
||
.ca-tools dialog.prompt-sender { width: min(760px, calc(100% - 32px)); max-width: none; max-height: calc(100dvh - 32px); padding: 22px; border-color: var(--ca-line); border-radius: 12px; } /* PR D */
|
||
.ca-tools dialog.prompt-sender::backdrop { background: #000b; } /* PR D */
|
||
.ca-tools dialog.recorded-run-dialog { width: min(1100px, calc(100% - 32px)); max-width: calc(100% - 32px); max-height: calc(100dvh - 32px); padding: 22px; border-radius: 7px; } /* PR F */
|
||
.prompt-text[tabindex]:focus-visible { outline: 2px solid var(--ca-focus); outline-offset: 3px; } /* production-only: a confirmed prompt stays keyboard-reachable (#249 D22) */
|
||
@media (pointer: coarse) {
|
||
.ca-tools .recorded-history .history-toolbar button { min-height: 44px; } /* PR F: concept coarse buttons are 40px */
|
||
}
|
||
@media (max-width: 700px) {
|
||
.ca-tools dialog.recorded-run-dialog { padding: 16px; } /* PR F */
|
||
}
|
||
|
||
/* Images side panel (#249 D2), production-only. Real prompts can be far longer than the concept's samples, so the sticky
|
||
panel scrolls within the window and its footer stays reachable; where the concept unsticks it, it grows instead. */
|
||
.detail-panel { min-inline-size: 0; max-block-size: calc(100dvh - 40px); overflow: auto; /* D2 */ }
|
||
@media (max-width: 700px) { .detail-panel { max-block-size: none; /* D2 */ } }
|
||
@container images-page (inline-size < 600px) { .detail-panel { position: static; max-block-size: none; /* D2 */ } }
|
||
/* #249 D2/D20: the prompt blocks' mount point is transparent, so their sections sit in `.detail-inner` (panel) and the
|
||
viewer details' wrapper as the concept's do. */
|
||
:is(.detail-inner, #viewer-details > div) > .prompt-inspector { display: contents; /* D20 */ }
|
||
/* #249 D2: real titles and filenames can be one unbroken word; they wrap inside the 310px panel. */
|
||
.panel-heading h2, .detail-inner > p.small { min-inline-size: 0; overflow-wrap: anywhere; }
|
||
|
||
/* #249 D3: production-only rules kept from the retired images.css and inspector.css. Each has a live consumer. */
|
||
/* The inspector wraps real unbroken literals (model paths, raw records) inside the 310px panel and the 340px viewer
|
||
details, and an unavailable download stays visibly inert next to its reason. */
|
||
.file-inspector { min-inline-size: 0; overflow-wrap: anywhere; }
|
||
.file-inspector [aria-disabled="true"] { opacity: .6; cursor: not-allowed; }
|
||
/* #249 D20: the viewer details' pair send and history buttons wrap inside the narrow rail at 200% zoom with wider fonts,
|
||
where the toolkit's `nowrap` would push them past its edge. */
|
||
.ca-tools #viewer-details > button { white-space: normal; /* D20 */ }
|
||
/* The Images page narrows to one column inside a narrow container (the Library-embedded gallery). */
|
||
.images-main { container: images-page / inline-size; }
|
||
@container images-page (inline-size < 600px) {
|
||
.image-workspace { grid-template-columns: minmax(0, 1fr); }
|
||
}
|
||
/* #249 D42: a discovery alert a rescan leaves scrolls clear of the concept's fixed toast (22px inset, 12px at 700px and
|
||
below, two 12px lines at 390px), never under it; the toast itself keeps the concept's placement. */
|
||
.options-status { font-size: 12px; padding-inline: 4px; scroll-margin-block-end: 96px; /* D42 */ }
|
||
/* Until their pages rebuild (PR F history): the history page's and the recorded run's prompt blocks keep their
|
||
spacing (on the shelf's cards `.prompt-inspector` is `display: contents`, so its margin is inert there), the Archive
|
||
negative dialog keeps `section.prompt-block`, and the shared prompt sender keeps its dialog and send button. */
|
||
.prompt-inspector { margin-block-start: 22px; min-inline-size: 0; }
|
||
.prompt-block { border-block-start: 1px solid var(--ca-line); padding-block: 13px; min-inline-size: 0; }
|
||
.prompt-negative { --prompt-role: #ff8c98; }
|
||
[data-ca-theme="light"] .prompt-negative { --prompt-role: #b71931; }
|
||
.prompt-block-heading { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 8px; }
|
||
.prompt-block h3 { font-size: 10px; text-transform: uppercase; letter-spacing: .5px; margin: 0; color: var(--prompt-role); }
|
||
.prompt-empty, .prompt-block > .muted { font-size: 10px; }
|
||
/* PR E/F: the Archive dialog and the frozen field provenance keep `pre.prompt-text`, guarded against the concept `pre` rule. */
|
||
pre.prompt-text { font: inherit; font-size: 12px; line-height: 1.75; white-space: pre-wrap; overflow-wrap: anywhere; margin-block: 10px; background: none; padding: 0; border: 0; border-radius: 0; max-height: none; overflow: visible; }
|
||
.prompt-send-button { display: inline-flex; align-items: center; justify-content: center; gap: 7px; color: var(--ca-focus); }
|
||
.prompt-send-button svg { inline-size: 16px; block-size: 16px; flex-shrink: 0; }
|
||
.prompt-sender { background: var(--ca-surface); color: var(--ca-text); border: 1px solid var(--ca-line); border-radius: 12px; padding: 22px; inline-size: min(760px, calc(100% - 32px)); max-inline-size: none; max-block-size: calc(100dvh - 32px); overflow: auto; }
|
||
.prompt-sender::backdrop { background: #000b; }
|
||
.prompt-sender-header, .prompt-sender-actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px; }
|
||
.prompt-sender-header h2 { font-size: 20px; margin: 0; }
|
||
.prompt-sender-options { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; margin-block: 20px; }
|
||
.prompt-sender-options .ca-field { display: flex; flex-direction: column; align-items: stretch; gap: 6px; }
|
||
.prompt-sender select { inline-size: 100%; min-block-size: 38px; }
|
||
.prompt-sender-status { font-size: 13px; line-height: 1.6; }
|
||
.prompt-sender-status[role="alert"] { color: var(--ca-error-text); }
|
||
.prompt-sender-preview { margin-block: 20px; min-inline-size: 0; }
|
||
.prompt-sender-field { border: 1px solid var(--ca-line); border-radius: 7px; padding: 14px; margin-block: 12px; min-inline-size: 0; }
|
||
.prompt-sender-field :where(pre, textarea), .prompt-sender-composition { white-space: pre-wrap; overflow-wrap: anywhere; font: inherit; font-size: 12px; line-height: 1.65; max-inline-size: 100%; }
|
||
.prompt-sender-field textarea { inline-size: 100%; min-block-size: 100px; resize: vertical; }
|
||
.prompt-sender-composition { padding: 12px; background: var(--ca-field); border-radius: 6px; }
|
||
.prompt-sender-actions { justify-content: flex-end; margin-block-start: 18px; }
|
||
@media (max-width: 780px) {
|
||
.prompt-sender-options { grid-template-columns: minmax(0, 1fr); }
|
||
}
|
||
|
||
/* Prompt library cards (#249 PR E): the concept's `article.prompt-card` (s653–708) with real prompts, titles and media. */
|
||
/* #249 D23: real prompts are far longer than the concept's samples, so the card clamps the positive to two lines; copy
|
||
and send keep the full text, and history shows all of it. */
|
||
.prompt-card .prompt-text { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden; /* D23 */ }
|
||
/* #249 D23: the prompt blocks' mount point is transparent, so the section sits in `.card-content` as the concept's does. */
|
||
.card-content > .prompt-inspector { display: contents; /* D23 */ }
|
||
/* #249 D24: the history summary's prompt blocks sit in `article.surface` as the concept's do (app.js a598); the mount
|
||
point keeps its "Saved prompt texts" region name. */
|
||
.library-history article.surface > .prompt-inspector { display: contents; /* D24 */ }
|
||
/* #249 D23: a real title can be long or one unbroken word; it wraps inside the title row (the toolkit's buttons never
|
||
wrap), while the category chip keeps whole words beside it and wraps only a name wider than its share of the row. */
|
||
.card-title button { min-width: 0; white-space: normal; overflow-wrap: anywhere; /* D23 */ }
|
||
.card-title .chip { flex-shrink: 0; max-width: 60%; overflow-wrap: anywhere; /* D23 */ }
|
||
.prompt-card .tags .chip { max-width: 100%; overflow-wrap: anywhere; /* D23 */ }
|
||
/* #249 D23: a parent with one preview, or none, fills the art row instead of leaving the concept's second cell empty. */
|
||
.prompt-card .card-art > :only-child { grid-column: 1 / -1; /* D23 */ }
|
||
.prompt-card .card-art > .small { display: grid; place-content: center; min-width: 0; color: var(--ca-muted); /* D23 */ }
|