* feat(images): store images' own categories in the library database (#332) One new table, image_categories, and its category index, created empty by a forward-only migration that reads and writes only its own objects. Startup checks the structure; restore also checks the values. The store sets, clears and reads own categories by file key (the deepest output source holding the file and the path below it), and copies a removed duplicate's category to a kept copy that has none. * feat(images): duplicate cleanup keeps images' own categories (#332) The kept copy's own category wins; a kept copy without one receives the removed copy's, keyed by the deepest output source holding each file. The removed copy's row is never deleted, so a give-back or a failed delete loses nothing. The job log reports how many were kept. * feat(images): set, remove and list images' own categories (#332) PUT /prompt_manager/images/category sets the own category of up to 500 listed files, DELETE removes it (listed or not), and GET /prompt_manager/images/categories lists the values whose file is listed. Targets are the Images listing identity of an output source, checked by media_target (shared with Collections). The routes never touch prompts, recipes or links; POST /prompt_manager/bulk/category is unchanged. * feat(vocabulary): count and rename image categories (#332) Tags & categories lists every category value with image_count, the stored image_categories rows holding it. Values only images hold follow every prompt-backed value (first_pid at SQLite's INTEGER maximum), so keyset cursors cross the boundary. /library/options keeps its own query and never lists image-only values. Manage category counts image rows (impact.images), treats an image-held target as existing, renames image rows in the same transaction, allows renaming an image-only value, and hashes the sorted image rowids into the impact token so a change between preview and apply is refused with vocabulary-conflict. Every image-category statement reads idx_image_categories_category. * feat(images): the category filter matches each image's effective category (#332) An image's own category wins; without one it inherits its link's: the recipe's category when set, else the positive prompt's (MC4, one rule with the inspector chip). Ambiguous and unlinked images inherit nothing, so an unlinked image with its own category is now listed. The link read no longer filters by category in SQL; the page filter, select all N matching and saved views compare the effective value. A failed own-category read answers the existing listing-failed 500. * fix(images): key symlinked output folders and keep key work off the event loop (#332) Review fixes: a source matches by its configured spelling too, so a detected output folder that is a symlink keys its files (routes and duplicate cleanup). File keys are computed in the executor. A path no source holds is a plain 403, a lone surrogate a 400. The install repairs a table that lacks its category index instead of refusing to open. * feat(inspector): show an image's own category in the inspector and History (#332) The inspector's library facts gain category_scope "image": an image's own category overrides the inherited one for any link status (linked, ambiguous, none). Images reads it for output sources only; History reads it once per inspection inside its snapshot, keyed by the same file key from the symlink-resolved output folder. A failed read hides the chip rather than show a possibly wrong value. The chip's hidden prefix names the origin ("Image category: "); the visible origin text comes in PR 3. * perf(images): keep the category filter's link read narrow; record D141, D142, D145 (#332) Under a category the link read keeps only links whose raw recipe or positive category is the value (Python still decides the inherited one), the links of images whose own category is the value are read by path, and plain reads skip the inherited-category work. The own-category map is keyed by normalized path. TESTING.md records D141, D142 and D145 and amends D13, D36 and D39; the filter budget seeds 2,000 own categories. * feat(images): set or remove a category on all N matching images (#332) The category routes also accept the Images listing filter of "Select all N matching" (#392) instead of explicit items, resolved now and capped at 500 images like an explicit selection. * fix(images): Windows path keys for own categories; plan check for the category read (#332) Review fixes: on Windows the own-category map keys each file with normalize_image_path (abspath drops trailing dots and spaces), and the plan test also covers the category's narrowing clause. * feat(images): Set category sets each image's own category; the chip names its origin (#332) The selection bar's Set category writes the selected images' own category (linked or not, up to 500, or all N matching up to 500), with the toolbar categories as suggestions; Use prompt category removes it after a confirmation that states the count. It never writes a prompt (#259's prompt action is retired from Images; the Library editor and the legacy admin Set Category still change prompt categories). The toolbar lists the listed images' own categories beside the prompt and recipe ones. The inspector chip shows its origin: "· this image", "· prompt" or "· recipe". Tags & categories counts images and opens Images for a category only images use. * test(images): compare the category chip's visible origin against the concept (#332) D146: the inspector comparison measures each chip's span.chip-source and lays the concept's own chip out with the recorded rule and origin added at runtime (withChipSource), in the side panel and the viewer's details. The D39 recipe case asserts the visible " · prompt"; the mutation check removes the origin. * test(images): compare Set category and the category options against the concept (#332) With one image's own category set through the real route, the toolbar's #category-filter options are compared with D141 mapped (the image category sorted in with the Library's, D36), and the Set category dialog against the concept's simpleInput laid out as recorded at runtime: D140 (empty field, datalist#workspace-input-suggestions of the toolbar's categories, Use prompt category between Cancel and Apply) and D8 (Apply). Unmapped, the dialog differs only in those parts. * test(history): compare the History media inspector's category chip against the concept (#332) D145: with an own category set on the baseline's newest output, the Recorded run viewer's chip is compared with the concept viewer's category chip laid out with the D146 origin and the own category at runtime; D21 maps its hidden "Image category: " prefix. Unmapped, the two differ only in the text, the prefix, the width and span.chip-source. * test(images): Set category sets each image's own category in page workflows (#332) Replace the #259 prompt-category tests with CUJ 1, 2, 3, 8 and 10, the suggestions datalist, the 500-image limit, the source-changed path, D142 and coded refusals; Images never posts /prompt_manager/bulk/category. * test(inspector): the category chip names its origin in the panel, viewer and History (#332) CUJ 4 and 5: "· this image", "· prompt" and "· recipe" chips, an unlinked file's own category, and History's current-file inspector. * test(vocabulary): category rename covers image categories; image-only rows browse Images (#332) CUJ 6 and 7 with own categories set through the API on two output files. * test(vocabulary): compare image-category rows and the Manage category preview against the concept (#332) The vocabulary fixture seeds own image categories on request (image_categories: Landscapes on two images, Portraits on one image only). mapVocabularyConcept maps D143 ("N positive prompts · M images", image-only rows before Manage category), a model checked against the concept page with those rows added at runtime, and mapVocabularyDialog takes the preview sentence, so the Manage category preview is compared with D9 and D144's images. The default fixture and mapping are unchanged. * test(vocabulary): park the pointer before measuring the category rows (#332) Firefox kept the pointer where the concept's Review was pressed, over the production Portraits row's Browse, so that button was measured hovered. * docs(testing): record D140, D143, D144 and D146 and the image category UI (#332) D140 (Set category writes images' own categories), D146 (the chip's visible origin) beside D141, D142 and D145; D143 and D144 in the vocabulary section. D8, D21 (the D20/D21 side panel) and D39 amended; the D8, the "all N matching" and the Native vocabulary UI paragraphs name the Prompt library editor and the legacy admin "Set Category" for prompt categories; the #332 section lists the interface and its comparisons. * test(vendor): count the #332 host case in the execution matrix image-attribution.spec.mjs gains the image category route case, so the execution config lists 92 tests (3 attribution cases per project). * fix(images): focus after a changed source; Browse is a fresh Images visit (#332) Review fixes: a 409 source-changed reload moves focus to the heading as a success does; a selection the listing no longer holds says none could be found instead of sending nothing; a Tags & categories Browse opens a plain Images visit filtered by the category (remembered and with the Appearance defaults) rather than a saved-view visit. * test(legacy): record the image category routes in the legacy contract snapshot (#332) #332 adds PUT/DELETE /prompt_manager/images/category and GET /prompt_manager/images/categories; the legacy pages' routes are unchanged. * refactor(images): take the selection bar's categories from its one caller (#332) The getCategories default was never used (images.mjs always passes the toolbar's categories) and left images-selection.mjs function coverage at 97.05 (floor 98).
453 lines
40 KiB
CSS
453 lines
40 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; }
|
||
}
|
||
|
||
/* D53: the running job's progress pill before the Activity button (the approved Activity mockup's `.pill`), in the
|
||
concept's tokens. At <=700px it keeps its verb and percentage; the file counts and bar stay in its accessible name. */
|
||
.activity-pill:not([hidden]) { display: inline-flex; align-items: center; gap: 8px; padding: 6px 12px; border: 1px solid var(--line);
|
||
border-radius: 999px; font-size: 12px; white-space: nowrap; color: var(--ca-text); background: var(--ca-surface); /* D53 */ }
|
||
.activity-pill:hover { border-color: var(--ca-accent); /* D53 */ }
|
||
.activity-pill-bar { display: block; inline-size: 70px; block-size: 5px; border-radius: 3px; background: var(--line); overflow: hidden; /* D53 */ }
|
||
.activity-pill-bar i { display: block; block-size: 100%; inline-size: 0; background: var(--ca-accent); /* D53 */ }
|
||
/* D111 (S5): a running job without a known total sweeps a short segment; reduced motion shows a dimmed full bar. */
|
||
.activity-pill-bar.indeterminate i { inline-size: 40%; animation: activity-pill-sweep 1.4s ease-in-out infinite; /* D111 */ }
|
||
@keyframes activity-pill-sweep { from { transform: translateX(-100%); } to { transform: translateX(250%); } }
|
||
@media (prefers-reduced-motion: reduce) { .activity-pill-bar.indeterminate i { inline-size: 100%; opacity: .45; animation: none; /* D111 */ } }
|
||
@media (pointer: coarse) { .activity-pill:not([hidden]) { min-block-size: 40px; /* D53 */ } }
|
||
@media (max-width: 700px) {
|
||
/* D52: with the Activity button the full-width row can be wider than a zoomed phone, so its items wrap. */
|
||
.top-actions { flex-wrap: wrap; /* D52 */ }
|
||
.activity-pill:not([hidden]) { gap: 0; padding-inline: 10px; /* D53 */ }
|
||
.activity-pill-files, .activity-pill-bar { display: none; /* D53 */ }
|
||
}
|
||
|
||
/* D56: each Activity item's title links its job's view in the title's own colour. */
|
||
.activity-item h3 a { color: inherit; text-decoration: none; /* D56 */ }
|
||
.activity-item h3 a:hover { text-decoration: underline; /* D56 */ }
|
||
|
||
/* D56: one job's view, the approved Activity mockup (activity-scan.html) in the concept's tokens: the counter tiles,
|
||
the outcome list beside the selected outcome's files, and the collapsible log. */
|
||
.stat-row { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 10px; margin: 16px 0; /* D56 */ }
|
||
.stat-row .stat { border: 1px solid var(--line); border-radius: 7px; padding: 12px 14px; background: var(--ca-surface); min-inline-size: 0; /* D56 */ }
|
||
.stat-row .stat dt { font-size: 11px; color: var(--ca-muted); /* D56 */ }
|
||
.stat-row .stat dd { margin: 6px 0 0; font-size: 20px; font-weight: 600; /* D56 */ }
|
||
.stat-row .stat.added dd { color: var(--green); /* D56 */ }
|
||
.stat-row .stat.warn dd { color: var(--warning); /* D56 */ }
|
||
.stat-row .stat.bad dd { color: var(--red); /* D56 */ }
|
||
.surface > .panel-heading > .search-field { max-width: 320px; /* D56 */ }
|
||
.outcomes { display: grid; grid-template-columns: 220px minmax(0, 1fr); gap: 16px; margin-top: 6px; /* D56 */ }
|
||
.ca-tools .outcome-list button { display: flex; justify-content: space-between; width: 100%; margin-bottom: 6px; text-align: left; /* D56 */ }
|
||
.ca-tools .outcome-list button[aria-pressed="true"] { border-color: var(--ca-accent); background: var(--soft-blue); /* D56 */ }
|
||
.outcome-files .file-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 12px; padding: 9px 4px;
|
||
border-bottom: 1px solid var(--line); font-size: 12px; /* D56 */ }
|
||
.ca-tools button.file-row { width: 100%; min-height: 0; justify-content: normal; align-items: start; border: 0;
|
||
border-bottom: 1px solid var(--line); border-radius: 0; background: transparent; color: inherit; text-align: left;
|
||
white-space: normal; font: inherit; font-size: 12px; /* D56 */ }
|
||
.ca-tools button.file-row:hover:not(:disabled) { background: var(--ca-hover); /* D56 */ }
|
||
.file-row .path { font-family: ui-monospace, monospace; overflow-wrap: anywhere; /* D56 */ }
|
||
.file-row .why { color: var(--ca-muted); /* D56 */ }
|
||
.outcome-files > p.small { color: var(--ca-muted); margin-top: 10px; /* D56 */ }
|
||
.term { margin-top: 16px; border: 1px solid var(--line); border-radius: 7px; overflow: hidden; /* D56 */ }
|
||
.term summary { padding: 11px 14px; cursor: pointer; font-size: 12px; background: var(--ca-surface); /* D56 */ }
|
||
.term summary span { color: var(--ca-muted); /* D56 */ }
|
||
.term pre { margin: 0; padding: 12px 14px; max-height: 180px; overflow: auto; font: 11px/1.7 ui-monospace, monospace;
|
||
color: var(--ca-muted); background: var(--page); white-space: pre-wrap; overflow-wrap: anywhere; /* D56 */ }
|
||
@media (max-width: 700px) {
|
||
.stat-row { grid-template-columns: repeat(2, minmax(0, 1fr)); /* D56 */ }
|
||
.outcomes { grid-template-columns: minmax(0, 1fr); /* D56 */ }
|
||
.outcome-files .file-row { grid-template-columns: minmax(0, 1fr); gap: 4px; /* D56 */ }
|
||
}
|
||
|
||
/* 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; }
|
||
|
||
/* D92: System's Save row is aria-disabled while nothing is unsaved (or a save runs), and a setting the last save could
|
||
not save is highlighted, as its "Review the highlighted settings" status says. */
|
||
.save-row button[aria-disabled="true"] { opacity: .55; cursor: not-allowed; /* D92 */ }
|
||
.setting-row [aria-invalid="true"] { outline: 2px solid var(--red); outline-offset: 2px; /* D92 */ }
|
||
/* D92: a source the last save was refused for (System › Sources) is highlighted the same way. */
|
||
#source-list > .operation[aria-invalid="true"] { outline: 2px solid var(--red); outline-offset: 2px; /* D92 */ }
|
||
/* D93: an Overview row whose action is not available yet (the review queue, Q6) keeps its button, disabled. */
|
||
.operation .row-actions button[aria-disabled="true"] { opacity: .55; cursor: not-allowed; /* D93 */ }
|
||
/* D97: Appearance's legacy display rows. "Show image details on cards" off keeps a card's Info and Tag at the end of
|
||
its row; "Collapse viewer details by default" gives the viewer's stage the whole width while its details are hidden. */
|
||
.media-info > div[hidden] + .image-card-actions { margin-inline-start: auto; /* D97 */ }
|
||
.viewer-layout:has(> #viewer-details[hidden]) { grid-template-columns: minmax(0, 1fr); /* D97 */ }
|
||
@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; }
|
||
/* #392 D135: a long-press on a card selects it, so no image callout (iOS) takes the press. */
|
||
#image-grid > .media-card { -webkit-touch-callout: none; }
|
||
/* #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%; }
|
||
/* #249 D31: Info and Tag (#249 C2) wrap onto two lines when even the stacked row cannot hold them side by side. */
|
||
.narrow-cards .media-info > .image-card-actions { flex: 0 1 auto; min-width: 0; flex-wrap: wrap; /* D31 */ }
|
||
/* 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` */ }
|
||
}
|
||
|
||
.prompt-text[tabindex]:focus-visible { outline: 2px solid var(--ca-focus); outline-offset: 3px; } /* production-only: a confirmed prompt stays keyboard-reachable (#249 D22) */
|
||
|
||
/* 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 */ }
|
||
/* LI-4: "Copy settings summary" after the facts wraps the same way in the narrow rail. */
|
||
.ca-tools #viewer-details .settings-summary { white-space: normal; /* D117 */ }
|
||
/* 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 */ }
|
||
|
||
/* 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 D25: each compared negative's block sits in the concept's `.compare-item` (app.js a600), so its mount point is
|
||
transparent and adds no margin. */
|
||
.compare-item > .prompt-inspector { display: contents; /* D25 */ }
|
||
/* #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 */ }
|
||
|
||
/* Prompt library shelf (#249 PR E). #249 D3: rows stay windowed, as the Images grid's do; the concept's `.library-grid`
|
||
tracks and breakpoints lay them out, each row spans them, and appended rows never anchor the scroll. The row's
|
||
`min-inline-size: 0` is load-bearing: it keeps cards out of the concept's ≤700px `1fr` track's min-content. */
|
||
.library-main .library-grid { overflow-anchor: none; /* D3 */ }
|
||
.library-main .library-row { grid-column: 1 / -1; display: grid; grid-template-columns: inherit; gap: 18px; min-inline-size: 0; /* D3 */ }
|
||
.library-main .library-row-spacer { grid-column: 1 / -1; pointer-events: none; /* D3 */ }
|
||
/* #249 D40: the shelf's and history's production-only load, failure and continuation states. */
|
||
.library-main .library-continuation { padding-block: 22px; display: flex; flex-wrap: wrap; align-items: center; gap: 12px; /* D40 */ }
|
||
.library-main .library-status { flex: 1 1 240px; color: var(--ca-muted); /* D40 */ }
|
||
|
||
/* History variants panel (#249 PR F). */
|
||
/* D45: the panel's actions wrap their labels at 390px and 200% zoom (the toolkit's buttons never wrap). */
|
||
.ca-tools .library-main .library-history .row-actions button { max-inline-size: 100%; white-space: normal; /* D45 */ }
|
||
/* D45: the panel's paging buttons wrap like the recorded toolbar's. */
|
||
.ca-tools .library-main :where(.timeline-panel, .history-toolbar) > button { max-inline-size: 100%; white-space: normal; /* D45 */ }
|
||
/* D45: archived pairs (#235) are concept lanes at reduced emphasis (the .6 disabled-marker opacity); text stays at full contrast. */
|
||
.library-history .variant-lane.library-variant-archived::before, .ca-tools .library-history .library-variant-archived .variant-dot { opacity: .6; /* D45 */ }
|
||
.ca-tools .library-main .library-variant-restore { position: static; margin-inline-start: 8px; min-block-size: 24px; padding: 2px 8px; font-size: 10px; vertical-align: middle; /* D45 */ }
|
||
@media (pointer: coarse) { .ca-tools .library-main .library-variant-restore { min-block-size: 40px; /* D45 */ } }
|
||
/* D45: at 390px and 200% zoom the panel heading and the legend row wrap, and the All images button wraps its label,
|
||
so the page never scrolls sideways (the retired production footer did the same). */
|
||
.library-history .timeline-panel > .panel-heading, .library-history .timeline-lower { flex-wrap: wrap; /* D45 */ }
|
||
/* D45: while the repeat-run dot shows (left: 190px), the lane text wraps inside the 57px lane before it (43px + 139px). */
|
||
.library-history .variant-lane:has(> .run-dot:not([hidden])) > span { max-inline-size: 139px; overflow-wrap: anywhere; /* D45 */ }
|
||
.ca-tools .library-history .timeline-lower > button { max-inline-size: 100%; white-space: normal; /* D45 */ }
|
||
@media (pointer: coarse) {
|
||
/* D45: a coarse pointer reaches the concept dot through a 47px target. */
|
||
.ca-tools .library-history .variant-dot::after { content: ""; position: absolute; inset: -9px; }
|
||
/* D45: the 12px repeat-run dot (8px inside its border) gets a 44px coarse target the same way. */
|
||
.ca-tools .library-history .run-dot::after { content: ""; position: absolute; inset: -18px; }
|
||
}
|
||
|
||
/* #249 D48: production surfaces the concept never shows keep their own rules, in the concept's tokens: recorded
|
||
executions and the Recorded run dialog (#209, D26), the Archive negative confirmation (#235) and the acknowledged
|
||
prompt sender (spec §2.2). The dialog rules name `dialog` so they win over the concept's `.ca-tools dialog`. */
|
||
/* D48: recorded executions (#209) below the shared gallery. */
|
||
.library-main .library-surface { padding: 22px; background: var(--ca-surface); border: 1px solid var(--ca-line); border-radius: 7px; margin-block: 18px; /* D48 */ }
|
||
.history-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-block: 14px; /* D48 */ }
|
||
.recorded-history > .muted, .history-count, .history-scope, .history-status, .history-legend { font-size: 11px; line-height: 1.65; overflow-wrap: anywhere; /* D48 */ }
|
||
.library-main .recorded-history > h2 { font-size: 14px; line-height: 1.5; /* D48 */ }
|
||
.library-main .recorded-history > h2 + p { font-size: 12px; line-height: 1.5; /* D48 */ }
|
||
.history-timeline { position: relative; box-sizing: border-box; inline-size: 100%; max-inline-size: 100%; max-block-size: 490px; overflow: auto; overscroll-behavior: contain; overflow-anchor: none; border: 1px solid var(--ca-line); border-radius: 6px; background: var(--ca-field); /* D48 */ }
|
||
.history-canvas { position: relative; min-inline-size: calc(100% - 40px); margin: 18px 20px; /* D48 */ }
|
||
.history-positive-lane { position: absolute; inset-block-start: 17px; inset-inline: 0; border-block-start: 2px solid var(--green); color: var(--green); font-size: 10px; line-height: 1.6; padding-inline-start: 38px; /* D48 */ }
|
||
.history-positive-lane::before { content: "+"; position: absolute; inset-block-start: -13px; inset-inline-start: 0; border-radius: 50%; inline-size: 25px; block-size: 25px; background: var(--green); color: #172b22; text-align: center; font-size: 14px; line-height: 21px; /* D48 */ }
|
||
.history-positive-lane > span { position: absolute; inset-block-start: -25px; inset-inline-start: 38px; font-size: 10px; color: var(--green); /* D48 */ }
|
||
.history-earlier-edge { position: absolute; inset: 0 auto 0 0; inline-size: 1px; /* D48 */ }
|
||
.history-context-branch { position: absolute; inset-inline-end: 0; block-size: 57px; border-block-start: 2px solid color-mix(in srgb, var(--red) 60%, transparent); /* D48 */ }
|
||
.history-context-branch::before { content: ""; position: absolute; inset-block-start: -25px; inset-inline-start: -30px; inline-size: 30px; block-size: 25px; border-inline-start: 2px solid var(--red); border-block-end: 2px solid var(--red); border-end-start-radius: 22px; /* D48 */ }
|
||
.history-context-label { position: absolute; inset-inline-start: 43px; inset-block-start: 6px; inline-size: 370px; max-inline-size: calc(100% - 43px); font-size: 10px; line-height: 1.5; color: var(--ca-muted); overflow-wrap: anywhere; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: clip; /* D48 */ }
|
||
.history-run-list { position: absolute; inset: 0; margin: 0; padding: 0; list-style: none; /* D48 */ }
|
||
.history-run-column { position: absolute; inset-block: 0; inline-size: 64px; pointer-events: none; /* D48 */ }
|
||
.history-position { position: absolute; inset-block-start: 41px; font-size: 10px; color: var(--ca-muted); /* D48 */ }
|
||
.ca-tools .history-run-marker { position: absolute; pointer-events: auto; transform: translate(-50%, -50%); inline-size: 12px; block-size: 12px; min-inline-size: 12px; min-block-size: 12px; border: 2px solid var(--red); border-radius: 50%; background: var(--ca-field); padding: 0; color: var(--ca-field); font-size: 16px; line-height: 1; /* D48 */ }
|
||
.ca-tools .history-context-anchor { inline-size: 29px; block-size: 29px; min-inline-size: 29px; min-block-size: 29px; background: var(--red); color: #38151b; font-size: 12px; /* D48 */ }
|
||
.history-run-marker[aria-pressed="true"], .history-run-marker:focus-visible { outline: 3px solid var(--ca-focus); outline-offset: 4px; /* D48 */ }
|
||
.history-run-marker[aria-disabled="true"] { opacity: .6; cursor: progress; /* D48 */ }
|
||
/* D48: the Recorded run dialog (#209, D26). */
|
||
.ca-tools dialog.recorded-run-dialog { box-sizing: border-box; inline-size: min(1100px, calc(100% - 32px)); max-inline-size: calc(100% - 32px); max-block-size: calc(100dvh - 32px); padding: 22px; border-radius: 7px; overscroll-behavior: contain; scrollbar-gutter: stable; overflow-wrap: anywhere; /* D48 */ }
|
||
.recorded-run-header { display: flex; gap: 12px; align-items: center; justify-content: space-between; /* D48 */ }
|
||
.recorded-run-header h2 { font-size: 20px; /* D48 */ }
|
||
.recorded-run-body, .recorded-run-body > section { min-inline-size: 0; /* D48 */ }
|
||
.recorded-run-facts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; font-size: 12px; /* D48 */ }
|
||
.recorded-run-facts dt { color: var(--ca-muted); font-size: 10px; /* D48 */ }
|
||
.recorded-run-facts dd { margin: 4px 0 0; white-space: pre-wrap; /* D48 */ }
|
||
.recorded-run-anchors, .recorded-run-mode { display: flex; flex-wrap: wrap; gap: 8px; margin-block: 18px; /* D48 */ }
|
||
.recorded-run-dialog [aria-pressed="true"] { outline: 2px solid var(--ca-focus); outline-offset: 2px; /* D48 */ }
|
||
.recorded-run-dialog :where(.muted:not(.prompt-text), .recorded-output-status) { font-size: 11px; line-height: 1.65; /* D48 */ }
|
||
/* D48: the current file's prompt blocks sit below the dialog's evidence line (elsewhere their mount point is `display: contents`). */
|
||
.recorded-run-dialog .prompt-inspector { margin-block-start: 22px; /* D48 */ }
|
||
.recorded-provenance { border-block-start: 1px solid var(--ca-line); padding-block-start: 12px; margin-block: 18px; /* D48 */ }
|
||
.recorded-outputs { margin-block: 24px; /* D48 */ }
|
||
.recorded-receipts { list-style: decimal; padding-inline-start: 24px; /* D48 */ }
|
||
.recorded-receipt { border: 1px solid var(--ca-line); border-radius: 5px; padding: 12px; margin-block: 10px; font-size: 12px; /* D48 */ }
|
||
.recorded-receipt > div { margin-block-end: 10px; /* D48 */ }
|
||
.recorded-receipt[aria-current="true"] { outline: 2px solid var(--ca-focus); outline-offset: 2px; /* D48 */ }
|
||
.recorded-current-file { border-block-start: 1px solid var(--ca-line); padding-block-start: 16px; /* D48 */ }
|
||
/* D48: the frozen field provenance and the Archive confirmation show stored text in `pre.prompt-text`, not as the
|
||
concept's code `pre`. */
|
||
pre.prompt-text { font: inherit; font-size: 12px; line-height: 1.75; margin-block: 10px; background: none; padding: 0; border: 0; border-radius: 0; max-height: none; overflow: visible; /* D48 */ }
|
||
/* D48: the Archive negative confirmation's negative block (#235). */
|
||
.prompt-block { border-block-start: 1px solid var(--ca-line); padding-block: 13px; min-inline-size: 0; /* D48 */ }
|
||
.prompt-block-heading { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 8px; /* D48 */ }
|
||
.prompt-block.prompt-negative h3 { font-size: 10px; text-transform: uppercase; letter-spacing: .5px; margin: 0; color: var(--red); /* D48 */ }
|
||
.prompt-empty { font-size: 10px; /* D48 */ }
|
||
/* D48: the acknowledged prompt sender (prompt-sender.mjs) and its labelled pair send button. */
|
||
.prompt-send-button { display: inline-flex; align-items: center; justify-content: center; gap: 7px; color: var(--ca-focus); /* D48 */ }
|
||
.prompt-send-button svg { inline-size: 16px; block-size: 16px; flex-shrink: 0; /* D48 */ }
|
||
.ca-tools dialog.prompt-sender { inline-size: min(760px, calc(100% - 32px)); max-inline-size: none; max-block-size: calc(100dvh - 32px); padding: 22px; border-color: var(--ca-line); border-radius: 12px; /* D48 */ }
|
||
.ca-tools dialog.prompt-sender::backdrop { background: #000b; /* D48 */ }
|
||
.prompt-sender-header, .prompt-sender-actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px; /* D48 */ }
|
||
.prompt-sender-header h2 { font-size: 20px; /* D48 */ }
|
||
.prompt-sender-options { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; margin-block: 20px; /* D48 */ }
|
||
.prompt-sender-options .ca-field { display: flex; flex-direction: column; align-items: stretch; gap: 6px; /* D48 */ }
|
||
.prompt-sender select { inline-size: 100%; min-block-size: 38px; /* D48 */ }
|
||
.prompt-sender-status { font-size: 13px; line-height: 1.6; /* D48 */ }
|
||
.prompt-sender-status[role="alert"] { color: var(--ca-error-text); /* D48 */ }
|
||
.prompt-sender-preview { margin-block: 20px; min-inline-size: 0; /* D48 */ }
|
||
.prompt-sender-field { border: 1px solid var(--ca-line); border-radius: 7px; padding: 14px; margin-block: 12px; min-inline-size: 0; /* D48 */ }
|
||
.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%; /* D48 */ }
|
||
.prompt-sender-field textarea { inline-size: 100%; min-block-size: 100px; resize: vertical; /* D48 */ }
|
||
.prompt-sender-composition { padding: 12px; background: var(--ca-field); border-radius: 6px; /* D48 */ }
|
||
.prompt-sender-actions { justify-content: flex-end; margin-block-start: 18px; /* D48 */ }
|
||
@media (max-width: 780px) {
|
||
.prompt-sender-options { grid-template-columns: minmax(0, 1fr); /* D48 */ }
|
||
}
|
||
@media (max-width: 700px) {
|
||
.ca-tools dialog.recorded-run-dialog { padding: 16px; /* D48 */ }
|
||
.recorded-run-facts { grid-template-columns: minmax(0, 1fr); /* D48 */ }
|
||
}
|
||
@media (pointer: coarse) {
|
||
/* D48: the recorded toolbar, its scope disclosure and the dialog's controls keep 44px targets (concept coarse buttons are 40px). */
|
||
.ca-tools .recorded-history :is(.history-toolbar button, .history-scope summary) { min-inline-size: 44px; min-block-size: 44px; }
|
||
.ca-tools .history-run-marker { inline-size: 44px; block-size: 44px; min-inline-size: 44px; min-block-size: 44px; border: 0; background: transparent; color: transparent; }
|
||
.ca-tools .history-run-marker::before { content: ""; position: absolute; inset: 16px; box-sizing: border-box; border: 2px solid var(--red); border-radius: 50%; background: var(--ca-field); }
|
||
.ca-tools .history-context-anchor::before { content: "−"; inset: 7.5px; inline-size: 29px; block-size: 29px; line-height: 25px; color: #38151b; background: var(--red); }
|
||
.ca-tools .recorded-run-dialog :where(button, summary) { min-inline-size: 44px; min-block-size: 44px; }
|
||
}
|
||
@media (forced-colors: active) {
|
||
.history-context-branch, .history-context-branch::before, .history-run-marker, .history-run-marker::before { border-color: CanvasText; }
|
||
.history-run-marker[aria-pressed="true"] { outline-color: Highlight; }
|
||
}
|
||
|
||
/* Tag image dialog (#249 C2): the concept's `tagging-modal` (s1598–1694) as it stands, but D83 (ruling T4): the
|
||
suggestions are removable chips, each the concept's `.chip` corner (4px; the later `.tag-suggestion` row rule would
|
||
give 5px) around the row's 12px text in its checked suggestion row's tokens (`.tag-suggestion:has(input:checked)`,
|
||
s1655): a name, the confidence and a × button. */
|
||
#tag-suggestions .chip.tag-suggestion { display: inline-flex; gap: 6px; padding: 2px 2px 2px 8px; margin-bottom: 0; cursor: default; color: inherit; border-color: var(--ca-accent); background: var(--soft-blue); border-radius: 4px; /* D83 */ }
|
||
#tag-suggestions .chip.tag-suggestion .tag-confidence { margin-inline-start: 0; /* D83 */ }
|
||
.ca-tools #tag-suggestions .chip.tag-suggestion > button { min-block-size: 24px; min-inline-size: 24px; padding: 0 6px; /* D83 */ }
|
||
@media (pointer: coarse) {
|
||
/* D83: the chip's × keeps the concept's 40px coarse target. */
|
||
.ca-tools #tag-suggestions .chip.tag-suggestion > button { min-block-size: 40px; min-inline-size: 40px; }
|
||
}
|
||
|
||
/* Needs review (#249 PR F): the concept's Metadata review page (app.js a749–760) in its own classes. */
|
||
/* #249 D3: the review queue is not virtualized, so its cards stay in the concept grid's flow. */
|
||
.image-grid.review-queue > .media-card { position: static; /* D3 */ }
|
||
/* #249 D20: the prompt blocks' mount point is transparent, so the chosen path's sections sit in the panel as the
|
||
concept's do. */
|
||
.review-panel > .prompt-inspector { display: contents; /* D20 */ }
|
||
/* D119: a file that cannot be served (no current root) has an inert, empty media button that keeps the concept's
|
||
1.33 media area (app.js `.image-open img`), so its card lines up with the queue's other cards. */
|
||
.review-queue .image-open[aria-disabled="true"] { cursor: not-allowed; aspect-ratio: 1.33; /* D119 */ }
|
||
/* D119: a long file name in the panel wraps inside the panel, where the concept's sample name never overflows. */
|
||
.review-panel > p.small { min-inline-size: 0; overflow-wrap: anywhere; /* D119 */ }
|
||
/* Auto-tag selected images (#249 PR D, D85/D90): the Tag image dialog's model panel, then How to apply, a radio group
|
||
in the concept's `.field` row whose radios keep their own size (the concept's `.field input` is a full-width text
|
||
field) beside their label, as the concept's own radio rows (s1160 `.compare-item label`). */
|
||
.field[role="radiogroup"] > label { display: flex; align-items: center; gap: 8px; /* D90 */ }
|
||
.field[role="radiogroup"] input[type="radio"] { width: auto; margin: 0; /* D90 */ }
|
||
/* D129: the library-location notice sits above every page, in main's own padding and width, so its `.notice` lines
|
||
up with the page below; main's top padding then spaces it from the page. */
|
||
.library-banner { max-width: 1900px; margin: auto; padding: 30px 30px 0; /* D129 */ }
|
||
.library-banner > .notice { margin-bottom: 0; /* D129 */ }
|
||
@media (max-width: 1250px) { .library-banner { padding: 24px 24px 0; /* D129 */ } }
|
||
@media (max-width: 700px) { .library-banner { padding: 20px 14px 0; /* D129 */ } }
|
||
/* D101: a duplicate copy's Keep label is its source and path, often long names with no space to break at; it wraps
|
||
inside its `.compare-item` so the review never scrolls sideways. */
|
||
.duplicate-groups .compare-item label { overflow-wrap: anywhere; /* D101 */ }
|
||
/* D105: a review's grid values (the restore's backup file name, a safety backup's name) may be long names with no space
|
||
to break at; they wrap inside their column, so the concept's two equal columns hold at phone width. */
|
||
#modal-body > dl.meta-grid dd { overflow-wrap: anywhere; /* D105 */ }
|
||
/* D106: Integrations' two selects keep their chosen option readable: they never shrink for the row's reason lines, and
|
||
at phone width they may grow past the concept's 120px cap (which cuts "Follow host renderer") up to 160px. */
|
||
.setting-row :is(#send-destination, #renderer) { flex-shrink: 0; /* D106 */ }
|
||
@media (max-width: 700px) {
|
||
#workspace-main .setting-row :is(#send-destination, #renderer) { max-width: 160px !important; /* D106 */ }
|
||
}
|
||
/* D132: at phone width the concept's 120px cap cuts every engine label ("WD14 SwinV2 · ~400 MB"); the Tagging model
|
||
select alone keeps its natural width there (its widest engine label) and, with no room beside the row text, wraps
|
||
under it. Existing tags (#398) never shrinks for its row text and, past the cap, keeps its natural width beside the
|
||
text ("Replace after review"), bounded by its row like the engine select. */
|
||
.setting-row #tag-existing { flex-shrink: 0; /* D132 */ }
|
||
@media (max-width: 700px) {
|
||
#workspace-main .setting-row:has(> #tag-model) { flex-wrap: wrap; /* D132 */ }
|
||
#workspace-main .setting-row #tag-model { flex-shrink: 0; max-width: 100% !important; /* D132 */ }
|
||
#workspace-main .setting-row #tag-existing { max-width: 100% !important; /* D132 */ }
|
||
}
|
||
|
||
/* D109: System › Diagnostics & logs' added Lines field sits in the concept's toolbar like its Level select, and a value
|
||
outside 1–500 is highlighted as a refused setting is (D92). */
|
||
.toolbar #log-limit { inline-size: 6em; min-height: 36px; font-size: 11px; /* D109 */ }
|
||
.toolbar #log-limit[aria-invalid="true"] { outline: 2px solid var(--red); outline-offset: 2px; /* D109 */ }
|
||
|
||
/* #332 D146: the category chip names its origin (" · this image", " · prompt", " · recipe") in muted text that never
|
||
splits; at 390px the chip may wrap between the category and its origin. */
|
||
.chip .chip-source { color: var(--ca-muted); white-space: nowrap; }
|