/* ClankerSaddle application stylesheet โ€” a THIN app-component layer on top of the Spry CSS framework (see docs/spry-conversion.md). Loaded AFTER spry.css. Every primitive Spry ships (typography, buttons, forms, tables, cards, spacing, dialogs, badges, expanders) is USED, not redefined: this file only carries ClankerSaddle's own components โ€” status badges, the goal identity, the event log, chats, the terminal, toasts โ€” and every colour is consumed through --spry-* custom properties so themes and dark mode just work. Sections are append-only: add new components under a header comment at the end of the matching section, never inline in the middle of another component's block. */ /* ========================================================================== App tokens ========================================================================== */ :root { /* Goal identity (plan 12): goals never read as tasks at a glance โ€” their chrome carries this instead of the task accent. Derived from the Spry accent so themes can shift it coherently. */ --goal: var(--spry-accent); /* A soft tint for goal surfaces (goalui pile cards). */ --goal-surface: var(--spry-accent-soft); } /* ========================================================================== Shared chrome ========================================================================== */ /* The emblem rides the topbar brand at its original 8rem size (the sampler review's explicit ask; the first conversion pass had restrained it to 2rem, which read as too small in review). */ .brand-emblem { height: 8rem; width: auto; margin-block: -0.2rem; } /* The account flyout sits at the nav's right edge; its panel right-aligns so it never leaves the viewport. */ .nav-auth { display: inline-flex; gap: var(--spry-space-1); margin-inline-start: auto; } .nav-auth .submenu { left: auto; right: 0; } /* The signed-in name leads the account menu, set off quietly. */ .nav-user { color: var(--spry-fg-muted); font-weight: 600; padding: 0.4rem 0.75rem; font-size: 0.95em; } /* The goals nav entry keeps its weight (plan 12: goals are a peer of tasks, not a subpage). */ .nav-goal { font-weight: 600; } .nav-goal .goal-icon { font-style: normal; margin-right: 0.15rem; } /* The ๐ŸŽฏ marker rides the nav entry, the goals pages and the goal pile cards. */ .goal-icon { font-style: normal; margin-right: 0.15rem; } /* The goal head strip: id, title and lifecycle badges in one line. */ .goal-strip { display: flex; gap: var(--spry-space-2); flex-wrap: wrap; align-items: center; } /* A quiet page footer under the container. */ .site-footer { border-top: var(--spry-border-width) solid var(--spry-border); color: var(--spry-fg-muted); font-size: 0.85rem; margin-top: var(--spry-space-5); padding: var(--spry-space-3) var(--spry-space-2); text-align: center; } /* ========================================================================== Login ========================================================================== */ .login { background: radial-gradient(60rem 24rem at 50% -8rem, var(--spry-accent-soft), transparent 70%), var(--spry-bg); display: flex; justify-content: center; min-height: 100vh; padding: var(--spry-space-5) var(--spry-space-2); } .login-card { align-self: center; max-width: 24rem; text-align: center; width: 100%; } .login-card .login-emblem { height: 6rem; width: auto; margin-inline: auto; } .login-card h1 { font-size: 1.5rem; margin: var(--spry-space-2) 0 0; } .login-card form { text-align: left; } .login-card form .field { margin-bottom: var(--spry-space-3); } .login-card button[type="submit"] { width: 100%; } /* ========================================================================== Status badges โ€” the badge-colour contract (docs/spry-conversion.md) ========================================================================== */ /* Spry's .badge is the base (accent tint). Statuses map onto the semantic Spry vars; page tasks extend this contract, never hard-coded hex. */ .badge-draft, .badge-paused, .badge-cancelled, .badge-offline, .badge-reference, .badge-command, .badge-shell-reset, .badge-created, .badge-goal-paused, .badge-goal-cancelled, .badge-goal-created, .badge-goal-task-deleted, .badge-suggestion-declined, .badge-mode-implement { background: var(--spry-bg-alt); color: var(--spry-fg-muted); } .badge-queued, .badge-goal-task-enqueued { background: var(--spry-warning-soft); color: var(--spry-warning); } .badge-assigned, .badge-co-developed, .badge-user-comment, .badge-question, .badge-planning, .badge-goal-started, .badge-goal-resumed, .badge-goal-task-created, .badge-started, .badge-dispatch, .badge-query, .badge-mode-investigate, .badge-chat-awaiting_answer, .badge-kind-task, .badge-state_change { background: var(--spry-info-soft); color: var(--spry-info); } .badge-working, .badge-blocked, .badge-block, .badge-block-raised, .badge-goal-block-raised, .badge-suspended, .badge-review_requested, .badge-review, .badge-completion_review, .badge-mode-review, .badge-goal-completion-submitted, .badge-block-completion, .badge-busy, .badge-chat-running, .badge-suggestion-pending { background: var(--spry-warning-soft); color: var(--spry-warning); } .badge-done, .badge-idle, .badge-progress, .badge-complete, .badge-active, .badge-goal-plan-approved, .badge-goal-goal-approved, .badge-goal-task-done, .badge-block-answered, .badge-completed, .badge-mode-test, .badge-chat-idle, .badge-suggestion-approved { background: var(--spry-success-soft); color: var(--spry-success); } .badge-failed, .badge-goal-plan-rejected, .badge-goal-completion-rejected, .badge-goal-goal-failed, .badge-goal-task-failed, .badge-event-cancel { background: var(--spry-danger-soft); color: var(--spry-danger); } .badge-comment, .badge-goal-comment, .badge-tool { background: var(--spry-bg-inset); color: var(--spry-fg-muted); } .badge-plan, .badge-plan_review, .badge-rfc, .badge-mode-orchestrate, .badge-goal-plan-submitted, .badge-block-plan, .badge-admin, .badge-capability, .badge-kind-goal { background: var(--spry-accent-soft); color: var(--spry-accent); } .badge-yeehaw { background: var(--spry-warning-soft); color: var(--spry-warning); } /* Health badges on the saddles page: filled, readable on their own tint, semantic vars under. */ .badge-health-healthy { background: var(--spry-success); color: var(--spry-accent-fg); } .badge-health-stale { background: var(--spry-warning); color: var(--spry-accent-fg); } .badge-health-fenced { background: var(--spry-danger-soft); color: var(--spry-danger); } .badge-health-offline { background: var(--spry-danger); color: var(--spry-accent-fg); } .badge-health-unknown { background: var(--spry-bg-inset); color: var(--spry-fg-muted); } /* The ร— inside a capability chip (badge) */ .chip-remove { background: none; border: none; color: inherit; cursor: pointer; font-size: 0.85rem; line-height: 1; margin-left: 0.25rem; padding: 0; } /* ========================================================================== Cards & layout shims ========================================================================== */ /* A responsive card grid (Spry has no auto-fill card grid). */ .cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr)); gap: var(--spry-space-3); margin: var(--spry-space-3) 0; } .card h3 { margin: 0 0 var(--spry-space-2); } /* The task page's status block โ€” the left cell of the status/ description grid (plan 11 task D). */ .status-head { border-left: 4px solid var(--spry-accent); } /* Status + description side by side (plan 11 task D); stacks status-then-description on small screens. */ .grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--spry-space-3); align-items: start; margin: var(--spry-space-3) 0; } /* The status card's corner badge floats right of its heading. */ .status-corner { float: right; font-size: 0.9rem; } /* Edit forms keep a readable measure โ€” the .field stack (Spry) plus this cap for long inputs. */ .form-rows { display: flex; flex-direction: column; gap: var(--spry-space-3); } .form-rows .field { max-width: 34rem; } /* ========================================================================== Markdown bodies (stm-markdown) ========================================================================== */ /* Spry styles bare markdown elements; the app's .md containers tame the page-level heading scale inside cards โ€” an agent report's h1 is a section head, not the page title. */ .md h1 { font-size: 1.35rem; margin: var(--spry-space-3) 0 var(--spry-space-2); } .md h2 { font-size: 1.2rem; margin: var(--spry-space-3) 0 var(--spry-space-2); } .md h3 { font-size: 1.05rem; margin: var(--spry-space-3) 0 var(--spry-space-2); } .md p, .md ul, .md ol { margin: var(--spry-space-2) 0; } .md > :first-child { margin-top: 0; } .md > :last-child { margin-bottom: 0; } /* ========================================================================== Comments ========================================================================== */ /* Footnote treatment (plan 13 B4 / plan 15 C4): comments read as muted, smaller footnotes โ€” the body leads, the chrome trails. */ .comment { background: var(--spry-surface); border: var(--spry-border-width) solid var(--spry-border); border-left: 3px solid var(--spry-border); border-radius: var(--spry-radius); color: var(--spry-fg-muted); font-size: 0.88rem; margin: var(--spry-space-2) 0; padding: var(--spry-space-2) var(--spry-space-3); } .comment.agent { border-left-color: var(--spry-accent); } /* Harness/system-authored rows render as a distinct SYSTEM event (plan 15 C2): labelled, muted, dashed rail, never an agent row. */ .comment.system { background: var(--spry-bg-inset); border-left: 3px dashed var(--spry-border); font-size: 0.82rem; } .comment.system .system-label { border: var(--spry-border-width) solid var(--spry-border); border-radius: var(--spry-radius-pill); color: var(--spry-fg-muted); font-size: 0.7rem; font-weight: 600; letter-spacing: 0.04em; margin-right: 0.35rem; padding: 0.05rem 0.5rem; text-transform: uppercase; } .comment blockquote { border-left: 3px solid var(--spry-border); color: var(--spry-fg-muted); margin: var(--spry-space-2) 0; padding: 0 var(--spry-space-2); white-space: pre-wrap; } .comment .comment-meta { margin: var(--spry-space-2) 0 0; } /* The comment footnote utility (dashboard timeline rows, goal narrative events). */ .footnote { color: var(--spry-fg-muted); font-size: 0.85rem; } /* ========================================================================== Blocks (questions, RFCs, reviews) ========================================================================== */ .block-card { background: var(--spry-surface); border: var(--spry-border-width) solid var(--spry-border); border-left: 4px solid var(--spry-accent); border-radius: var(--spry-radius); margin: var(--spry-space-3) 0; padding: var(--spry-space-3); } .block-card.rfc { border-left-color: var(--spry-accent); } .block-card.review { border-left-color: var(--spry-warning); } .block-card.answered { border-left-color: var(--spry-border); opacity: 0.85; } .block-card .options { display: flex; flex-direction: column; gap: var(--spry-space-2); margin: var(--spry-space-2) 0; } .block-card .options label { display: flex; gap: var(--spry-space-2); align-items: baseline; font-weight: normal; } .block-card textarea { min-height: 4rem; } /* Clickable option cards: the whole label selects its radio, and a checked card highlights (evergreen :has). */ .options .option-card { align-items: center; display: block; margin-bottom: var(--spry-space-2); border: var(--spry-border-width) solid var(--spry-border); border-radius: var(--spry-radius); cursor: pointer; padding: var(--spry-space-2) var(--spry-space-3); } .options .option-card:hover { border-color: var(--spry-accent); } .options .option-card:has(input:checked) { background: var(--spry-accent-soft); border-color: var(--spry-accent); } /* ========================================================================== Plan review & review banner ========================================================================== */ .plan-review .actions { display: flex; gap: var(--spry-space-2); flex-wrap: wrap; margin: var(--spry-space-2) 0; } .plan-review .rfc-body { border: var(--spry-border-width) solid var(--spry-border); border-radius: var(--spry-radius); margin: var(--spry-space-2) 0; padding: var(--spry-space-3); } .plan-comments { list-style: none; margin: var(--spry-space-2) 0; padding: 0; } .plan-comments li { border: var(--spry-border-width) solid var(--spry-border); border-left: 3px solid var(--spry-accent); border-radius: var(--spry-radius); margin: var(--spry-space-2) 0; padding: var(--spry-space-2) var(--spry-space-3); } .plan-comments blockquote { margin: 0 0 var(--spry-space-2); } .plan-popover { background: var(--spry-popover-bg); border: var(--spry-border-width) solid var(--spry-popover-border); border-radius: var(--spry-radius); box-shadow: var(--spry-popover-shadow); padding: var(--spry-space-3); position: absolute; width: 22rem; z-index: 40; } .plan-popover .actions { display: flex; gap: var(--spry-space-2); margin-top: var(--spry-space-2); } /* The review banner: a warning-tinted alert panel. */ .review-banner { background: var(--spry-warning-soft); border: var(--spry-border-width) solid var(--spry-warning); border-radius: var(--spry-radius); margin: var(--spry-space-3) 0; padding: var(--spry-space-3); } .review-banner h3 { color: var(--spry-warning); margin: 0 0 var(--spry-space-2); } .review-banner .actions { display: flex; gap: var(--spry-space-2); flex-wrap: wrap; margin-top: var(--spry-space-2); } /* ========================================================================== Progress ========================================================================== */ /* The goal page's card rhythm (plan 15 C4): every section is a card and the cards breathe between themselves. */ .goal-page .card { margin-bottom: var(--spry-space-3); } .goal-page .card:last-child { margin-bottom: 0; } .progress-latest { background: var(--spry-success-soft); border: var(--spry-border-width) solid var(--spry-success); border-radius: var(--spry-radius); font-weight: 600; margin: var(--spry-space-2) 0; padding: var(--spry-space-2) var(--spry-space-3); } .progress-history li { margin: var(--spry-space-2) 0; } /* ========================================================================== Goal identity ========================================================================== */ /* The goal page's status card: the goal accent replaces the task accent of .status-head. */ .goal-head { border-left: 4px solid var(--goal); } /* Dashboard pile cards for goal items: goal accent + tinted surface. */ .card.goal-card { background: var(--goal-surface); border-left: 4px solid var(--goal); } /* The supervision narrative's markdown bodies flow inside the flex event row. */ .goal-event-text { flex: 1 1 16rem; min-width: 0; } .goal-event-text > :first-child { margin-top: 0; } .goal-event-text > :last-child { margin-bottom: 0; } /* The current-activity card on the goal page. */ .goal-activity code { word-break: break-all; } /* Filter pills (goal page, event log toolbar). */ .goal-filter, .log-filter { background: transparent; border: var(--spry-border-width) solid var(--spry-border); border-radius: var(--spry-radius-pill); color: var(--spry-fg-muted); cursor: pointer; font-size: 0.8rem; padding: 0.15rem 0.7rem; } .goal-filter:hover, .log-filter:hover { border-color: var(--spry-fg-muted); color: var(--spry-fg); } .goal-filter.active, .log-filter.active { background: var(--spry-accent-soft); border-color: var(--spry-accent); color: var(--spry-accent); } .goal-event { border-left: 2px solid var(--spry-border); display: flex; gap: 0.5rem; flex-wrap: wrap; margin: 0.35rem 0; padding-left: 0.6rem; } /* Plan 14 B6: comment events read as FOOTNOTES in the narrative. */ .goal-event.footnote { border-left-color: transparent; font-size: 0.85rem; margin: 0.15rem 0; opacity: 0.75; padding-left: 0.6rem; } /* Plan 14 B5: the goal's Rules section rows. */ .rule-card { border: var(--spry-border-width) solid var(--spry-border); border-radius: var(--spry-radius-sm); margin: var(--spry-space-2) 0; padding: var(--spry-space-2) var(--spry-space-3); } .rule-card > h4 { margin: 0 0 var(--spry-space-2); } .plan-revision { margin: 0.5rem 0; } /* ========================================================================== Text-selection quote button (the comment popover's only survivor) ========================================================================== */ /* T11 sweep: the terminal surface (.terminal + the .term-entry family, ~120 lines) and the .quote-popover pair are deleted โ€” the terminal page died with plan 17's event-log migration and app.js's quote engine creates only this floating .quote-button + the dialog. The event log's own row grammar lives in the Event log section. */ .quote-button { background: var(--spry-accent); border: none; border-radius: var(--spry-radius-sm); color: var(--spry-accent-fg); font-size: 0.8rem; padding: 0.3rem 0.7rem; position: absolute; z-index: 41; } /* ========================================================================== Toasts ========================================================================== */ .toast-stack { bottom: var(--spry-space-3); display: flex; flex-direction: column; gap: var(--spry-space-2); max-width: min(24rem, calc(100vw - 3rem)); position: fixed; right: var(--spry-space-3); z-index: 100; } .toast { animation: toast-in 0.25s ease-out; background: var(--spry-surface); border-left: 4px solid var(--spry-accent); border-radius: var(--spry-radius-sm); box-shadow: var(--spry-shadow-lg); font-size: 0.9rem; padding: var(--spry-space-2) var(--spry-space-3); } .toast.success { border-left-color: var(--spry-success); } .toast.error { border-left-color: var(--spry-danger); } .toast.leaving { animation: toast-out 0.25s ease-in forwards; } @keyframes toast-in { from { opacity: 0; transform: translateX(1rem); } to { opacity: 1; transform: translateX(0); } } @keyframes toast-out { from { opacity: 1; transform: translateX(0); } to { opacity: 0; transform: translateX(1rem); } } /* ========================================================================== Comment dialog (quote โ†’ comment) ========================================================================== */ dialog#quote-comment-dialog blockquote { background: var(--spry-bg-inset); border-left: 3px solid var(--spry-accent); font-family: var(--spry-font-mono); font-size: 0.85rem; margin: 0 0 var(--spry-space-3); max-height: 10rem; overflow-y: auto; padding: var(--spry-space-3); white-space: pre-wrap; } .term-comment-quote { color: var(--spry-success); font-style: italic; } /* ========================================================================== Event log (task_log.html) โ€” the live console ========================================================================== */ .log-page h2 { margin: 0; } .log-back { margin: 0 0 var(--spry-space-2); } .log-header { align-items: flex-end; display: flex; flex-wrap: wrap; gap: var(--spry-space-3); justify-content: space-between; margin-bottom: var(--spry-space-3); } .log-title .muted { margin: 0.25rem 0 0; } .log-actions { align-items: center; display: flex; gap: var(--spry-space-2); } .log-live { align-items: center; background: var(--spry-success-soft); border: var(--spry-border-width) solid var(--spry-success); border-radius: var(--spry-radius-pill); color: var(--spry-success); display: inline-flex; font-size: 0.8rem; gap: 0.4rem; padding: 0.2rem 0.7rem; } .log-live.retry { background: var(--spry-warning-soft); border-color: var(--spry-warning); color: var(--spry-warning); } /* A non-live task (parked, blocked, done...) names its state: the status palette carries the colour, the dot holds still. */ .log-live.still .live-dot { animation: none; opacity: 0.85; } .log-live.still.badge-suspended { background: var(--spry-accent-soft); border-color: var(--spry-accent); color: var(--spry-accent); } .log-live.still.badge-draft { background: var(--spry-bg-alt); border-color: var(--spry-border); color: var(--spry-fg-muted); } .log-live.still.badge-queued { background: var(--spry-warning-soft); border-color: var(--spry-warning); color: var(--spry-warning); } .log-live.still.badge-blocked, .log-live.still.badge-review_requested { background: var(--spry-warning-soft); border-color: var(--spry-warning); color: var(--spry-warning); } .log-live.still.badge-done { background: var(--spry-success-soft); border-color: var(--spry-success); color: var(--spry-success); } .log-live.still.badge-failed { background: var(--spry-danger-soft); border-color: var(--spry-danger); color: var(--spry-danger); } .live-dot { animation: live-pulse 1.6s ease-in-out infinite; background: currentColor; border-radius: 50%; height: 0.5rem; width: 0.5rem; } @keyframes live-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } } .log-shell { background: var(--spry-surface); border: var(--spry-border-width) solid var(--spry-border); border-radius: var(--spry-radius); box-shadow: var(--spry-shadow); overflow: hidden; } .log-toolbar { align-items: center; background: var(--spry-bg-alt); border-bottom: var(--spry-border-width) solid var(--spry-border); display: flex; flex-wrap: wrap; gap: var(--spry-space-2); padding: var(--spry-space-2) var(--spry-space-3); } .log-toolbar-label { color: var(--spry-fg-muted); font-size: 0.8rem; } .log-toolbar input[type="search"] { background: var(--spry-surface); border: var(--spry-border-width) solid var(--spry-border); border-radius: var(--spry-radius-pill); color: var(--spry-fg); flex: 1; min-width: 12rem; padding: 0.3rem 0.9rem; } .log-toolbar input[type="search"]::placeholder { color: var(--spry-fg-muted); } .log-more { background: transparent; border: 1px dashed var(--spry-border); color: var(--spry-accent); display: block; margin: 0 auto var(--spry-space-3); padding: 0.25rem 0.9rem; } .log-more:hover { background: var(--spry-bg-alt); text-decoration: none; } .log-feed { display: flex; flex-direction: column; gap: var(--spry-space-2); max-height: 75vh; overflow-y: auto; padding: var(--spry-space-3); scrollbar-color: var(--spry-border) transparent; scrollbar-width: thin; } .log-feed::-webkit-scrollbar { width: 10px; } .log-feed::-webkit-scrollbar-thumb { background: var(--spry-border); border-radius: 5px; } .log-feed::-webkit-scrollbar-track { background: transparent; } .log-empty { color: var(--spry-fg-muted); padding: var(--spry-space-4) 0; text-align: center; } /* Turns hang from a rail; their events sit under it. */ .turn-card { border-left: 2px solid var(--spry-border); display: flex; flex-direction: column; gap: 0.4rem; margin: var(--spry-space-2) 0; padding: 0.2rem 0 0.2rem var(--spry-space-3); } .turn-head { align-items: center; color: var(--spry-fg-muted); display: flex; font-size: 0.7rem; gap: 0.5rem; letter-spacing: 0.06em; margin: 0; text-transform: uppercase; } .turn-chip { background: var(--spry-accent-soft); border: var(--spry-border-width) solid var(--spry-accent); border-radius: var(--spry-radius-pill); color: var(--spry-accent); padding: 0.05rem 0.6rem; } /* Event rows: a glyph gutter carries the kind, the chip names it. */ .event-row { border-radius: var(--spry-radius-sm); padding: 0.1rem 0.4rem 0.1rem 1.8rem; position: relative; } .event-row:hover { background: var(--spry-bg-alt); } .event-row::before { color: var(--spry-fg-muted); content: "\00b7"; display: inline-block; font-family: var(--spry-font-mono); font-size: 0.9rem; left: 0.35rem; position: absolute; text-align: center; top: 0.2rem; width: 1rem; } .event-row.event-kind-tool_call::before { color: var(--spry-accent); content: "\25b8"; } .event-row.event-kind-tool_result::before { content: "\21b3"; } .event-row.event-kind-turn_text::before { color: var(--spry-accent); content: "\2726"; } .event-row.event-kind-command_output::before { color: var(--spry-accent); content: "\203a"; } .event-row.event-kind-progress::before { color: var(--spry-success); content: "\25c6"; } .event-row.event-kind-comment::before { color: var(--spry-warning); content: "\201c"; } .event-row.event-kind-answer::before { color: var(--spry-success); content: "\2713"; } .event-row.event-kind-verdict::before { color: var(--spry-warning); content: "\2261"; } .event-row.event-kind-usage::before { content: "\2211"; } .event-row.event-kind-state_change::before { color: var(--spry-accent); content: "\2192"; } .event-row.event-kind-lifecycle::before { color: var(--spry-accent); content: "\25cf"; } .event-row.event-kind-force_wake::before { color: var(--spry-warning); content: "\21d1"; } .event-row.event-kind-cancel::before { color: var(--spry-danger); content: "\00d7"; } .event-row.event-kind-yeehaw::before { color: var(--spry-warning); content: "\2605"; } .event-row .event-head { align-items: baseline; display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0.15rem 0; } .log-feed .badge { border-radius: var(--spry-radius-pill); font-size: 0.7rem; letter-spacing: 0.03em; padding: 0.05rem 0.55rem; } .event-kind-turn_text { background: var(--spry-accent-soft); color: var(--spry-accent); } .event-kind-tool_call { background: var(--spry-info-soft); color: var(--spry-info); } .event-kind-command_output { background: var(--spry-info-soft); color: var(--spry-info); } .event-kind-tool_result, .event-kind-usage { background: var(--spry-bg-alt); color: var(--spry-fg-muted); } .event-kind-progress, .event-kind-answer { background: var(--spry-success-soft); color: var(--spry-success); } .event-kind-comment, .event-kind-verdict, .event-kind-force_wake { background: var(--spry-warning-soft); color: var(--spry-warning); } .event-kind-state_change { background: var(--spry-info-soft); color: var(--spry-info); } .event-kind-lifecycle { background: var(--spry-accent-soft); color: var(--spry-accent); } .event-kind-cancel { background: var(--spry-danger-soft); color: var(--spry-danger); } .event-kind-yeehaw { background: var(--spry-warning-soft); color: var(--spry-warning); } .log-feed p { margin: 0.15rem 0; } .event-pre { background: var(--spry-bg-inset); border: var(--spry-border-width) solid var(--spry-border); border-radius: var(--spry-radius-sm); color: var(--spry-fg); font-size: 0.8rem; margin: 0.2rem 0; max-height: 20rem; overflow: auto; padding: 0.3rem 0.5rem; white-space: pre-wrap; word-break: break-word; } .event-pre.event-command { background: var(--spry-info-soft); border-color: var(--spry-info); color: var(--spry-info); } .event-pre.event-stderr { background: var(--spry-danger-soft); border-color: var(--spry-danger); color: var(--spry-danger); } .event-turn { background: transparent; border-color: var(--spry-accent); border-left: 3px solid var(--spry-accent); color: var(--spry-fg-muted); } .event-question { color: var(--spry-warning); font-style: italic; } .event-usage { color: var(--spry-fg-muted); font-family: var(--spry-font-mono); font-size: 0.8rem; } .event-fold { margin: 0.2rem 0; } .event-fold > summary { color: var(--spry-fg-muted); cursor: pointer; font-size: 0.8rem; } .event-fold > summary:hover { color: var(--spry-fg); } .event-fold[open] > summary { margin-bottom: 0.2rem; } /* Tool results: short ones render inline under a quiet metric line; long ones collapse behind the coloured summary. */ .event-result > summary { color: var(--spry-success); cursor: pointer; font-family: var(--spry-font-mono); font-size: 0.8rem; } .event-result > summary.failed { color: var(--spry-danger); } .event-result > summary::marker { color: var(--spry-fg-muted); } /* Tool results ride attached to their call. */ .event-attached { border-left: 2px solid var(--spry-border); margin: 0.3rem 0 0.1rem 0.4rem; padding-left: 0.75rem; } .event-attached > .result-meta { margin: 0; } .event-attached .event-result { margin: 0.1rem 0; } .event-attached .event-pre { max-height: 16rem; overflow: auto; } .event-result-body { margin-top: 0.3rem; } .result-ok { color: var(--spry-success); } .result-meta { color: var(--spry-fg-muted); font-family: var(--spry-font-mono); font-size: 0.75rem; } .result-tasks { margin: 0.2rem 0; padding-left: 1.2rem; } .result-tasks li { margin: 0.15rem 0; } .result-tasks .badge { margin-left: 0.5rem; } .log-feed blockquote { border-left: 3px solid var(--spry-border); color: var(--spry-fg); margin: 0.2rem 0; padding: 0 0.75rem; } .log-feed .yeehaw-message { color: var(--spry-warning); } .event-diff { background: var(--spry-surface); border: var(--spry-border-width) solid var(--spry-border); border-radius: var(--spry-radius-sm); font-family: var(--spry-font-mono); font-size: 0.8rem; margin: 0.2rem 0; max-height: 24rem; overflow: auto; padding: 0.3rem 0; } .diff-line { padding: 0 0.6rem; white-space: pre-wrap; word-break: break-word; } @media (max-width: 40rem) { .log-header { align-items: flex-start; flex-direction: column; } } /* ========================================================================== Advisor chats (goal 27) ========================================================================== */ /* The dashboard's hero composer: the page's FIRST and predominant control, above every dashboard section. */ .advisor-hero { border-color: var(--spry-accent); margin-bottom: var(--spry-space-4); } .advisor-hero .notice { margin-bottom: 0; } /* The hero's own block form โ€” deliberately NOT an inline cluster (it would squeeze the textarea) nor a capped .field (the 34rem column cap is for edit forms, not a hero): a full-width label/textarea/ actions stack with a roomy, larger-type composer. */ .advisor-hero-form { display: flex; flex-direction: column; gap: var(--spry-space-2); margin: 0 0 var(--spry-space-2); } .advisor-hero-form label { font-weight: 600; font-size: 0.95rem; } .advisor-hero-form textarea { width: 100%; min-height: 4.5rem; resize: vertical; font-size: 1.1rem; line-height: 1.45; } .advisor-hero-form .advisor-hero-actions { display: flex; justify-content: flex-end; } /* The chat page: a column of transcript lanes under the head. */ .chat-page { display: flex; flex-direction: column; gap: var(--spry-space-3); } .chat-head h2 { margin-bottom: 0.25rem; } .chat-head .chat-delete { margin-left: var(--spry-space-2); } .chat-thread { background: var(--spry-surface); border: var(--spry-border-width) solid var(--spry-border); border-radius: var(--spry-radius); display: flex; flex-direction: column; gap: var(--spry-space-2); max-height: 60vh; overflow-y: auto; padding: var(--spry-space-3); } /* Bubbles: the user's rows hug the right, the advisor's the left. */ .chat-row { display: flex; flex-direction: column; } .chat-row-user { align-items: flex-end; } .chat-row-assistant, .chat-row-system, .chat-row-error { align-items: flex-start; } .chat-bubble { border: var(--spry-border-width) solid var(--spry-border); border-radius: var(--spry-radius); max-width: 46rem; padding: var(--spry-space-2) var(--spry-space-3); } .chat-bubble .md, .chat-bubble .md p { margin: 0; } .chat-bubble .md p + p { margin-top: 0.5rem; } .chat-user { background: var(--spry-info-soft); border-color: var(--spry-info); } .chat-assistant { background: var(--spry-bg-alt); } .chat-bubble .muted { font-size: 0.75rem; margin: 0.25rem 0 0; } /* Tool calls sit between the lanes as collapsed details. */ .chat-tool { width: 100%; } .chat-tool details { background: var(--spry-bg-alt); border: var(--spry-border-width) solid var(--spry-border); border-radius: var(--spry-radius-sm); font-size: 0.85rem; padding: 0.35rem 0.75rem; } .chat-tool summary { cursor: pointer; } .chat-tool .badge { margin-right: 0.5rem; } .chat-tool .muted { margin: 0.4rem 0 0.15rem; } .chat-tool pre { background: var(--spry-surface); border: var(--spry-border-width) solid var(--spry-border); border-radius: var(--spry-radius-sm); font-size: 0.78rem; margin: 0; max-height: 18rem; overflow: auto; padding: 0.5rem 0.6rem; white-space: pre-wrap; word-break: break-word; } /* Errors stand out; system notices stay quiet. */ .chat-error { width: 100%; } .chat-error p { background: var(--spry-danger-soft); border: var(--spry-border-width) solid var(--spry-danger); border-radius: var(--spry-radius); color: var(--spry-danger); margin: 0; padding: var(--spry-space-2) var(--spry-space-3); } .chat-system { width: 100%; } .chat-system p { border-left: 3px solid var(--spry-border); color: var(--spry-fg-muted); margin: 0; padding-left: 0.75rem; } /* A parked question: the bubble plus its answer card. */ .chat-question { align-items: flex-start; display: flex; flex-direction: column; } .chat-question-card { margin-top: var(--spry-space-2); padding: var(--spry-space-2) var(--spry-space-3); } .chat-question-card .muted { margin: 0 0 var(--spry-space-2); } .chat-options { display: flex; flex-wrap: wrap; gap: var(--spry-space-2); margin-bottom: var(--spry-space-2); } .chat-options button { cursor: pointer; } /* The thinking indicator, while a turn runs. */ .chat-thinking p { animation: chat-think 1.2s ease-in-out infinite; font-style: italic; margin: 0; } @keyframes chat-think { 50% { opacity: 0.45; } } /* Suggestion cards: the decision buttons row. */ .chat-suggestion-actions { display: flex; flex-wrap: wrap; gap: var(--spry-space-2); margin-top: var(--spry-space-2); } /* The composer: a textarea beside its send button. */ .chat-composer-row { display: flex; gap: var(--spry-space-2); align-items: flex-end; } .chat-composer-row textarea { flex: 1; resize: vertical; } .chat-composer-row button:disabled, .chat-composer-row textarea:disabled { opacity: 0.6; } @media (max-width: 40rem) { .cards { grid-template-columns: 1fr; } .grid-2 { grid-template-columns: 1fr; } .chat-bubble { max-width: 100%; } .chat-composer-row { flex-direction: column; align-items: stretch; } } /* The yeehaw celebration row (T11: .yeehaw-card/.yeehaw-list deleted โ€” the earned-yeehaw card surface died with the entry stream; the live celebration is the log's .yeehaw-message span, pinned by test_task_log_js.py). */ .yeehaw-message { font-weight: 600; margin-right: var(--spry-space-2); } /* The saddle's SSH public key display: a quiet monospace panel. */ .ssh-key { background: var(--spry-bg-inset); border: var(--spry-border-width) solid var(--spry-border); border-radius: var(--spry-radius-sm); color: var(--spry-fg-muted); font-size: 0.75rem; margin: 0.5em 0; max-height: 7em; overflow: auto; padding: 0.5em 0.75em; white-space: pre-wrap; word-break: break-all; } /* ========================================================================== Dashboard attention-first layout (goal 41 task 3) Page-specific rules for home.html ONLY โ€” appended per the spry conversion contract; every colour/spacing value consumes a --spry-* token so themes and auto dark mode work with zero extra rules. ========================================================================== */ /* The attention/rail split: Spry's .with-sidebar gives the rail a minmax(11rem, 16rem) first column; the dashboard wants the rail on the RIGHT and WIDER (metrics + saddle list + bell strip), the attention column taking the rest. */ .dash-board { grid-template-columns: minmax(0, 1fr) minmax(14rem, 19rem); } /* app.css loads after spry.css, so the re-proportioned columns above would out-rank the framework's own 55rem stacking rule inside its @media block (media queries add no specificity). Restore the stack here so the rail drops under the attention column on narrow screens. */ @media (max-width: 55rem) { .dash-board { grid-template-columns: minmax(0, 1fr); } } /* Section headings inside the attention column: quieter than the page's card headings so the pinned cards stay the visual lead. */ .dash-heading { font-size: 1.15rem; margin-bottom: var(--spry-space-2); } .dash-heading .badge { margin-left: var(--spry-space-2); vertical-align: middle; } /* The rail's metric lead line: the number reads larger than the card's prose so the one-glance verdict lands first. */ .dash-metric { font-size: 1.05rem; margin: 0 0 var(--spry-space-1); } .dash-metric strong { font-size: 1.6rem; font-weight: 650; line-height: 1; margin-right: var(--spry-space-1); } /* The rail's saddle list: a compact status list (ux-notes ยง1 B), one line per saddle โ€” badge, name, current work โ€” instead of the old full card grid. */ .dash-saddles { display: flex; flex-direction: column; gap: var(--spry-space-1); list-style: none; margin: 0 0 var(--spry-space-2); max-width: 100%; padding: 0; } .dash-saddles li { align-items: baseline; display: flex; flex-wrap: wrap; font-size: 0.85rem; gap: 0 var(--spry-space-1); } .dash-saddles .badge { font-size: 0.7rem; margin-right: var(--spry-space-1); } /* Ambient zones (activity, notes, discoveries): reference material, deliberately flat โ€” the rules below keep Spry's expander chrome from boxing them into card-weight sections that would compete with the attention column above (the goal-41 direction review: collapsed zones read as ruled rows, not cards). */ .dash-ambient > summary { color: var(--spry-fg-muted); font-size: 0.95rem; font-weight: 600; } .dash-ambient > summary .badge { font-size: 0.7rem; font-weight: 400; } .dash-ambient-body { border-left: 2px solid var(--spry-border); margin: var(--spry-space-1) 0 var(--spry-space-3) var(--spry-space-2); padding: 0 0 0 var(--spry-space-3); } /* Activity cap (ux-notes ยง1 C): eight rows visible until "Show all" flips [data-activity-all]; the count badge still covers the whole window the slot carries. T11: the two .dash-activity-table rules are GONE โ€” T10's work stream replaced the activity table surface (the t10views bundle asserts its absence), so the cap rules were dead selectors. */ /*/* The advisor hero's in-card heading copy (the direction review's composer shape): the description sits inside the composer card, above the textarea; the chats link sits with the action, left. Same specificity as the goal-27 rule above โ€” this section is later in the file, so it wins the tie without editing the earlier rule. */ .advisor-hero-lede { margin: calc(-1 * var(--spry-space-1)) 0 0; } .advisor-hero-form .advisor-hero-actions { align-items: baseline; justify-content: space-between; } .advisor-hero-form .advisor-hero-actions .advisor-hero-link { margin: 0; } /* ========================================================================== Goal 41 T4 โ€” tasks/goals/projects/stacks list pages (append-only) The faceted status chips reuse .goal-strip/.goal-filter above; this section only adds what the pages needed that no layer owned. ========================================================================== */ /* A page head with its primary action: the h1/h2 on the left, the "+ New โ€ฆ" button on the right, wrapping under on narrow screens (.cluster gives the wrap + gap; only the space-between is ours). */ .cluster-between { justify-content: space-between; } /* The projects list's folded "repo" cell (ux-notes projects list): the clone URL truncated on one mono line with the branch under it. .truncate needs a width bound inside a table cell to bite. */ td .truncate.repo-url { display: block; max-width: 22rem; } /* ========================================================================== Goal 41 T5 โ€” task detail Act|Consult layout, anchor sub-nav, sticky log toolbar (append-only section per the spry-conversion contract; every value consumes a --spry-* token so themes and auto dark mode need zero extra rules). ========================================================================== */ /* The Act|Consult split (T11: ONE class for both detail pages โ€” the task page's .task-columns and the goal page's .goal-columns were the same rule with drifting rail widths, now unified): Spry's .with-sidebar gives the rail a minmax(11rem, 16rem) FIRST column; the detail pages want the acting column WIDE (the blocks and comments are the page's work) and the consult rail bounded. Same override shape as T3's .dash-board. */ .act-consult { grid-template-columns: minmax(0, 1fr) minmax(18rem, 24rem); } /* app.css loads after spry.css, so the re-proportioned columns above would out-rank the framework's own 55rem stacking rule inside its @media block (media queries add no specificity). Restore the stack here so Consult drops under Act on narrow screens โ€” Act leads. */ @media (max-width: 55rem) { .act-consult { grid-template-columns: minmax(0, 1fr); } } /* The anchor sub-nav (ux-notes ยง5 C, T11 unified): a horizontal strip of in-page links, sticky UNDER the app topbar. T5 shipped the pill language, T6 an underline variant under .goal-subnav; the unified engine + class keep ONE strip vocabulary. The strip is quiet page furniture until the engine flags it .is-stuck (the opaque surface and edge arrive with stickiness); the active link reads as the current section through the accent pill. */ .subnav { display: flex; gap: var(--spry-space-1); margin: var(--spry-space-3) 0; overflow-x: auto; padding: var(--spry-space-1) 0; scrollbar-width: none; } .subnav::-webkit-scrollbar { display: none; } .subnav a { border: var(--spry-border-width) solid transparent; border-radius: var(--spry-radius-pill); color: var(--spry-fg-muted); font-size: 0.88rem; padding: 0.18rem 0.75rem; text-decoration: none; white-space: nowrap; } .subnav a:hover { background: var(--spry-bg-alt); color: var(--spry-fg); } .subnav a.active { background: var(--spry-accent-soft); border-color: var(--spry-accent); color: var(--spry-accent); } .subnav.is-stuck { background: var(--spry-surface); border-bottom: var(--spry-border-width) solid var(--spry-border); box-shadow: var(--spry-shadow); margin: 0 0 var(--spry-space-3); position: sticky; top: var(--topbar-bottom, 3.1rem); z-index: 10; } /* Anchor targets clear the sticky chrome (the strip + topbar) so a sub-nav jump lands the heading below them, not behind. */ [data-subnav-section] { scroll-margin-top: 6.5rem; } /* The config cluster's summary count line: the numbers read at a glance while the region stays collapsed (ux-notes task detail C). */ .config-history summary { cursor: pointer; } .config-history h4 { font-size: 1rem; margin-bottom: var(--spry-space-1); } /* The log page's sticky toolbar (ux-notes event log A): the shell is a plain block; making it a column flex keeps the toolbar glued to the shell's top edge while the FEED keeps its own internal scroll (its max-height contract from the log engine is untouched). The jump-to-latest button inherits the filter pill language. */ .log-shell { display: flex; flex-direction: column; } .log-toolbar { position: sticky; top: var(--topbar-bottom, 3.1rem); z-index: 5; } .log-jump { background: var(--spry-accent-soft); border: var(--spry-border-width) solid var(--spry-accent); border-radius: var(--spry-radius-pill); color: var(--spry-accent); font-size: 0.8rem; padding: 0.25rem 0.8rem; white-space: nowrap; } .log-jump:hover { background: var(--spry-accent); color: var(--spry-accent-fg); } /* ========================================================================== Goal 41 T6 โ€” goal detail: state strip + sub-nav + Act|Consult (goal_detail.html only; appended per the spry conversion contract; every colour/spacing value consumes a --spry-* token). ========================================================================== */ /* The leading review cards (goal 41 T6 "actions surface"): a pending plan or completion review renders as its own card at the TOP of the acting column, accented like the status head so the open decision reads before anything else. */ .plan-review-card { border-left: 4px solid var(--spry-accent); } /* The one-line state strip (ux-notes ยง5 A): status badge, the open-item verdict, the primary lifecycle button โ€” one glance before anything else on the page. A quiet accent-tinted panel so it reads as the page's verdict line, not another card. */ .goal-state-strip { align-items: center; background: var(--spry-accent-soft); border: var(--spry-border-width) solid var(--spry-accent); border-left: 4px solid var(--spry-accent); border-radius: var(--spry-radius); display: flex; flex-wrap: wrap; gap: var(--spry-space-2) var(--spry-space-3); margin-bottom: var(--spry-space-3); padding: var(--spry-space-2) var(--spry-space-3); } .goal-state-verdict { flex: 1 1 12rem; min-width: 0; } /* The stacked pieces of a compound verdict ("plan revision pending" + "1 question waiting") read with a middot, like the status head. */ .goal-state-verdict > span + span::before { content: ' ยท '; } /* T11: the goal page's sub-nav strip + Act|Consult columns adopted the shared engine โ€” the strip is the unified .subnav (the engine's .is-stuck class now owns stickiness, replacing the always-sticky .goal-subnav) and the columns are the shared .act-consult. The per-page variants (.goal-subnav, .goal-columns, .task-columns) were deleted in this sweep. */ /* ========================================================================== Goal 41 T7 โ€” task page <-> event log unification (append-only section per the spry-conversion contract; every value consumes a --spry-* token so themes and auto dark mode need zero extra rules). ========================================================================== */ /* The task page's Overview|Log tabs: Spry's .tabs already owns the radio/label/panel mechanics; the task page just widens the strip's presence a touch (the tab row sits directly under the breadcrumb, above the sub-nav it now frames). */ .task-tabs { margin-top: var(--spry-space-2); } /* The Progress cluster's "Event log" control is a