Files
odysseus/static/css/08-skills.css
T
Léo a4715b77a5 refactor(css): name the fragments by area instead of by number
style-part-01..08 were arbitrary 6,000-line cuts, so the names said nothing
and a reader had no way to guess which file held a rule.

The original stylesheet turns out to be roughly area-grouped already, so
cutting where the content changes rather than every 6,000 lines produces
boundaries worth naming. Classifying every top-level rule by selector prefix
and smoothing over a 40-rule window gives 17 stable runs: agent-chat, compare,
memory, documents, admin-settings, skills, gallery, cookbook, tasks,
image-editor, email, notes, calendar, research.

The numeric prefix stays because load order is load-bearing, and it also
disambiguates the areas that appear twice: the original interleaves, and
merging non-adjacent blocks of the same area would reorder the cascade.

The names describe where a file sits, not a claim that it holds every rule for
that area or only rules for it. The header in each file says so, because that
is the misreading this naming invites.

Reconstruction was checked against lab's style.css before rewriting: identical
after whitespace normalisation, all 47,528 lines accounted for. The computed
style baseline still reproduces exactly.
2026-09-29 17:50:04 +02:00

1089 lines
33 KiB
CSS

/* 08-skills - part of the former static/style.css.
*
* The stylesheet is split by area, but the split is mechanical: rules were
* moved verbatim and NOT regrouped. The numeric prefix is the load order, and
* that order is what preserves the cascade. A rule sits in the file its
* original position fell into, so an area's rules are not all in its file and
* a file is not only that area's rules.
*
* Do not reorder the <link> tags in static/index.html, and do not move a rule
* between files: with 1,151 redeclarations and 1,886 !important in this
* cascade, either can change which declaration wins.
*
* static/index.html and the PRECACHE list in static/sw.js must agree on this
* order. tests/test_static_stylesheet_manifest.py and
* tests/test_css_computed_style_snapshot.py fail if that drifts.
*/
/* Document cards can be expanded in the normal windowed library, where the
grid has no definite height. A zero-basis <pre> then collapses to nothing
while the footer remains visible. Keep the document text's intrinsic height
in that layout; bounded/fullscreen layouts can still shrink it and scroll. */
#doclib-modal .doclib-card.doclib-card-expanded .doclib-card-preview > pre {
flex: 1 1 auto;
min-height: 1.5em;
}
/* Collapsible skills section headers (Your skills / Built-in). */
.skills-section-header {
display: flex;
align-items: center;
gap: 6px;
cursor: pointer;
user-select: none;
padding: 6px 4px 4px;
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.06em;
opacity: 0.6;
font-weight: 600;
}
.skills-section-header:hover { opacity: 0.9; }
.skills-section-chevron { transition: transform 0.15s ease; flex-shrink: 0; }
.skills-section-header.collapsed .skills-section-chevron { transform: rotate(-90deg); }
.skills-section-count {
margin-left: auto;
opacity: 0.6;
font-variant-numeric: tabular-nums;
font-weight: normal;
}
.skill-card-section-hidden { display: none !important; }
/* Warning banner shown when previewing/editing a built-in capability. */
.skill-builtin-warn {
font-size: 9.5px;
line-height: 1.45;
color: var(--color-warning, #f0ad4e);
background: color-mix(in srgb, var(--color-warning, #f0ad4e) 12%, transparent);
border: 1px solid color-mix(in srgb, var(--color-warning, #f0ad4e) 35%, transparent);
border-radius: 6px;
padding: 6px 9px;
margin: 0 0 8px;
flex-shrink: 0;
}
/* #skills-list wears both .memory-list and .doclib-grid. doclib-grid's
max-height:400px would cap the list AND clamp an expanded card — keep
the memory-list fill-the-modal behaviour instead. */
#skills-list.doclib-grid {
max-height: none;
flex: 1;
min-height: 0;
}
#skills-list.doclib-grid:has(.doclib-card-expanded) {
overflow: hidden; /* expanded card owns its own scroll */
}
/* When a skill is expanded, hide the sibling Add-Skill form card so the
expanded SKILL.md uses the full panel. Otherwise the two admin-cards
split the height ~50/50 and the expanded skill is stuck in its half
while the Add form idles in the other. */
.memory-tab-panel[data-memory-panel="skills"]:has(.doclib-card-expanded) > .admin-card:nth-of-type(2) {
display: none !important;
}
/* Skills cards reuse the doclib expand/footer machinery. The SKILL.md
preview + the edit textarea need to fill the expanded card and own
their own scroll, same as a document preview. */
/* Collapsed skill rows used a smaller (0.9em code) title, so the click row
read slimmer than document/chat/library cards. Give the header a min-height
so the tap target matches the other library items. */
.skill-card > .skill-card-header {
min-height: 53px;
box-sizing: border-box;
display: flex;
align-items: center;
gap: 6px;
}
/* Skill rows share Tasks' card hover colors. Draft state belongs in the
status badge, not in a second opacity layer across the whole row. */
/* Name + description column. Name wraps to 2 lines (skill slugs are long —
use the space rather than truncating to "check-model-downl…"). */
.skill-card-textcol {
flex: 1 1 auto;
min-width: 0;
overflow: hidden;
}
.skill-card-title-row {
display: flex;
align-items: center;
gap: 6px;
min-width: 0;
}
.skill-card-name {
font-family: inherit;
font-weight: 500;
font-size: 12px;
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
word-break: normal;
line-height: normal;
}
.skill-card-title-row .skill-card-name { min-width: 0; }
.skill-status-published {
width: 20px;
height: 20px;
padding: 0;
display: inline-flex;
align-items: center;
justify-content: center;
color: var(--color-success, #4ade80);
background: color-mix(in srgb, var(--color-success, #4ade80) 15%, transparent);
border-color: color-mix(in srgb, var(--color-success, #4ade80) 32%, transparent);
border-radius: 50%;
}
.skill-stats .skill-status-published {
width: 15px;
height: 15px;
margin-left: 3px;
vertical-align: middle;
}
.skill-stats .skill-status-published svg {
width: 9px;
height: 9px;
}
.skill-card-desc {
font-size: 10px;
opacity: 0.4;
margin-top: -1px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.skills-audit-model-field {
display: flex;
align-items: center;
gap: 8px;
margin-top: 10px;
font-size: 11px;
}
.skills-audit-model-field > span {
flex: 0 0 auto;
color: color-mix(in srgb, var(--fg) 68%, transparent);
}
.skills-audit-model-field select {
min-width: 0;
flex: 1;
height: 26px;
padding: 0 6px;
border: 1px solid var(--border);
border-radius: 4px;
background: var(--bg);
color: var(--fg);
font: inherit;
}
.skill-card-tags {
display: flex;
gap: 4px;
flex-wrap: wrap;
margin-top: 4px;
}
.skill-tag-pill {
padding: 1px 5px;
font-size: 9px;
}
.skill-usage-pill {
margin-left: auto;
color: color-mix(in srgb, var(--fg) 62%, transparent);
}
.skill-tag-filter-chip { cursor: pointer; }
@media (max-width: 768px) {
.skill-card > .skill-card-header {
align-items: flex-start;
}
.skill-card-textcol {
min-width: 0;
}
.skill-card-tags {
max-width: 100%;
overflow: hidden;
}
.skill-card-tags .skill-tag-pill {
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
/* Right cluster — pills (right-aligned), stats, then the menu/chevron. */
.skill-card-right {
display: flex;
align-items: center;
gap: 6px;
flex-shrink: 0;
margin-left: auto;
}
.skill-card-right .memory-cat-badge { flex-shrink: 0; }
.skill-status-bin {
background: color-mix(in srgb, var(--color-danger, #d9534f) 20%, transparent);
color: var(--color-danger, #d9534f);
}
.skill-model-pill {
max-width: 150px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.skill-model-student {
background: color-mix(in srgb, var(--fg) 10%, transparent);
}
.skill-audit-pass {
color: var(--color-success, #4ade80);
background: color-mix(in srgb, var(--color-success, #4ade80) 16%, transparent);
}
.skill-audit-needs_work {
color: var(--color-warning, #f0ad4e);
background: color-mix(in srgb, var(--color-warning, #f0ad4e) 16%, transparent);
}
.skill-audit-fail {
color: var(--color-danger, #e06c75);
background: color-mix(in srgb, var(--color-danger, #e06c75) 16%, transparent);
}
.skill-audit-inconclusive,
.skill-audit-skipped,
.skill-audit-unknown {
color: color-mix(in srgb, var(--fg) 64%, transparent);
background: color-mix(in srgb, var(--fg) 8%, transparent);
}
.skill-model-teacher {
background: color-mix(in srgb, var(--color-warning, #f0ad4e) 24%, transparent);
color: var(--color-warning, #f0ad4e);
}
.skill-necessity-pill {
max-width: 150px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.skill-necessity-duplicate {
background: color-mix(in srgb, var(--color-warning, #f0ad4e) 22%, transparent);
color: var(--color-warning, #f0ad4e);
}
.skill-necessity-trivial {
background: color-mix(in srgb, var(--fg) 8%, transparent);
color: color-mix(in srgb, var(--fg) 62%, transparent);
border-color: color-mix(in srgb, var(--fg) 16%, transparent);
}
.skill-necessity-irrelevant {
background: color-mix(in srgb, var(--color-danger, #e06c75) 18%, transparent);
color: var(--color-danger, #e06c75);
}
.skills-summary-strip {
display: flex;
align-items: center;
gap: 6px;
flex-wrap: nowrap;
overflow-x: auto;
overflow-y: hidden;
overscroll-behavior-x: contain;
touch-action: pan-x;
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
margin: -3px 0 8px;
}
.skills-summary-strip::-webkit-scrollbar { display: none; }
.skills-summary-strip:empty {
display: none;
}
.skills-summary-chip {
display: inline-flex;
align-items: center;
gap: 5px;
height: 23px;
padding: 0 8px;
border: 1px solid color-mix(in srgb, var(--fg) 12%, transparent);
border-radius: 6px;
background: color-mix(in srgb, var(--fg) 4%, transparent);
color: color-mix(in srgb, var(--fg) 70%, transparent);
font-size: 10px;
line-height: 1;
cursor: pointer;
flex: 0 0 auto;
}
.skills-summary-chip:hover {
background: color-mix(in srgb, var(--fg) 8%, transparent);
border-color: color-mix(in srgb, var(--fg) 18%, transparent);
}
.skills-summary-chip.active {
background: color-mix(in srgb, var(--accent, var(--red)) 13%, transparent);
border-color: color-mix(in srgb, var(--accent, var(--red)) 28%, transparent);
color: var(--fg);
}
.skills-summary-chip strong {
font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace;
font-size: 10px;
color: var(--fg);
}
/* Shared filter/tag strip contract. These rows all sit immediately below
their screen's search or primary toolbar and stay one line at every width. */
:is(
.skills-summary-strip,
#memory-category-filters,
.memory-category-filters:has(> .memory-cat-chip),
.doclib-lang-chips,
.doclib-chips,
.notes-labels-bar,
.tasks-activity-filters,
.gallery-tag-chips,
.gallery-album-chips,
.gallery-ai-tags,
.cal-filters
) {
align-items: center;
flex-wrap: nowrap !important;
gap: 6px;
width: 100%;
max-width: 100%;
min-width: 0;
max-height: none;
overflow-x: auto !important;
overflow-y: hidden !important;
overscroll-behavior-x: contain;
touch-action: pan-x;
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
box-sizing: border-box;
padding: 0;
margin: -3px 0 8px;
}
:is(
.skills-summary-strip,
#memory-category-filters,
.memory-category-filters:has(> .memory-cat-chip),
.doclib-lang-chips,
.doclib-chips,
.notes-labels-bar,
.tasks-activity-filters,
.gallery-tag-chips,
.gallery-album-chips,
.gallery-ai-tags,
.cal-filters
)::-webkit-scrollbar { display: none; }
:is(
.skills-summary-strip > .skills-summary-chip,
#memory-category-filters > .skills-summary-chip,
.memory-category-filters > .memory-cat-chip,
.doclib-lang-chips > :is(.memory-cat-chip, .skills-summary-chip),
.doclib-chips > .doclib-chip,
.notes-labels-bar > .notes-label-chip,
.tasks-activity-filters > .memory-cat-chip,
.gallery-tag-chips > .gallery-chip,
.gallery-album-chips > .gallery-chip,
.gallery-ai-tags > .gallery-ai-chip,
.cal-filters > .cal-filter-item
) {
flex: 0 0 auto;
box-sizing: border-box;
height: 23px !important;
min-height: 23px;
padding: 0 8px !important;
border-radius: 6px !important;
font-size: 10px !important;
line-height: 1;
white-space: nowrap;
}
.doclib-lang-chips {
flex: 0 0 auto;
min-height: 25px;
padding: 1px 0 !important;
scroll-padding-inline: 1px;
}
.doclib-chip-scroll-frame {
position: relative;
display: flex;
align-items: center;
flex: 1 1 100%;
min-width: 0;
max-width: 100%;
}
.doclib-chip-scroll-frame > .chip-scroll-strip {
flex: 1 1 auto;
min-width: 0;
margin-left: 0;
margin-right: 0;
}
.doclib-chip-scroll-arrow {
display: none;
position: absolute;
top: calc(50% - 1px) !important;
transform: translateY(-50%);
margin-top: 0;
z-index: 2;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
padding: 0;
border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent);
border-radius: 50%;
box-sizing: border-box;
background: color-mix(in srgb, var(--bg) 86%, var(--fg) 14%);
color: var(--accent, var(--red));
box-shadow: 0 6px 18px rgba(0,0,0,0.16);
font-size: 18px;
line-height: 18px;
cursor: pointer;
opacity: 0.84;
transition: opacity 0.12s ease, background 0.12s ease, color 0.12s ease;
}
.doclib-chip-scroll-arrow-glyph {
display: inline-block;
position: relative;
top: -1px;
line-height: 1;
}
.doclib-chip-scroll-arrow:hover {
opacity: 1;
background: color-mix(in srgb, var(--bg) 76%, var(--fg) 24%);
}
@media (max-width: 768px) {
.doclib-chip-scroll-arrow { display: none !important; }
}
.doclib-chip-scroll-frame.has-overflow > .doclib-chip-scroll-arrow:not([hidden]) {
display: inline-flex;
}
.doclib-chip-scroll-arrow.left { left: 0; }
.doclib-chip-scroll-arrow.right { right: 0; }
.doclib-chip-scroll-frame.has-overflow > .chip-scroll-strip {
padding-left: 0 !important;
padding-right: 22px !important;
}
.doclib-chip-scroll-frame > .chip-scroll-strip.is-pointer-dragging {
cursor: grabbing;
user-select: none;
}
.doclib-chip-scroll-frame > .chip-scroll-strip:not(.is-pointer-dragging) {
cursor: grab;
}
/* Calendar tags are wrapped by chipScroll.js after render. Keep that wrapper
content-sized inside the calendar's column layout; the default flex growth
otherwise turns the one-line tag strip into a large empty panel. */
#cal-body > .doclib-chip-scroll-frame:has(> .cal-filters) {
flex: 0 0 auto;
min-height: 0;
}
/* The completed-task filters are a compact toolbar, not a flexible panel.
Without this override the shared chip frame grows into the available
height of the card, which is especially visible the first time the mobile
view switches to Completed. */
.tasks-completed-card > .doclib-chip-scroll-frame:has(> #tasks-completed-status-chips) {
flex: 0 0 auto;
min-height: 0;
}
/* The active Tasks filter row is also a compact toolbar. Keep its wrapper
content-sized so changing a filter cannot make the mobile panel grow a
large empty gap between the chips and the task cards. */
.admin-card > .doclib-chip-scroll-frame:has(> #tasks-filter-chips) {
flex: 0 0 auto;
min-height: 0;
}
#memory-category-filters {
flex: 0 0 auto;
min-height: 25px;
padding: 1px 0 !important;
scroll-padding-inline: 1px;
}
.cookbook-serve-tag-label {
display: inline-flex;
align-items: center;
min-width: 0;
}
#serve-search,
#hwfit-cache-select,
#serve-tags {
position: relative;
top: -2px;
}
#hwfit-cache-select {
top: -4px;
}
/* Calendar previously framed its tags as a separate card. It now follows the
same unframed placement as the other filter strips. */
.cal-filters {
border: 0;
background: none;
box-shadow: none;
}
.skill-duplicate-keep {
background: color-mix(in srgb, var(--color-success, #4ade80) 22%, transparent);
color: var(--color-success, #4ade80);
}
.skill-duplicate-group {
font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace;
color: color-mix(in srgb, var(--fg) 62%, transparent);
background: color-mix(in srgb, var(--fg) 8%, transparent);
}
.skill-duplicate-lower {
background: color-mix(in srgb, var(--color-danger, #e06c75) 14%, transparent);
color: var(--color-danger, #e06c75);
}
.skill-stats {
font-size: 9px;
font-family: monospace;
white-space: nowrap;
color: color-mix(in srgb, var(--fg) 45%, transparent);
}
.skill-turns-saved {
font-size: 9px;
opacity: 0.82;
font-variant-numeric: tabular-nums;
white-space: nowrap;
flex: 0 0 auto;
}
.skill-expanded-savings {
display: flex;
align-items: center;
min-height: 18px;
margin: 0 0 5px;
}
.skill-turns-positive {
color: color-mix(in srgb, var(--color-success, #4ade80) 82%, var(--fg));
}
.skill-turns-zero,
.skill-turns-unknown {
color: color-mix(in srgb, var(--fg) 48%, transparent);
}
.skill-turns-negative {
color: color-mix(in srgb, var(--color-danger, #e06c75) 80%, var(--fg));
}
.skill-conf {
display: inline-block;
min-width: 4ch;
text-align: right;
font-weight: 700;
color: var(--accent, var(--red));
}
.skill-verified,
.skill-teachermark,
.skill-needsmark {
display: inline-flex;
align-items: center;
vertical-align: middle;
margin-right: 3px;
}
.skill-verified { color: var(--accent, #4ade80); }
.skill-teachermark { color: var(--color-warning, #f0ad4e); }
.skill-needsmark { color: var(--color-danger, #e06c75); cursor: help; }
/* The card currently being processed by an "Audit now" run glows + pulses so
it's obvious which one the audit is on. */
@keyframes skill-audit-pulse {
0%, 100% { box-shadow: 0 0 6px 0 color-mix(in srgb, var(--accent, #4ade80) 55%, transparent); }
50% { box-shadow: 0 0 16px 3px color-mix(in srgb, var(--accent, #4ade80) 85%, transparent); }
}
.skill-card.skill-audit-active {
border-color: var(--accent, #4ade80) !important;
animation: skill-audit-pulse 1.3s ease-in-out infinite;
}
/* A test is running for this skill — the app's whirlpool spinner is injected
next to the name from JS (see _setCardRunning) so the collapsed/folded card
still makes it obvious work is in progress. */
/* Collapsed bar shows the kebab menu; expanded shows an up-chevron to
collapse. (Toggled by the .doclib-card-expanded class.) */
.skill-kebab-btn {
display: inline-flex;
align-items: center;
justify-content: center;
background: none;
border: none;
padding: 4px;
margin: 0;
cursor: pointer;
color: var(--fg);
opacity: 0.5;
border-radius: 5px;
flex-shrink: 0;
position: relative;
top: -2px;
}
.skill-kebab-btn:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 10%, transparent); }
.skill-chevron-up { display: none; align-items: center; opacity: 0.5; flex-shrink: 0; }
/* Kebab dropdown */
.skill-kebab-menu {
position: fixed;
/* z-index is set inline via topPortalZ() at open time (#4720); a static
value here loses once the modal bring-to-front counter climbs past it. */
min-width: auto;
width: max-content;
padding: 4px;
background: var(--panel, var(--bg));
border: 1px solid var(--border);
border-radius: 8px;
box-shadow: 0 6px 20px rgba(0,0,0,0.22);
display: flex;
flex-direction: column;
gap: 1px;
}
.skill-kebab-item {
display: flex;
align-items: center;
gap: 10px;
width: 100%;
padding: 6px 8px;
background: none;
border: none;
border-radius: 8px;
cursor: pointer;
color: var(--fg);
font-size: 11px;
text-align: left;
}
.skill-kebab-item:hover { background: color-mix(in srgb, var(--fg) 10%, transparent); }
.skill-kebab-item.danger { color: var(--color-error, #e55); }
.skill-kebab-item.danger:hover { background: color-mix(in srgb, var(--color-error, #e55) 15%, transparent); }
/* Section labels separating "Your skills" from the read-only "Built-in
capabilities" list. */
.skills-section-label {
font-size: 10px;
opacity: 0.5;
text-transform: uppercase;
letter-spacing: 0.04em;
margin: 10px 2px 4px;
flex-shrink: 0;
}
.skills-section-label:first-child { margin-top: 0; }
/* When a learned skill is expanded the grid hides sibling cards; hide the
section labels too so they don't orphan above the collapsed list. */
#skills-list:has(.doclib-card-expanded) .skills-section-label { display: none; }
/* Built-in cards are read-only — no expand/hover-pointer affordance. */
.skill-builtin-card { cursor: default; }
.skill-builtin-card:hover { border-color: var(--border); }
.skill-card-preview .skill-md-pre {
font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace;
font-size: 11.5px;
line-height: 1.5;
white-space: pre-wrap;
word-break: break-word;
}
.skill-preview-copy {
position: absolute;
top: 6px;
right: 8px;
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
padding: 0;
opacity: 0;
color: var(--fg);
background: var(--bg);
border: 1px solid var(--border);
border-radius: 6px;
cursor: pointer;
transition: opacity .15s, color .15s, border-color .15s;
}
.skill-card-preview:hover .skill-preview-copy { opacity: 0.7; }
.skill-preview-copy:hover,
.skill-preview-copy:focus-visible { opacity: 1 !important; }
.skill-preview-copy.copied {
opacity: 1;
color: var(--color-save-green, #4caf50);
border-color: var(--color-save-green, #4caf50);
}
/* :has()-free expand layout. skills.js adds .skills-has-expanded to the
skills admin-card when a skill opens, so this works on engines that don't
support :has() (e.g. older Firefox mobile, where the expand stuck at
~50%). Hide everything but the list so the grid fills the card; the
absolutely-positioned expanded card (mobile) then fills the grid. */
.admin-card.skills-has-expanded > *:not(#skills-list) {
display: none !important;
}
.admin-card.skills-has-expanded > #skills-list.doclib-grid {
flex: 1 1 auto !important;
height: 100% !important;
min-height: 0 !important;
overflow: hidden !important;
}
/* Expand animation — a clean opacity fade. The mobile card snaps to a
full-screen overlay (position:absolute), so any translate/scale on it
reads as a jarring "explosion"; a pure fade just appears smoothly. (No
transform also keeps skills.js's height measurement accurate.) */
@keyframes skill-card-expand {
from { opacity: 0; }
to { opacity: 1; }
}
.skill-card.doclib-card-expanded {
animation: skill-card-expand 0.18s ease-out;
}
/* Switching directly from one expanded card to another: no fade (it would
show the previous card collapsing through the semi-transparent new one). */
.skill-card.doclib-card-expanded.skill-expand-instant {
animation: none !important;
}
@media (prefers-reduced-motion: reduce) {
.skill-card.doclib-card-expanded,
.skill-card.doclib-card-expanded .doclib-card-preview {
animation: none !important;
}
}
/* The preview WRAPPER inherits flex:1 (grow) from the generic doclib expand
rule — that's what stretched the card and left a void / pushed the footer
out. Size it to content (shrink + scroll only when long) so the footer
sits right under the preview and stays fully visible. */
.skill-card.doclib-card-expanded .doclib-card-preview {
flex: 0 1 auto !important;
min-height: 0;
/* The footer lives INSIDE this wrapper. Don't let the wrapper scroll, or
the footer scrolls off with the content. Keep it clipped; the <pre>
inside is the scroller, so the footer stays pinned at the bottom. */
overflow: hidden !important;
}
.skill-card.doclib-card-expanded .skill-md-pre {
flex: 1 1 auto;
min-height: 0;
overflow-y: auto;
}
/* Skill footer buttons sit 4px high (shared rule sets top:-4px) — drop them
back down 4px so they're vertically centered in the skill card footer. */
.skill-card .doclib-card-expanded-actions > .doclib-card-action-btn,
.skill-card .doclib-card-expanded-actions .doclib-action-btn-row > .doclib-card-action-btn {
top: 0;
}
/* Skills select-mode bulk bar — nudge Cancel / Approve / Delete up 2px. */
#skills-bulk-bar .memory-toolbar-btn {
position: relative;
top: -2px;
}
/* ── Audit-all progress panel ── */
.skills-audit-panel {
border: 1px solid var(--border);
border-radius: 8px;
padding: 8px 10px;
margin: 6px 0;
background: color-mix(in srgb, var(--fg) 3%, transparent);
}
.skills-audit-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.skills-audit-title { font-size: 12px; font-weight: 600; }
.skills-audit-bar { height: 4px; border-radius: 2px; background: color-mix(in srgb, var(--fg) 12%, transparent); margin: 6px 0; overflow: hidden; }
.skills-audit-fill { height: 100%; background: var(--accent, var(--red)); transition: width 0.3s ease; }
.skills-audit-summary { font-size: 11px; opacity: 0.7; margin-bottom: 4px; }
.skills-audit-log {
max-height: 22vh; overflow-y: auto;
font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace;
font-size: 10.5px; line-height: 1.5; opacity: 0.8;
}
/* ── Skill test monitor + AI eval verdict ── */
.skill-test { display: flex; flex-direction: column; gap: 8px; min-height: 0; }
.skill-test-log {
flex: 1 1 auto;
min-height: 120px;
max-height: 48vh;
overflow-y: auto;
font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace;
font-size: 9.5px;
line-height: 1.45;
padding: 8px 10px;
border: 1px solid var(--border);
border-radius: 6px;
background: var(--bg);
white-space: pre-wrap;
word-break: break-word;
}
.skill-test-log > div { margin: 1px 0; }
.skill-test-meta { opacity: 0.5; }
.skill-test-task { color: var(--accent, var(--red)); font-weight: 500; font-size: 9px; opacity: 0.85; }
.skill-test-round { opacity: 0.6; margin-top: 6px !important; }
.skill-test-tool { color: var(--accent, var(--red)); opacity: 0.85; }
.skill-test-out { opacity: 0.7; padding-left: 10px; }
.skill-test-say { white-space: pre-wrap; }
.skill-test-err { color: var(--color-danger, #e06c75); }
.skill-eval-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.skill-eval-badge {
font-size: 10px; font-weight: 700; letter-spacing: 0.03em;
padding: 2px 8px; border-radius: 10px; flex-shrink: 0;
}
.skill-eval-ok { background: color-mix(in srgb, var(--color-success, #4ade80) 26%, transparent); color: var(--color-success, #4ade80); }
.skill-eval-warn { background: color-mix(in srgb, var(--color-warning, #f0ad4e) 26%, transparent); color: var(--color-warning, #f0ad4e); }
.skill-eval-bad { background: color-mix(in srgb, var(--color-danger, #e06c75) 26%, transparent); color: var(--color-danger, #e06c75); }
.skill-eval-unknown { background: color-mix(in srgb, var(--fg) 14%, transparent); }
.skill-eval-summary { font-size: 11px; opacity: 0.85; }
.skill-eval-issues { margin: 6px 0 0; padding-left: 18px; font-size: 11px; opacity: 0.8; }
.skill-eval-issues li { margin: 2px 0; }
.skill-eval-actions { display: flex; gap: 6px; justify-content: flex-end; margin-top: 8px; }
.skill-eval-approve.suggested {
border-color: var(--color-success, #4ade80) !important;
color: var(--color-success, #4ade80) !important;
}
/* Already published — the button confirms the state (click to unpublish). */
.skill-eval-approve.is-approved {
border-color: var(--color-success, #4ade80) !important;
color: var(--color-success, #4ade80) !important;
background: color-mix(in srgb, var(--color-success, #4ade80) 14%, transparent) !important;
}
/* Add-Skill form: a "rich placeholder" overlay so only the FIRST word (Title,
When to use, How, Tags) is accent-colored while the rest stays muted — real
placeholders can't be partially colored. The native placeholder is a single
space so :placeholder-shown still toggles the overlay. */
.skill-ph-wrap { position: relative; }
.skill-ph-wrap .skill-hint-input { width: 100%; box-sizing: border-box; }
.skill-rich-ph {
position: absolute;
left: 11px; right: 11px; top: 50%;
transform: translateY(-50%);
pointer-events: none;
font-size: 12px;
line-height: 1.2;
color: color-mix(in srgb, var(--fg) 40%, transparent);
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Textarea: align the hint to the first line instead of vertical-centering. */
.skill-rich-ph-top { top: 7px; transform: none; }
.skill-rich-ph .k { color: var(--accent, var(--red)); }
/* Keep the memory-input enter hint pinned to the field's right edge instead
of letting it drift after the example text. */
#new-memory-input ~ .skill-rich-ph > svg {
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
margin-left: 0 !important;
}
/* Hide the overlay once the field has real content. */
.skill-hint-input:not(:placeholder-shown) ~ .skill-rich-ph { display: none; }
.skill-md-editor {
flex: 1 1 auto;
min-height: 0;
width: 100%;
box-sizing: border-box;
resize: none;
font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace;
font-size: 11.5px;
line-height: 1.5;
padding: 8px;
border: 1px solid var(--border);
border-radius: 6px;
background: var(--bg);
color: var(--fg);
}
.doclib-empty {
text-align: center;
color: color-mix(in srgb, var(--fg) 35%, transparent);
padding: 32px 16px;
font-size: 12px;
font-style: italic;
}
/* Unified loading row across every Library tab (Chats / Documents / Research /
Archive): one opacity, one size, with the whirlpool spinner next to it. */
.lib-loading-row {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 28px 16px;
font-size: 12px;
color: color-mix(in srgb, var(--fg) 45%, transparent);
}
.pdf-loading-state {
min-height: 120px;
margin: 0;
box-sizing: border-box;
flex-direction: column;
gap: 10px;
border: 0;
border-radius: 0;
background: transparent;
color: color-mix(in srgb, var(--fg) 68%, transparent);
box-shadow: none;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.01em;
}
.pdf-loading-state .ai-spinner-whirlpool {
color: var(--accent, var(--red));
opacity: 0.95;
}
.pdf-loading-state .ai-spinner-whirlpool canvas {
display: block;
}
.doclib-load-more {
display: block;
margin: 10px auto 0;
padding: 6px 16px;
background: transparent;
border: 1px solid var(--border);
border-radius: 6px;
color: var(--fg-muted);
font-size: 11px;
cursor: pointer;
transition: border-color 0.15s, color 0.15s;
flex-shrink: 0;
position: relative;
top: 10px;
}
/* Soft dark gradient above the button — blends the list content into the
load-more strip instead of a hard cutoff. Spans the modal width and
sits behind/above the button. */
.doclib-load-more::before {
content: "";
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: 100%;
margin-bottom: 4px;
width: min(600px, 92vw);
height: 34px;
pointer-events: none;
background: linear-gradient(to top, color-mix(in srgb, #000 24%, transparent), transparent);
}
.doclib-load-more:hover {
border-color: var(--red);
color: var(--red);
}
.doclib-inline-load-more {
border: 0;
border-radius: 0;
padding: 0;
background: none;
top: 0;
font-size: 9px !important;
line-height: 1.2 !important;
}
/* Document library toolbar buttons */
.doclib-toolbar-btn {
background: none;
border: 1px solid var(--border);
color: var(--fg-muted);
font-size: 11px;
padding: 5px 10px;
border-radius: 6px;
cursor: pointer;
transition: all 0.15s;
white-space: nowrap;
font-family: inherit;
display: inline-flex;
align-items: center;
gap: 3px;
}
.doclib-toolbar-btn:hover {
color: var(--fg);
border-color: var(--fg);
}
.doclib-toolbar-btn.active {
background: color-mix(in srgb, var(--red) 15%, transparent);
border-color: color-mix(in srgb, var(--red) 40%, transparent);
color: var(--red);
}
.doclib-toolbar-btn.danger {
color: var(--color-error, #e55);
}
.doclib-toolbar-btn.danger:hover:not(:disabled) {
border-color: var(--color-error, #e55);
}
.doclib-toolbar-btn:disabled {
opacity: 0.4;
cursor: default;
}
/* Bulk action bar */
.doclib-bulk-bar {
display: flex;
align-items: center;
gap: 8px;
padding: 4px 8px;
border: 1px solid color-mix(in srgb, var(--red) 30%, transparent);
border-radius: 6px;
background: color-mix(in srgb, var(--red) 5%, transparent);
font-size: 10px;
margin-bottom: 8px;
}
.doclib-bulk-bar.hidden {
display: none;
}
.doclib-bulk-check-all {
display: flex;
align-items: center;
gap: 3px;
cursor: pointer;
color: color-mix(in srgb, var(--fg) 60%, transparent);
font-size: 10px;
}
#doclib-bulk-bar .memory-toolbar-btn {
position: relative;
top: -3px;
}
/* Custom checkboxes */
.doclib-select-cb,
.doclib-bulk-check-all input {
-webkit-appearance: none;
appearance: none;
width: 13px;
height: 13px;
border: 1px solid var(--border);
border-radius: 4px;
background: var(--bg);
cursor: pointer;
flex-shrink: 0;
margin: 0;
position: relative;
transition: all 0.15s;
}
.doclib-select-cb:hover,
.doclib-bulk-check-all input:hover {
border-color: var(--red);
}
.doclib-select-cb:checked,
.doclib-bulk-check-all input:checked {
background: var(--red);
border-color: var(--red);
}
.doclib-select-cb:checked::after,
.doclib-bulk-check-all input:checked::after {
content: '';
position: absolute;
left: 3px;
top: 0px;
width: 4px;
height: 8px;
border: solid var(--bg);
border-width: 0 1.5px 1.5px 0;
transform: rotate(45deg);
}
/* + button outside scroll area (when doc is on right side) */
@media (max-width: 600px) {
.doclib-toolbar {
flex-direction: column;
}
.doclib-card-session,
.doclib-card-time {
display: none;
}
.doclib-card-preview {
max-height: 40vh;
}
}
/* Collapse card tags before they compete with attachment/favorite controls. */
.email-card-tags .email-tags-more {
color: var(--email-tags-more-color, var(--fg));
background: color-mix(in srgb, var(--email-tags-more-color, var(--fg)) 18%, transparent);
}
.email-tags-more-single,
[data-email-tags-count-mini] { display: none; }
.email-tags-more-single svg { display: none; }
.email-card-tags.email-tags-mini:not(.email-tags-expanded) > .email-tag,
.email-card-tags.email-tags-mini:not(.email-tags-expanded) > .email-tag-extra {
display: none;
}
.email-card-tags.email-tags-mini:not(.email-tags-expanded) .email-tags-more-single,
.email-card-tags.email-tags-expanded .email-tags-more-single {
display: inline-flex;
}
.email-card-tags.email-tags-mini:not(.email-tags-expanded) [data-email-tags-count-normal] { display: none; }
.email-card-tags.email-tags-mini:not(.email-tags-expanded) [data-email-tags-count-mini],
.email-card-tags.email-tags-expanded .email-tags-more-single [data-email-tags-count-mini] { display: inline; }
/* Keep the reader actions in drafting order. */
.email-reader-actions-inline > [data-act="ai-reply"] { order: 1; }
.email-reader-actions-inline > [data-act="reply"] { order: 2; }
.email-reader-actions-inline > [data-act="reply-all"] { order: 3; }
.email-reader-actions-inline > [data-act="forward"] { order: 4; }