chore: roll up in-progress changes across plugins
- claude-design: scaffold new plugin (plugin.json, CHANGELOG, README) - llm-security: playground design-system updates (tokens, components, tier3 supplement, new tier4 project-view CSS) - ms-ai-architect: v2 mockup screenshots + local screenshot script - voyage: annotate.mjs update Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
18
plugins/claude-design/.claude-plugin/plugin.json
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "claude-design",
|
||||
"version": "0.1.0-pre",
|
||||
"description": "Claude Design expertise — facilitates the full prompt-to-artifact workflow on claude.ai/design. Skills, agents, and commands to be defined via Voyage pipeline.",
|
||||
"author": {
|
||||
"name": "Kjell Tore Guttormsen"
|
||||
},
|
||||
"auto_discover": true,
|
||||
"license": "MIT",
|
||||
"repository": "https://git.fromaitochitta.com/open/ktg-plugin-marketplace",
|
||||
"keywords": [
|
||||
"claude-design",
|
||||
"claude-ai",
|
||||
"prompt-engineering",
|
||||
"artifacts",
|
||||
"design"
|
||||
]
|
||||
}
|
||||
17
plugins/claude-design/CHANGELOG.md
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- Plugin bootstrap: directory created inside `ktg-plugin-marketplace`, minimal manifest and skeleton docs.
|
||||
- Voyage pipeline kicked off: brief → research → plan → execute → review.
|
||||
|
||||
## [0.1.0-pre] — 2026-05-15
|
||||
|
||||
### Added
|
||||
- Initial scaffold (README, CLAUDE.md, ROADMAP, TODO, plugin.json placeholder).
|
||||
22
plugins/claude-design/README.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# claude-design
|
||||
|
||||
Claude Code plugin: be an expert on **Claude Design** (claude.ai/design).
|
||||
|
||||
This plugin holds the knowledge, skills, agents, and commands needed to facilitate the entire process of designing and producing artifacts on Claude Design — from initial idea, through prompt engineering, through iteration, to a finished result.
|
||||
|
||||
> **Status: 0.1.0-pre — bootstrap.** Skill/agent/command surface is being defined through the Voyage pipeline (`/trekbrief` → `/trekresearch` → `/trekplan` → `/trekexecute` → `/trekreview`).
|
||||
|
||||
## Scope (to be confirmed via brief)
|
||||
|
||||
- Deep knowledge of Claude Design as a product surface
|
||||
- Prompt-engineering patterns specifically tuned for Claude Design
|
||||
- End-to-end facilitation: discovery → prompt → preview → refine → ship
|
||||
- Reference material from the Anthropic cookbook and other authoritative sources
|
||||
|
||||
## Status
|
||||
|
||||
Pre-release. No commands or agents yet. See `ROADMAP.md` and `TODO.md` for the current Voyage iteration.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
|
@ -1,5 +1,88 @@
|
|||
# playground-design-system — CHANGELOG
|
||||
|
||||
## 0.6.0 — 2026-05-15
|
||||
|
||||
### Added — Project-view archetype (Tier 4)
|
||||
|
||||
Generic "project as artifact-collection" archetype for plugins where a project owns 0-N read-only report artifacts grouped by category. Default view is an aggregated dashboard; clicking a sidebar item swaps the main panel to the per-artifact render. Edit-mode is paste-import only (no inline editor).
|
||||
|
||||
- **New file `components-tier4-project-view.css`** — 11 sections covering:
|
||||
- `.project-view` + `.project-view__layout` (grid: nav 280px + main 1fr, responsive collapse at 1280 / 960px)
|
||||
- `.project-view__header` (CSS Grid with eyebrow/title/lede/verdict/key-stats/actions areas)
|
||||
- `.verdict-pill` (small pill variant — companion to existing `.verdict-pill-lg` in tier2)
|
||||
- `.project-view__nav` + `.project-view__nav-search` (sticky sidebar with search)
|
||||
- `.artifact-list` + `__group` / `__group-label` / `__group-count` / `__group-items` / `__item` / `__item-marker` / `__item-body` / `__item-name` / `__item-meta` (grouped, severity-coded sidebar)
|
||||
- `.artifact-status[data-severity]` (mini-pill: positive | medium | critical)
|
||||
- `.project-view__main` (main column container)
|
||||
- `.project-overview` + `__intro` / `__verdict-grid` / `__verdict-tile[data-severity]` / `__section` / `__top-risks` / `__next-actions` / `__missing-reports` (aggregated dashboard)
|
||||
- `.project-view__artifact` + `__artifact-header` / `__artifact-title` / `__artifact-meta` / `__artifact-actions` / `__artifact-body` (single-rapport viewer wrapper)
|
||||
- `.empty-artifact-prompt` + `__icon` / `__title` / `__text` / `__actions` (empty-state)
|
||||
- `.import-modal` + `__backdrop` / `__panel` / `__head` / `__title` / `__close` / `__form` / `__detect` / `__preview` / `__preview-label` / `__footer` (overlay modal for paste-import)
|
||||
|
||||
- **6 new tokens in `tokens.css`:**
|
||||
- `--project-view-nav-width: 280px` — sidebar width at full layout
|
||||
- `--project-view-collapse-bp: 960px` — doc-only token referenced by responsive breakpoints
|
||||
- `--artifact-list-item-pad-y: var(--space-2)` — sidebar row vertical padding
|
||||
- `--artifact-list-item-pad-x: var(--space-3)` — sidebar row horizontal padding
|
||||
- `--artifact-marker-size: 14px` — sidebar status marker diameter
|
||||
- `--artifact-marker-border: 1.5px` — sidebar status marker border thickness
|
||||
|
||||
### Påvirkning
|
||||
|
||||
Endringen er **additiv**: ny komponent-fil + 6 nye tokens, ingen eksisterende selectors eller verdier endres. Plugin-konsumenter (`ms-ai-architect`, `llm-security`, `okr`, `config-audit`, `voyage`) får silent drift mot ny source-commit, men kan re-sync på eget tempo. Bare `ms-ai-architect` og `llm-security` re-syncer i samme commit som denne DS-bumpen (forberedelse til koordinert v1.15.0 / v7.7.0-release etter ~8 sesjoner med JS-implementasjon).
|
||||
|
||||
Førsteadoptere: `ms-ai-architect` v1.15.0 (17 artefakter, 5 kategorier) + `llm-security` v7.7.0 (≥18 artefakter, 6 kategorier). State-driven visibility håndteres i plugin-JS, ikke i denne CSS-en — kun aktiv state rendres per pass.
|
||||
|
||||
### Plugins som må laste den nye filen
|
||||
|
||||
Etter `<link>` til `components-tier3-supplement.css`, legg til:
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="vendor/playground-design-system/components-tier4-project-view.css">
|
||||
```
|
||||
|
||||
### For å adoptere v0.6.0
|
||||
|
||||
```bash
|
||||
node scripts/sync-design-system.mjs <plugin-name>
|
||||
# --force hvis drift detected
|
||||
```
|
||||
|
||||
## 0.5.0 — 2026-05-10
|
||||
|
||||
### Added
|
||||
- **voyage scope tokens (B-DS-4):** `--color-scope-voyage` (aqua-blue `#1B5FB8`), `--color-scope-voyage-soft` (`#E5EFFA`), `--color-scope-voyage-strong` (`#143E78`) appended to scope-color group in `tokens.css`. Matches the existing `--color-scope-{architect,okr,security,ultraplan,config}` family so voyage-playground can use the canonical badge convention.
|
||||
- **`.badge--scope-voyage`** in `base.css`: white-on-aqua-blue badge variant matching the existing scope-badge family.
|
||||
|
||||
### Påvirkning
|
||||
|
||||
Endringen er **additiv**: legger TIL voyage-scope-tokens og en ny badge-modifier. Ingen eksisterende selectors eller token-verdier endres. Plugin-konsumenter (llm-security, ms-ai-architect, okr, config-audit) får stale vendor-state mot ny source-commit, men det er silent drift — re-sync skjer på eget tempo neste playground-touch. Bare `voyage` re-syncer i denne commit-en.
|
||||
|
||||
Førsteadopter: `voyage` v4.3.0 (multi-sesjons-løp 2026-05-10, sesjon 1 = Wave 0+1 Foundation).
|
||||
|
||||
## 0.4.0 — 2026-05-08
|
||||
|
||||
### Bug fixes
|
||||
- **`.kanban-card__name`** (components-tier3-supplement.css): bytt `word-break: break-all` til `word-break: break-word` + `overflow-wrap: anywhere`. `break-all` knekker midt i ord ("Tekn isk dokumen tasjon"); ny verdi respekterer ordskjøt og brytter kun lange tokens (B-DS-1).
|
||||
- **`.expansion__title-main`, `.expansion__title-sub`** (components-tier3-supplement.css): legg til `display: block`. Begge er `<span>`-elementer som flyter inline by default, noe som gir "dokumentertKilde: Art. 9" på samme linje. `display: block` sikrer vertikal stacking (B-DS-2).
|
||||
- **`.matrix__bubble`** (components.css): legg til `cursor: pointer`, `transition`, `:hover { transform: scale(1.15) }` og `:focus-visible { outline + offset }`. Antar at consumer rendrer bobler som `<button>` for click-handlers — gir visuell + keyboard-fokus-feedback (B-DS-3).
|
||||
|
||||
### Påvirkning
|
||||
|
||||
Bugfixene er **backward-compatible** — alle eksisterende selectors og verdier som er endret, var bugfixes. Plugin-konsumenter som har lokal-overrides for disse mønstrene bør re-syncer og slette overridene:
|
||||
|
||||
- **ms-ai-architect:** re-sync i samme commit, sletter linje 191-193 (matrix-bubble), 208-211 (expansion-title), 213-216 (kanban-card-name) i `playground/ms-ai-architect-playground.html`.
|
||||
- **llm-security, voyage, okr, config-audit:** re-sync på eget tempo (ikke breaking — gammel vendored DS fungerer fortsatt med eksisterende lokal-overrides).
|
||||
|
||||
### For å adoptere v0.4
|
||||
|
||||
```bash
|
||||
node scripts/sync-design-system.mjs <plugin-name>
|
||||
# --force hvis drift detected
|
||||
```
|
||||
|
||||
Førsteadopter: `ms-ai-architect` v1.14.0 (planlagt 2026-05-08, multi-sesjons-løp som starter med DS-bump i sesjon 2).
|
||||
|
||||
## 0.3.0 — 2026-05-04
|
||||
|
||||
### Added — Playground/report-page foundation primitives (sections 13-25 in tier3-supplement)
|
||||
|
|
|
|||
|
|
@ -2,17 +2,18 @@
|
|||
"generated_by": "scripts/sync-design-system.mjs",
|
||||
"do_not_edit": true,
|
||||
"source": "shared/playground-design-system/",
|
||||
"source_commit": "487f7ae746aeb1c0f19bb0f4b8d0ffcf0a59a677",
|
||||
"sync_date": "2026-05-05T16:33:38.829Z",
|
||||
"file_count": 26,
|
||||
"source_commit": "c1b7bad3899c5cfe9ff90663003609b018aa79a0",
|
||||
"sync_date": "2026-05-15T14:11:15.296Z",
|
||||
"file_count": 27,
|
||||
"files": {
|
||||
"CHANGELOG.md": "e293a911701e0ae8e95f8d30e2b583d1c578d0c2af4fd2abfbee3a7d65d5f7ba",
|
||||
"CHANGELOG.md": "b5018b46cd0830334109e915d23b5554c060412c2b7e132f97f2933e5dd5d79c",
|
||||
"README.md": "83de0e29b207c979b7b2a3327b7a4ec0c2e1b4d3705ee2677f26c28c3a3ee643",
|
||||
"base.css": "604fe6839e2ed304bc0ba112a4e045f208b4b3f084f449a1abdb94ce0a1e5263",
|
||||
"base.css": "df0db874473412eb771b7355b589f7478042987756898f0921584286bd5ba70a",
|
||||
"components-tier2.css": "c2cb7e9d76d6af28d50db654030413777feb2f2f2b93213e598de8b686b14523",
|
||||
"components-tier3-supplement.css": "b78664275948f05b9cb4e577921695bd39d15b34c671809d8c8465cac4e1739b",
|
||||
"components-tier3-supplement.css": "51fab10377d80029d6552613069d46fd14ce66af77fe6705b1c6bdf5c9e6481e",
|
||||
"components-tier3.css": "c391ea387298ce864bc35078e7e044b2cdd4187e3130456347d91876599ff4b1",
|
||||
"components.css": "f76b22ba9fd64c2e806b4467536174347105f3e5ccca8a6349a919287d864b86",
|
||||
"components-tier4-project-view.css": "f8f784df70044ecc9bdc862a327b1ee58b201d056581316808a9b60632c5a993",
|
||||
"components.css": "56fa7392b8b20b567a46f72a8fe9e0205d78ce475eae6b22fc3f50b39b235545",
|
||||
"fonts.css": "e3c3df581c6e4d66e25c555f125c745f6512a33038401089d2519a94ea63ee3d",
|
||||
"fonts/Inter-Bold.woff2": "220976705fbec109f43c5cfdceca639e99ace7e51f3eb67292b105d3575eb39b",
|
||||
"fonts/Inter-Medium.woff2": "8458f8afa67b5691c1fcbe51607a2dafb53a9839e48131c608a186b65415d96d",
|
||||
|
|
@ -31,6 +32,6 @@
|
|||
"schemas/finding.schema.json": "0b24797373650582bac232d31a4dd9260593375a0d17259e18f1141a20de8d0c",
|
||||
"schemas/okr-set.schema.json": "aa27347fb232a956ec9dcee1775115710e2715a665c8d729ac50b90c6884de26",
|
||||
"schemas/ros-threat.schema.json": "e16497c1a6b79d6e78149d6cf1c28ac9df1e93234627a0c546814fb24d6c96d9",
|
||||
"tokens.css": "1499bc2eea0178e35935413c79a10bbee7d49fdfa91bd33eeba3bb9e9acab809"
|
||||
"tokens.css": "63dca13f8341937169fc8e84d3f37ae0c714901fa006c865ea377bd448f87644"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -146,6 +146,7 @@ button { font-family: inherit; }
|
|||
.badge--scope-security { background: var(--color-scope-security); color: #fff; border-color: transparent; }
|
||||
.badge--scope-ultraplan { background: var(--color-scope-ultraplan); color: #fff; border-color: transparent; }
|
||||
.badge--scope-config { background: var(--color-scope-config); color: #fff; border-color: transparent; }
|
||||
.badge--scope-voyage { background: var(--color-scope-voyage); color: #fff; border-color: transparent; }
|
||||
|
||||
/* ---------- Cards / surfaces ---------- */
|
||||
.card {
|
||||
|
|
|
|||
|
|
@ -280,7 +280,7 @@
|
|||
.kanban-card[data-verdict="trusted"] { border-left: 4px solid var(--color-state-success); }
|
||||
.kanban-card[data-verdict="unknown"] { border-left: 4px solid var(--color-state-warning); }
|
||||
|
||||
.kanban-card__name { font-family: var(--font-family-mono); font-size: 13px; color: var(--color-text-primary); word-break: break-all; }
|
||||
.kanban-card__name { font-family: var(--font-family-mono); font-size: 13px; color: var(--color-text-primary); word-break: break-word; overflow-wrap: anywhere; }
|
||||
.kanban-card__meta { font-size: 11px; color: var(--color-text-tertiary); }
|
||||
.kanban-card__reason { font-size: 12px; color: var(--color-text-secondary); }
|
||||
|
||||
|
|
@ -696,8 +696,8 @@
|
|||
.expansion__head:hover { background: var(--color-bg-soft); }
|
||||
.expansion__head:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
|
||||
.expansion__title { flex: 1; }
|
||||
.expansion__title-main { font-size: var(--font-size-md); color: var(--color-text-primary); font-weight: var(--font-weight-medium); }
|
||||
.expansion__title-sub { font-size: var(--font-size-sm); color: var(--color-text-secondary); margin-top: 2px; }
|
||||
.expansion__title-main { display: block; font-size: var(--font-size-md); color: var(--color-text-primary); font-weight: var(--font-weight-medium); }
|
||||
.expansion__title-sub { display: block; font-size: var(--font-size-sm); color: var(--color-text-secondary); margin-top: 2px; }
|
||||
.expansion__chev {
|
||||
color: var(--color-text-tertiary);
|
||||
transition: transform var(--duration-normal) var(--ease-default);
|
||||
|
|
|
|||
666
plugins/llm-security/playground/vendor/playground-design-system/components-tier4-project-view.css
vendored
Normal file
|
|
@ -0,0 +1,666 @@
|
|||
/* Code generated by sync-design-system.mjs; DO NOT EDIT. */
|
||||
/* =============================================================================
|
||||
Playground Design System — components-tier4-project-view.css
|
||||
v0.6.0 — Tier 4 project-view archetype
|
||||
============================================================================
|
||||
|
||||
Generic "project as artifact-collection" archetype. Default-view is an
|
||||
aggregated overview dashboard; clicking a sidebar item swaps main to a
|
||||
per-artifact render. Tracks 0-N read-only artifacts; edit-mode is paste-
|
||||
import only (markdown from terminal → parser → store).
|
||||
|
||||
First adopters: ms-ai-architect v1.15.0 (17 artifacts, 5 categories) +
|
||||
llm-security v7.7.0 (≥18 artifacts, 6 categories). Each plugin injects a
|
||||
PROJECT_VIEW_CONFIG object that maps commands → renderers, categories,
|
||||
verdict-aggregators, missing-report heuristics.
|
||||
|
||||
The CSS in this file is plugin-agnostic. Plugin-specific shape (category
|
||||
names, artifact ordering, custom severity-mappings) lives in JS config.
|
||||
|
||||
State-driven visibility is NOT handled here — production playgrounds emit
|
||||
only the active state (overview | artifact | empty | import) per render
|
||||
pass. The mockup uses body[data-state="..."] for prototyping; production
|
||||
renders one branch at a time.
|
||||
============================================================================= */
|
||||
|
||||
|
||||
/* === 1. Project-view top-level layout ===================================== */
|
||||
|
||||
.project-view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-6);
|
||||
}
|
||||
|
||||
.project-view__layout {
|
||||
display: grid;
|
||||
grid-template-columns: var(--project-view-nav-width) 1fr;
|
||||
gap: var(--space-6);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
@media (max-width: 1279px) {
|
||||
.project-view__layout { grid-template-columns: 240px 1fr; }
|
||||
}
|
||||
|
||||
@media (max-width: 959px) {
|
||||
.project-view__layout { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
|
||||
/* === 2. Project-view header =============================================== */
|
||||
|
||||
.project-view__header {
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border-subtle);
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--space-5) var(--space-6);
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
grid-template-areas:
|
||||
"title verdict"
|
||||
"title keystats"
|
||||
"actions actions";
|
||||
gap: var(--space-4) var(--space-6);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.project-view__title-block { grid-area: title; }
|
||||
.project-view__verdict { grid-area: verdict; justify-self: end; }
|
||||
.project-view__key-stats { grid-area: keystats; justify-self: end; }
|
||||
.project-view__actions { grid-area: actions; display: flex; gap: var(--space-2); justify-content: flex-end; }
|
||||
|
||||
.project-view__eyebrow {
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: var(--color-text-tertiary);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
margin: 0 0 var(--space-2) 0;
|
||||
}
|
||||
|
||||
.project-view__title {
|
||||
font-size: var(--font-size-2xl);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-text-primary);
|
||||
margin: 0 0 var(--space-2) 0;
|
||||
}
|
||||
|
||||
.project-view__lede {
|
||||
color: var(--color-text-secondary);
|
||||
margin: 0;
|
||||
max-width: 60ch;
|
||||
}
|
||||
|
||||
.project-view__key-stats {
|
||||
display: flex;
|
||||
gap: var(--space-5);
|
||||
}
|
||||
|
||||
.project-view__key-stat-label {
|
||||
font-size: 10px;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-text-tertiary);
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
|
||||
.project-view__key-stat-value {
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
|
||||
/* === 3. Verdict-pill (small) ==============================================
|
||||
Companion to .verdict-pill-lg (Tier 2). Inline-flex pill used in project
|
||||
header + sidebar status badges. The larger -lg variant lives in
|
||||
components-tier2.css; both share the same severity-band semantics. */
|
||||
|
||||
.verdict-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
padding: 4px 12px;
|
||||
border-radius: 999px;
|
||||
font-weight: var(--font-weight-semibold);
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.verdict-pill--positive { background: var(--color-state-success); color: #fff; }
|
||||
.verdict-pill--medium { background: var(--color-severity-medium); color: var(--color-severity-medium-on); }
|
||||
.verdict-pill--critical { background: var(--color-severity-critical); color: #fff; }
|
||||
.verdict-pill--in-progress {
|
||||
background: var(--color-bg-soft);
|
||||
color: var(--color-text-secondary);
|
||||
border: 1px dashed var(--color-border-moderate);
|
||||
}
|
||||
|
||||
|
||||
/* === 4. Sidebar nav ======================================================= */
|
||||
|
||||
.project-view__nav {
|
||||
position: sticky;
|
||||
top: var(--space-6);
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border-subtle);
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--space-4);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.project-view__nav-search input {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 6px 10px;
|
||||
font-size: var(--font-size-sm);
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text-primary);
|
||||
border: 1px solid var(--color-border-moderate);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
|
||||
/* === 5. Artifact-list ===================================================== */
|
||||
|
||||
.artifact-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-4);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.artifact-list__group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
.artifact-list__group-label {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 10px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--color-text-tertiary);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
padding: 0 var(--space-2);
|
||||
}
|
||||
|
||||
.artifact-list__group-count {
|
||||
background: var(--color-bg-soft);
|
||||
color: var(--color-text-tertiary);
|
||||
font-family: var(--font-family-mono);
|
||||
font-size: 10px;
|
||||
padding: 1px 6px;
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.artifact-list__group-items {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.artifact-list__item {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
padding: var(--artifact-list-item-pad-y) var(--artifact-list-item-pad-x);
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
transition: background 120ms ease, border-color 120ms ease;
|
||||
}
|
||||
|
||||
.artifact-list__item:hover { background: var(--color-bg-soft); }
|
||||
|
||||
.artifact-list__item[data-state="active"] {
|
||||
background: var(--color-bg-soft);
|
||||
border-color: var(--color-primary-500);
|
||||
box-shadow: inset 3px 0 0 var(--color-primary-500);
|
||||
padding-left: calc(var(--artifact-list-item-pad-x) - 3px);
|
||||
}
|
||||
|
||||
.artifact-list__item-marker {
|
||||
width: var(--artifact-marker-size);
|
||||
height: var(--artifact-marker-size);
|
||||
border-radius: 50%;
|
||||
border: var(--artifact-marker-border) solid var(--color-border-moderate);
|
||||
background: transparent;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.artifact-list__item[data-state="filled"][data-severity="positive"] .artifact-list__item-marker {
|
||||
background: var(--color-state-success);
|
||||
border-color: var(--color-state-success);
|
||||
}
|
||||
.artifact-list__item[data-state="filled"][data-severity="medium"] .artifact-list__item-marker {
|
||||
background: var(--color-severity-medium);
|
||||
border-color: var(--color-severity-medium);
|
||||
}
|
||||
.artifact-list__item[data-state="filled"][data-severity="critical"] .artifact-list__item-marker {
|
||||
background: var(--color-severity-critical);
|
||||
border-color: var(--color-severity-critical);
|
||||
}
|
||||
|
||||
.artifact-list__item-body { min-width: 0; }
|
||||
|
||||
.artifact-list__item-name {
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--color-text-primary);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.artifact-list__item[data-state="empty"] .artifact-list__item-name {
|
||||
color: var(--color-text-tertiary);
|
||||
font-weight: var(--font-weight-regular);
|
||||
}
|
||||
|
||||
.artifact-list__item-meta {
|
||||
font-size: 11px;
|
||||
color: var(--color-text-tertiary);
|
||||
}
|
||||
|
||||
|
||||
/* === 6. Artifact-status (mini pill in sidebar) =========================== */
|
||||
|
||||
.artifact-status {
|
||||
font-family: var(--font-family-mono);
|
||||
font-size: 10px;
|
||||
font-weight: var(--font-weight-semibold);
|
||||
padding: 1px 5px;
|
||||
border-radius: var(--radius-sm);
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.artifact-status[data-severity="positive"] { background: var(--color-severity-low-soft); color: var(--color-severity-low-on); }
|
||||
.artifact-status[data-severity="medium"] { background: var(--color-severity-medium-soft); color: var(--color-severity-medium-on); }
|
||||
.artifact-status[data-severity="critical"] { background: var(--color-severity-critical-soft); color: var(--color-severity-critical-on); }
|
||||
|
||||
|
||||
/* === 7. Project-view main panel ========================================== */
|
||||
|
||||
.project-view__main {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-5);
|
||||
}
|
||||
|
||||
|
||||
/* === 8. Project-overview (default dashboard) ============================= */
|
||||
|
||||
.project-overview {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-6);
|
||||
}
|
||||
|
||||
.project-overview__intro {
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border-subtle);
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--space-5);
|
||||
}
|
||||
|
||||
.project-overview__intro h2 {
|
||||
font-size: var(--font-size-lg);
|
||||
margin: 0 0 var(--space-2) 0;
|
||||
}
|
||||
|
||||
.project-overview__intro p {
|
||||
color: var(--color-text-secondary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.project-overview__verdict-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.project-overview__verdict-tile {
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border-subtle);
|
||||
border-left: 4px solid var(--color-border-moderate);
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--space-4);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
.project-overview__verdict-tile[data-severity="positive"] { border-left-color: var(--color-state-success); }
|
||||
.project-overview__verdict-tile[data-severity="medium"] { border-left-color: var(--color-severity-medium); }
|
||||
.project-overview__verdict-tile[data-severity="critical"] { border-left-color: var(--color-severity-critical); }
|
||||
.project-overview__verdict-tile[data-severity="empty"] { border-left-style: dashed; }
|
||||
|
||||
.project-overview__verdict-tile-label {
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--color-text-tertiary);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
.project-overview__verdict-tile-value {
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
.project-overview__verdict-tile-meta {
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.project-overview__section h3 {
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: var(--color-text-tertiary);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
margin: 0 0 var(--space-3) 0;
|
||||
}
|
||||
|
||||
.project-overview__top-risks,
|
||||
.project-overview__next-actions {
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border-subtle);
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--space-5);
|
||||
}
|
||||
|
||||
.project-overview__top-risks ol,
|
||||
.project-overview__next-actions ol {
|
||||
list-style: none;
|
||||
counter-reset: rank;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.project-overview__top-risks li,
|
||||
.project-overview__next-actions li {
|
||||
counter-increment: rank;
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-bg-soft);
|
||||
}
|
||||
|
||||
.project-overview__top-risks li::before,
|
||||
.project-overview__next-actions li::before {
|
||||
content: counter(rank);
|
||||
font-family: var(--font-family-mono);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-text-tertiary);
|
||||
font-size: var(--font-size-sm);
|
||||
min-width: 20px;
|
||||
}
|
||||
|
||||
.project-overview__missing-reports {
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border-subtle);
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--space-5);
|
||||
}
|
||||
|
||||
.project-overview__missing-reports ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.project-overview__missing-reports li {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
background: var(--color-bg-soft);
|
||||
border-radius: var(--radius-sm);
|
||||
border-left: 3px dashed var(--color-border-moderate);
|
||||
}
|
||||
|
||||
|
||||
/* === 9. Artifact-view (one report rendered) ============================== */
|
||||
|
||||
.project-view__artifact {
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border-subtle);
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--space-6);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-5);
|
||||
}
|
||||
|
||||
.project-view__artifact-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: start;
|
||||
gap: var(--space-4);
|
||||
padding-bottom: var(--space-4);
|
||||
border-bottom: 1px solid var(--color-border-subtle);
|
||||
}
|
||||
|
||||
.project-view__artifact-title {
|
||||
font-size: var(--font-size-xl);
|
||||
margin: 0 0 var(--space-1) 0;
|
||||
}
|
||||
|
||||
.project-view__artifact-meta {
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-text-tertiary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.project-view__artifact-actions {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.project-view__artifact-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-5);
|
||||
}
|
||||
|
||||
|
||||
/* === 10. Empty-artifact-prompt (no report imported yet) ================== */
|
||||
|
||||
.empty-artifact-prompt {
|
||||
background: var(--color-surface);
|
||||
border: 2px dashed var(--color-border-moderate);
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--space-8);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-artifact-prompt__icon {
|
||||
font-size: 48px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.empty-artifact-prompt__title {
|
||||
font-size: var(--font-size-lg);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.empty-artifact-prompt__text {
|
||||
color: var(--color-text-secondary);
|
||||
margin: 0;
|
||||
max-width: 50ch;
|
||||
}
|
||||
|
||||
.empty-artifact-prompt__actions {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
margin-top: var(--space-2);
|
||||
}
|
||||
|
||||
|
||||
/* === 11. Import-modal (overlay) ========================================== */
|
||||
|
||||
.import-modal {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 200;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.import-modal[data-open="true"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.import-modal__backdrop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.55);
|
||||
}
|
||||
|
||||
.import-modal__panel {
|
||||
position: relative;
|
||||
width: min(720px, 92vw);
|
||||
max-height: 90vh;
|
||||
overflow: auto;
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border-strong);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-lg);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.import-modal__head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-4) var(--space-5);
|
||||
border-bottom: 1px solid var(--color-border-subtle);
|
||||
}
|
||||
|
||||
.import-modal__title {
|
||||
margin: 0;
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
.import-modal__close {
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 4px 10px;
|
||||
color: var(--color-text-tertiary);
|
||||
font-size: 20px;
|
||||
line-height: 1;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.import-modal__close:hover {
|
||||
background: var(--color-bg-soft);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.import-modal__form {
|
||||
padding: var(--space-5);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.import-modal__form .field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
.import-modal__form label {
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.import-modal__form select,
|
||||
.import-modal__form textarea {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text-primary);
|
||||
border: 1px solid var(--color-border-moderate);
|
||||
border-radius: var(--radius-sm);
|
||||
font-family: var(--font-family-mono);
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.import-modal__form textarea {
|
||||
resize: vertical;
|
||||
min-height: 180px;
|
||||
}
|
||||
|
||||
.import-modal__detect {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-severity-low-soft);
|
||||
color: var(--color-severity-low-on);
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.import-modal__preview {
|
||||
border: 1px solid var(--color-border-subtle);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: var(--space-3);
|
||||
background: var(--color-bg);
|
||||
max-height: 200px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.import-modal__preview-label {
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--color-text-tertiary);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.import-modal__footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-3) var(--space-5);
|
||||
border-top: 1px solid var(--color-border-subtle);
|
||||
background: var(--color-bg-soft);
|
||||
}
|
||||
|
|
@ -191,6 +191,15 @@
|
|||
color: var(--color-bg);
|
||||
border: none;
|
||||
}
|
||||
/* B-DS-3 (v0.4.0): bobler rendres som <button> i renderMatrixHtml — gi
|
||||
visuell + keyboard-fokus-feedback. Antar at consumer bruker
|
||||
<button class="matrix__bubble">, ellers bare-virkning ufarlig på <span>. */
|
||||
.matrix__bubble {
|
||||
cursor: pointer;
|
||||
transition: transform var(--duration-fast) var(--ease-default);
|
||||
}
|
||||
.matrix__bubble:hover { transform: scale(1.15); }
|
||||
.matrix__bubble:focus-visible { outline: 2px solid var(--color-primary-500); outline-offset: 2px; }
|
||||
[data-theme="dark"] .matrix__bubble { background: rgba(0,0,0,0.45); color: var(--color-text-primary); border-color: rgba(255,255,255,0.15); }
|
||||
|
||||
.matrix__x-label {
|
||||
|
|
|
|||
|
|
@ -102,6 +102,9 @@
|
|||
--color-scope-security: #A40E26; /* llm-security — crimson */
|
||||
--color-scope-ultraplan: #4338CA; /* ultraplan-local — indigo */
|
||||
--color-scope-config: #3F5963; /* config-audit — slate */
|
||||
--color-scope-voyage: #1B5FB8; /* voyage — aqua-blue */
|
||||
--color-scope-voyage-soft: #E5EFFA; /* voyage — light tint */
|
||||
--color-scope-voyage-strong: #143E78; /* voyage — dark strong */
|
||||
|
||||
/* ---------- Spacing -------------------------------------------------- */
|
||||
--space-1: 4px;
|
||||
|
|
@ -140,6 +143,14 @@
|
|||
--container-default: 1080px;
|
||||
--container-wide: 1280px;
|
||||
--sidebar-width: 280px;
|
||||
|
||||
/* ---------- Project-view (Tier 4 — v0.6.0) --------------------------- */
|
||||
--project-view-nav-width: 280px;
|
||||
--project-view-collapse-bp: 960px; /* doc-only — referenced by media queries */
|
||||
--artifact-list-item-pad-y: var(--space-2);
|
||||
--artifact-list-item-pad-x: var(--space-3);
|
||||
--artifact-marker-size: 14px;
|
||||
--artifact-marker-border: 1.5px;
|
||||
}
|
||||
|
||||
:root { color-scheme: light; }
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 740 KiB |
|
After Width: | Height: | Size: 735 KiB |
|
After Width: | Height: | Size: 475 KiB |
|
After Width: | Height: | Size: 473 KiB |
|
After Width: | Height: | Size: 668 KiB |
|
After Width: | Height: | Size: 665 KiB |
|
After Width: | Height: | Size: 742 KiB |
|
After Width: | Height: | Size: 740 KiB |
|
|
@ -0,0 +1,53 @@
|
|||
#!/usr/bin/env node
|
||||
// Mockup verification screenshots — sesjon 2 (DS-hoist).
|
||||
// Captures the 4 mockup states × 2 themes to confirm visual identity
|
||||
// after hoisting project-view CSS to shared DS.
|
||||
//
|
||||
// Output: playground/screenshots/v2-mockup/<state>-<theme>.png
|
||||
|
||||
import { chromium } from 'playwright';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, resolve, join } from 'node:path';
|
||||
import { mkdirSync, existsSync } from 'node:fs';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const PLUGIN_ROOT = resolve(__dirname, '..', '..');
|
||||
const HTML_PATH = join(PLUGIN_ROOT, 'playground', 'v2-mockup.local.html');
|
||||
const OUT_DIR = join(PLUGIN_ROOT, 'playground', 'screenshots', 'v2-mockup');
|
||||
const HTML_URL = 'file://' + HTML_PATH;
|
||||
|
||||
const VIEWPORT = { width: 1440, height: 1200 };
|
||||
const STATES = ['overview', 'artifact', 'empty', 'import'];
|
||||
const THEMES = ['dark', 'light'];
|
||||
|
||||
if (!existsSync(OUT_DIR)) mkdirSync(OUT_DIR, { recursive: true });
|
||||
|
||||
const browser = await chromium.launch();
|
||||
const ctx = await browser.newContext({ viewport: VIEWPORT, deviceScaleFactor: 2 });
|
||||
const page = await ctx.newPage();
|
||||
|
||||
await page.goto(HTML_URL, { waitUntil: 'load' });
|
||||
|
||||
for (const theme of THEMES) {
|
||||
await page.evaluate((t) => {
|
||||
document.documentElement.setAttribute('data-theme', t);
|
||||
const btns = document.querySelectorAll('[data-action="set-theme"]');
|
||||
btns.forEach((b) => b.setAttribute('aria-pressed', b.getAttribute('data-target') === t ? 'true' : 'false'));
|
||||
}, theme);
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
for (const state of STATES) {
|
||||
await page.evaluate((s) => {
|
||||
const btn = document.querySelector(`[data-action="set-state"][data-target="${s}"]`);
|
||||
if (btn) btn.click();
|
||||
}, state);
|
||||
await page.waitForTimeout(300);
|
||||
const outPath = join(OUT_DIR, `${state}-${theme}.png`);
|
||||
await page.screenshot({ path: outPath, fullPage: true });
|
||||
console.log(` → ${state}-${theme}.png`);
|
||||
}
|
||||
}
|
||||
|
||||
await browser.close();
|
||||
console.log('\nDone. 8 screenshots → ' + OUT_DIR);
|
||||
|
|
@ -386,9 +386,18 @@ body:not(.ann-mode) .article-help { display: none; }
|
|||
.article a { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; }
|
||||
.article code { font-family: var(--mono); font-size: .9em; background: var(--bg-soft);
|
||||
padding: .12em .4em; border-radius: 4px; }
|
||||
.article pre { background: #1e1e24; color: #e6e6eb; padding: 16px 18px; border-radius: 8px;
|
||||
.article pre { position: relative; background: #1e1e24; color: #e6e6eb; padding: 16px 18px; border-radius: 8px;
|
||||
overflow-x: auto; font-size: .88rem; line-height: 1.55; margin: 1.2em 0; }
|
||||
.article pre code { background: none; padding: 0; color: inherit; font-size: inherit; }
|
||||
.code-copy-btn { position: absolute; top: 8px; right: 8px; background: rgba(255,255,255,0.08);
|
||||
color: #e6e6eb; border: 1px solid rgba(255,255,255,0.15); border-radius: 5px;
|
||||
padding: 4px 10px; font: 600 11px var(--sans); cursor: pointer; opacity: 0;
|
||||
transition: opacity .15s ease, background .15s ease; letter-spacing: .02em; z-index: 2; }
|
||||
.article pre:hover .code-copy-btn,
|
||||
.article pre:focus-within .code-copy-btn { opacity: 1; }
|
||||
.code-copy-btn:hover { background: rgba(255,255,255,0.16); }
|
||||
.code-copy-btn:focus { opacity: 1; outline: 2px solid var(--accent); outline-offset: 2px; }
|
||||
.code-copy-btn.copied { background: var(--green); color: #fff; border-color: var(--green); }
|
||||
.article blockquote { margin: 1.2em 0; padding: .5em 1.2em; border-left: 4px solid var(--accent);
|
||||
background: var(--accent-soft); color: var(--text-dim); border-radius: 0 6px 6px 0; }
|
||||
.article ul, .article ol { padding-left: 1.8em; margin: .9em 0; }
|
||||
|
|
@ -871,6 +880,32 @@ refreshArticleAnnotations();
|
|||
renderPanel();
|
||||
updateCounts();
|
||||
setMode(true);
|
||||
|
||||
// ── Code-block copy buttons ──
|
||||
document.querySelectorAll('.article pre').forEach(function(pre) {
|
||||
var btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'code-copy-btn';
|
||||
btn.textContent = 'Copy';
|
||||
btn.setAttribute('aria-label', 'Copy code block to clipboard');
|
||||
btn.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
var code = pre.querySelector('code');
|
||||
var text = code ? code.textContent : pre.textContent;
|
||||
navigator.clipboard.writeText(text).then(function() {
|
||||
btn.textContent = 'Copied';
|
||||
btn.classList.add('copied');
|
||||
setTimeout(function() {
|
||||
btn.textContent = 'Copy';
|
||||
btn.classList.remove('copied');
|
||||
}, 1500);
|
||||
}).catch(function() {
|
||||
btn.textContent = 'Failed';
|
||||
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
|
||||
});
|
||||
});
|
||||
pre.appendChild(btn);
|
||||
});
|
||||
`.trim();
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
|||