style(web): полотно доски — карточки, дорожки, даты в стиле системы (#65)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev 2026-10-22 00:42:53 +04:00
parent cec8beecfb
commit 3a711ff2ea
4 changed files with 93 additions and 84 deletions

View File

@ -1,5 +1,5 @@
/* 3. Закреплённая шапка: заголовок и действия (PageHeader), панель управления (Panel), предупреждения, легенда */
.sticky{position:sticky;top:env(safe-area-inset-top,0px);z-index:10;background:var(--bg);padding-block:var(--space-4) var(--space-2);margin:0 0 var(--space-2);box-shadow:0 1px 0 var(--line)}
.sticky{position:sticky;top:env(safe-area-inset-top,0px);z-index:10;background:var(--color-bg-canvas);padding-block:var(--space-4) var(--space-2);margin:0 0 var(--space-2);box-shadow:0 1px 0 var(--color-border-default)}
.sticky .ui-page-header{margin-bottom:var(--space-3);gap:var(--space-2)}
.stats{font-variant-numeric:tabular-nums}
.stats b{color:var(--color-text-primary);font-weight:600}
@ -27,140 +27,147 @@
.legend-head{display:flex;align-items:center;gap:var(--space-2)}
.legend{display:flex;flex-wrap:wrap;align-items:center;gap:2px 12px;margin:var(--space-1) 0 0;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--color-text-secondary)}
.legend span{display:inline-flex;align-items:center;gap:6px}
.legend .sep{width:1px;height:14px;background:var(--line)}
.legend .sep{width:1px;height:14px;background:var(--color-border-default)}
.legend .hint{margin-left:auto}
.lg{font:inherit;font-size:13px;color:var(--muted);background:none;border:0;padding:2px 5px;border-radius:4px;cursor:pointer;display:inline-flex;align-items:center;gap:6px}
.lg:hover,.lg:focus-visible{background:var(--soft);color:var(--fg);outline:none}
.lg{font:inherit;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--color-text-secondary);background:none;border:1px solid transparent;padding:2px 6px;border-radius:var(--radius-xs);cursor:pointer;display:inline-flex;align-items:center;gap:6px}
.lg:hover{background:var(--color-bg-hover);color:var(--color-text-primary)}
.lg:focus-visible{background:var(--color-bg-hover);color:var(--color-text-primary);border-color:var(--color-border-selected);outline:none}
.lg.off{opacity:.45;text-decoration:line-through}
.sw{width:10px;height:10px;border-radius:2px;display:inline-block}
.ln{width:26px;height:0;border-top:2px solid var(--edge-strong);display:inline-block}
.ln.dash{border-top-style:dashed;border-top-color:var(--edge)}
.ln.hand{border-top-color:var(--guide)}
.ln.crit{border-top-width:4px;border-top-color:var(--guide)}
.out-sw{width:16px;height:11px;border:1.5px dashed var(--muted);border-radius:3px;display:inline-block}
.out-sw{width:16px;height:11px;border:1.5px dashed var(--color-text-secondary);border-radius:3px;display:inline-block}
/* 6. Доска: прокрутка только по горизонтали (вертикальная — у страницы), дорожки по эпикам, карточки абсолютные */
.stage{display:flex;gap:10px;align-items:stretch}
.wrap{flex:1 1 auto;min-width:0;overflow-x:auto;overflow-y:hidden;border:1px solid var(--line);border-radius:8px;background:var(--card);user-select:none;-webkit-user-select:none}
.board{position:relative;min-width:max(100%,max-content)}
.stage{display:flex;gap:var(--space-3);align-items:stretch}
.wrap{flex:1 1 auto;min-width:0;overflow-x:auto;overflow-y:hidden;border:1px solid var(--color-border-default);border-radius:var(--radius-xl);background:var(--color-bg-surface);user-select:none;-webkit-user-select:none}
.board{position:relative;min-width:100%}
svg.edges,svg.guides{position:absolute;inset:0;width:100%;height:100%;pointer-events:none}
svg.guides{z-index:3}
svg.guides line{stroke:var(--guide);stroke-width:1;stroke-dasharray:4 3}
.empty{padding:40px 16px;color:var(--muted);text-align:center}
.empty{padding:40px 16px;color:var(--color-text-secondary);text-align:center}
#hubState{white-space:normal;min-width:0}
.login{max-width:560px;margin:48px auto;padding:20px 24px;background:var(--panel);border:1px solid var(--line);border-radius:8px}
.login h2{margin:0 0 8px;font-size:17px;font-weight:600}
.login p{margin:0 0 12px;font-size:14px;color:var(--muted)}
.login p.err{color:var(--p-crit)}
.login code{font-family:var(--font-mono);font-size:13px;color:var(--fg)}
.login{max-width:560px;margin:48px auto;padding:20px 24px;background:var(--color-bg-surface);border:1px solid var(--color-border-default);border-radius:var(--radius-xl)}
.login h2{margin:0 0 8px;font-size:var(--fs-lg);line-height:var(--lh-lg);font-weight:700}
.login p{margin:0 0 12px;font-size:var(--fs-body);color:var(--color-text-secondary)}
.login p.err{color:var(--color-status-error)}
.login code{font-family:var(--font-mono);font-size:13px;color:var(--color-text-primary)}
.login form{display:flex;gap:8px;margin:0 0 12px}
.login input{flex:1;min-width:0;font:inherit;font-size:14px;height:34px;box-sizing:border-box;padding:0 10px;color:var(--fg);background:var(--bg);border:1px solid var(--line);border-radius:6px}
.login input:focus-visible{border-color:var(--accent);outline:none}
.lane{position:absolute;left:0;right:0;border-top:1px solid var(--line)}
.lane:nth-child(even of .lane){background:color-mix(in srgb,var(--soft) 45%,transparent)}
.lane.folded{background:color-mix(in srgb,var(--soft) 80%,transparent)}
.lane.target{box-shadow:inset 0 3px 0 var(--accent)}
.lane.target-after{box-shadow:inset 0 -3px 0 var(--accent)}
.login input{flex:1;min-width:0;font:inherit;font-size:var(--fs-body);height:var(--control-h);box-sizing:border-box;padding:0 10px;color:var(--color-text-primary);background:var(--color-bg-canvas);border:1px solid var(--color-border-default);border-radius:var(--radius-sm)}
.login input:focus-visible{border-color:var(--color-border-selected);outline:none}
.lane{position:absolute;left:0;right:0;border-top:1px solid var(--color-border-default)}
.lane:nth-child(even of .lane){background:var(--lane-alt)}
.lane.folded{background:var(--color-bg-hover)}
.lane.target{box-shadow:inset 0 3px 0 var(--color-action-primary)}
.lane.target-after{box-shadow:inset 0 -3px 0 var(--color-action-primary)}
.lane.dragging{opacity:.6}
.lane-head{position:absolute;left:8px;top:8px;display:flex;align-items:center;gap:4px;z-index:1}
.fold{width:18px;height:18px;border:0;background:none;color:var(--muted);cursor:pointer;padding:0;border-radius:4px;display:inline-flex;align-items:center;justify-content:center;font-size:0}
.fold::before{content:"";border:5px solid transparent;border-top:6px solid currentColor;border-bottom:0;transition:transform .15s}
.fold:hover{background:var(--soft);color:var(--fg)}
.lane.folded .fold::before{transform:rotate(-90deg)}
.lane-name{font-size:12.5px;letter-spacing:.06em;text-transform:uppercase;color:var(--muted);font-weight:500;cursor:grab;padding:2px 4px;border-radius:4px;user-select:none;touch-action:none}
.lane-name:hover{background:var(--soft);color:var(--fg)}
.lane-count{display:none;font-size:12.5px;color:var(--muted);margin-left:4px}
.fold{width:20px;height:20px;border:0;background:none;color:var(--color-text-secondary);cursor:pointer;padding:0;border-radius:var(--radius-xs);display:inline-flex;align-items:center;justify-content:center}
.fold svg{transition:transform .15s}
.fold:hover{background:var(--color-bg-hover);color:var(--color-text-primary)}
.fold:focus-visible,.lane-tools button:focus-visible,.card .open-card:focus-visible{outline:2px solid var(--color-focus-ring);outline-offset:1px}
.lane.folded .fold svg{transform:rotate(-90deg)}
.lane-name{font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--color-text-secondary);font-weight:600;cursor:grab;padding:2px 4px;border-radius:var(--radius-xs);user-select:none;touch-action:none}
.lane-name:hover{background:var(--color-bg-hover);color:var(--color-text-primary)}
.lane-count{display:none;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--color-text-secondary);margin-left:4px}
.lane.folded .lane-count{display:inline}
.lane-tools{display:none;gap:2px}
.lane:hover .lane-tools{display:inline-flex}
.lane-tools button{font:inherit;font-size:12px;line-height:1;color:var(--muted);background:var(--card);border:1px solid var(--line);border-radius:4px;width:22px;height:22px;cursor:pointer;padding:0}
.lane-tools button:hover{border-color:var(--accent);color:var(--accent)}
.lane-tools button{display:inline-flex;align-items:center;justify-content:center;color:var(--color-text-secondary);background:var(--color-bg-surface);border:1px solid var(--color-border-strong);border-radius:var(--radius-xs);width:22px;height:22px;cursor:pointer;padding:0}
.lane-tools button:hover{border-color:var(--color-action-primary);color:var(--color-action-primary)}
.lane-tools button:disabled{opacity:.35;cursor:default}
.lane-tools .up svg{transform:rotate(180deg)}
/* режим «по датам»: колонки по плановым датам окончания, шапка колонки над дорожками; карточки двигаются внутри своей колонки */
.dcol{position:absolute;top:0;border-left:1px solid var(--line);box-sizing:border-box;pointer-events:none}
.dcol{position:absolute;top:0;border-left:1px solid var(--color-border-default);box-sizing:border-box;pointer-events:none}
.dcol:first-of-type{border-left:0}
.dcol.past{background:color-mix(in srgb,var(--p-crit) 5%,transparent)}
.dcol.today{background:color-mix(in srgb,var(--accent) 7%,transparent)}
.dcol.today{background:color-mix(in srgb,var(--color-action-primary) 7%,transparent)}
/* полоса дат над доской: липнет под шапкой страницы, по горизонтали двигается вместе с доской (transform по scrollLeft) */
.dates-bar{position:sticky;top:calc(var(--head-h,0px) + env(safe-area-inset-top,0px));z-index:9;background:var(--bg)}
.dates-in{overflow:hidden;border:1px solid var(--line);border-bottom:0;border-radius:8px 8px 0 0;background:var(--card);height:36px;box-sizing:border-box}
.dates-bar{position:sticky;top:calc(var(--head-h,0px) + env(safe-area-inset-top,0px));z-index:9;background:var(--color-bg-canvas)}
.dates-in{overflow:hidden;border:1px solid var(--color-border-default);border-bottom:0;border-radius:var(--radius-xl) var(--radius-xl) 0 0;background:var(--color-bg-surface);height:36px;box-sizing:border-box}
.dates-track{position:relative;height:100%}
.dlabel{position:absolute;top:9px;font-size:12.5px;letter-spacing:.06em;text-transform:uppercase;color:var(--fg);font-weight:600;white-space:nowrap}
.dlabel .n{font-weight:400;color:var(--muted);margin-left:6px;font-variant-numeric:tabular-nums}
.dlabel{position:absolute;top:10px;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--color-text-primary);font-weight:600;white-space:nowrap}
.dlabel .n{font-weight:400;color:var(--color-text-secondary);margin-left:6px;font-variant-numeric:tabular-nums}
.dlabel.past{color:var(--p-crit)}
.dlabel.today{color:var(--accent)}
.dlabel.none{color:var(--muted);font-weight:500}
.dlabel.today{color:var(--color-action-primary)}
.dlabel.none{color:var(--color-text-secondary);font-weight:400}
.stage.bydate .wrap{border-top-left-radius:0;border-top-right-radius:0}
.dsplit{position:absolute;top:0;bottom:0;width:11px;margin-left:-5px;cursor:col-resize;z-index:1}
.dsplit::after{content:"";position:absolute;left:5px;top:6px;bottom:6px;width:1px;background:var(--line)}
.dsplit:hover::after,.dsplit.on::after{background:var(--accent);width:3px;left:4px;border-radius:2px}
.dsplit::after{content:"";position:absolute;left:5px;top:6px;bottom:6px;width:1px;background:var(--color-border-default)}
.dsplit:hover::after,.dsplit.on::after{background:var(--color-action-primary);width:3px;left:4px;border-radius:2px}
.dates-in.resizing{cursor:col-resize}
/* горизонтальная прокрутка доски, прилипшая к низу окна: полоса всегда видна, скроллбар нарисован явно (на macOS системный прячется) */
.hscroll{position:sticky;bottom:0;z-index:8;overflow-x:scroll;overflow-y:hidden;height:14px;background:var(--bg);padding-top:2px;box-sizing:border-box;scrollbar-width:thin;scrollbar-color:var(--edge) var(--soft)}
.hscroll{position:sticky;bottom:0;z-index:8;overflow-x:scroll;overflow-y:hidden;height:14px;background:var(--color-bg-canvas);padding-top:2px;box-sizing:border-box;scrollbar-width:thin;scrollbar-color:var(--color-border-strong) var(--color-bg-track)}
.hscroll>div{height:1px}
.hscroll::-webkit-scrollbar{height:12px}
.hscroll::-webkit-scrollbar-track{background:var(--soft);border-radius:6px}
.hscroll::-webkit-scrollbar-thumb{background:var(--edge);border-radius:6px;border:2px solid var(--soft)}
.hscroll::-webkit-scrollbar-thumb:hover{background:var(--accent)}
.hscroll::-webkit-scrollbar-track{background:var(--color-bg-track);border-radius:6px}
.hscroll::-webkit-scrollbar-thumb{background:var(--color-border-strong);border-radius:6px;border:2px solid var(--color-bg-track)}
.hscroll::-webkit-scrollbar-thumb:hover{background:var(--color-action-primary)}
/* карточка: текст выделяется, тянуть можно только за рамку (появляется при наведении) и за уголок */
.card{position:absolute;width:240px;height:120px;box-sizing:border-box;background:var(--card);border:1px solid var(--line);border-radius:6px;padding:12px 14px;display:flex;flex-direction:column;gap:4px;overflow:hidden;cursor:default;touch-action:none}
.card:hover{border-color:var(--accent)}
.card{position:absolute;width:240px;height:120px;box-sizing:border-box;background:var(--color-bg-surface);border:1px solid var(--color-border-default);border-radius:var(--radius-lg);padding:12px;display:flex;flex-direction:column;gap:4px;overflow:hidden;cursor:default;touch-action:none}
.card:hover{border-color:var(--color-border-strong)}
.card.out{border-style:dashed;opacity:.7}
.card.closed{opacity:.4}
.card.closed:hover,.card.closed:focus-within{opacity:1}
.edges path.closed{opacity:.4}
.card.crit{box-shadow:0 0 0 2px var(--guide)}
.card.focus{box-shadow:0 0 0 2px var(--accent)}
.card.drag{cursor:grabbing;border-color:var(--accent);box-shadow:var(--shadow-popover);z-index:2}
.card.focus{box-shadow:0 0 0 2px var(--color-action-primary)}
.card.drag{cursor:grabbing;border-color:var(--color-action-primary);box-shadow:var(--shadow-popover);z-index:2}
.card .title,.card .state,.card .who,.card .meta,.card .key,.card .sprint-tag{user-select:text;-webkit-user-select:text;cursor:text;touch-action:auto}
.card .key{cursor:pointer}
.card .meta{cursor:default}
.card .grip{position:absolute;background:var(--accent);opacity:0;cursor:grab;z-index:1;transition:opacity .12s}
.card .grip.t{left:0;right:0;top:0;height:8px;border-radius:5px 5px 0 0}
.card .grip.b{left:0;right:14px;bottom:0;height:8px;border-radius:0 0 0 5px}
.card .grip.l{left:0;top:0;bottom:0;width:8px;border-radius:5px 0 0 5px}
.card .grip.r{right:0;top:0;bottom:14px;width:8px;border-radius:0 5px 0 0}
.card:hover .grip,.card.drag .grip{opacity:.85}
.card .grip{position:absolute;background:var(--color-action-primary);opacity:0;cursor:grab;z-index:1;transition:opacity .12s}
.card .grip.t{left:0;right:0;top:0;height:8px;border-radius:11px 11px 0 0}
.card .grip.b{left:0;right:14px;bottom:0;height:8px;border-radius:0 0 0 11px}
.card .grip.l{left:0;top:0;bottom:0;width:8px;border-radius:11px 0 0 11px}
.card .grip.r{right:0;top:0;bottom:14px;width:8px;border-radius:0 11px 0 0}
.card:hover .grip,.card.drag .grip{opacity:.6}
.card.drag .grip{cursor:grabbing}
.card .rsz{position:absolute;right:0;bottom:0;width:14px;height:14px;cursor:nwse-resize;background:linear-gradient(135deg,transparent 50%,var(--line) 50%);border-bottom-right-radius:5px;z-index:2}
.card:hover .rsz{background:linear-gradient(135deg,transparent 50%,var(--accent) 50%)}
.card .rsz{position:absolute;right:0;bottom:0;width:14px;height:14px;cursor:nwse-resize;background:linear-gradient(135deg,transparent 50%,var(--color-border-strong) 50%);border-bottom-right-radius:11px;z-index:2}
.card:hover .rsz{background:linear-gradient(135deg,transparent 50%,var(--color-action-primary) 50%)}
.head{display:flex;align-items:center;justify-content:space-between;gap:6px;min-width:0}
.key{font-family:var(--font-mono);font-size:13.5px;font-weight:500;color:var(--accent);text-decoration:none;flex:0 0 auto}
.sprint-tag{font-size:11px;line-height:1;padding:4px 7px;border-radius:10px;background:var(--soft);color:var(--muted);max-width:55%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:0 1 auto}
.sprint-tag.sel{background:var(--accent);color:var(--accent-fg)}
.sprint-tag.none{background:transparent;border:1px dashed var(--line)}
.title{font-size:14.5px;line-height:19px;min-width:0;flex:1 1 auto;overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}
.meta{display:flex;align-items:center;gap:8px;font-size:12.5px;color:var(--muted);white-space:nowrap;flex:0 0 auto}
.key{font-family:var(--font-mono);font-size:13px;font-weight:600;color:var(--color-action-primary);text-decoration:none;flex:0 0 auto}
.sprint-tag{font-size:11px;line-height:1;padding:4px 7px;border-radius:var(--radius-round);background:var(--color-bg-track);color:var(--color-text-secondary);max-width:55%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:0 1 auto}
.sprint-tag.sel{background:var(--color-action-primary);color:var(--color-text-on-primary)}
.sprint-tag.none{background:transparent;border:1px dashed var(--color-border-strong)}
.title{font-size:14.5px;line-height:19px;font-weight:600;color:var(--color-text-primary);min-width:0;flex:1 1 auto;overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}
.meta{display:flex;align-items:center;gap:8px;font-size:12.5px;color:var(--color-text-secondary);white-space:nowrap;flex:0 0 auto}
.kind{display:inline-flex;align-items:center;gap:4px}
.kind i{width:8px;height:8px;border-radius:2px;display:inline-block}
.prio{font-weight:600}
/* состояние задачи (статус, срок, исполнитель) — отдельный блок под чертой, основным цветом: это главное после названия */
.state,.who{font-size:13px;flex:0 0 auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--fg)}
.state{display:flex;align-items:center;gap:6px;margin-top:4px;padding-top:7px;border-top:1px solid var(--line)}
.status{font-size:12.5px;line-height:1;padding:4px 7px;border-radius:10px;background:var(--soft);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.state,.who{font-size:13px;flex:0 0 auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--color-text-primary)}
.state{display:flex;align-items:center;gap:6px;margin-top:4px;padding-top:7px;border-top:1px solid var(--color-border-default)}
/* статус — вид Badge (neutral) */
.status{font-size:var(--fs-caption);line-height:var(--lh-caption);font-weight:600;padding:1px 8px;border:1px solid var(--color-border-default);border-radius:var(--radius-round);background:var(--color-bg-track);color:var(--color-text-secondary);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.due{font-variant-numeric:tabular-nums;font-weight:600;flex:0 0 auto}
.who::before{content:"";display:inline-block;width:7px;height:7px;border-radius:50%;background:var(--accent);margin-right:5px;vertical-align:1px}
.who.none{color:var(--muted);font-style:italic}
.who.none::before{background:transparent;border:1px solid var(--muted)}
.who::before{content:"";display:inline-block;width:7px;height:7px;border-radius:50%;background:var(--color-action-primary);margin-right:5px;vertical-align:1px}
.who.none{color:var(--color-text-secondary);font-style:italic}
.who.none::before{background:transparent;border:1px solid var(--color-text-secondary)}
/* 7. Панель проблем справа от доски, липнет под шапкой и прокручивается внутри себя */
.drawer{flex:0 0 340px;max-width:40%;overflow:auto;box-sizing:border-box;align-self:flex-start;position:sticky;top:calc(var(--head-h,0px) + 8px);max-height:calc(100vh - var(--head-h,0px) - 24px);font-size:var(--fs-body)}
.drawer-head{display:flex;justify-content:space-between;align-items:center;gap:var(--space-2)}
.board-h2{margin:0;font-size:var(--fs-lg);line-height:var(--lh-lg);font-weight:600;color:var(--color-text-primary)}
/* 7. Панель проблем справа от доски, липнет под шапкой; прокручивается содержимое внутри рамки Panel */
.drawer{flex:0 0 340px;max-width:40%;box-sizing:border-box;align-self:flex-start;position:sticky;top:calc(var(--head-h,0px) + 8px);max-height:calc(100vh - var(--head-h,0px) - 24px);display:flex;font-size:var(--fs-body)}
.drawer>.ui-panel{display:flex;flex-direction:column;gap:var(--space-3);min-height:0;min-width:0;flex:1 1 auto;padding:var(--space-4) 0 var(--space-4) var(--space-4)}
.drawer-head{display:flex;justify-content:space-between;align-items:center;gap:var(--space-2);padding-right:var(--space-4)}
.drawer-body{overflow-y:auto;min-height:0;padding-right:var(--space-4)}
.board-h2{margin:0;font-size:var(--fs-lg);line-height:var(--lh-lg);font-weight:700;color:var(--color-text-primary)}
.board-h3{margin:var(--space-3) 0 var(--space-1);font-size:var(--fs-caption);line-height:var(--lh-caption);font-weight:600;color:var(--color-text-secondary)}
.board-h3:first-child{margin-top:0}
.drawer ul{margin:0;padding-left:18px}
.drawer li{margin:3px 0}
.drawer .k{font-family:var(--font-mono);font-size:13px;color:var(--color-action-primary);text-decoration:none}
.drawer .ok{color:var(--color-status-success);margin:var(--space-2) 0 0}
.drawer .why{color:var(--muted)}
.drawer .k{font-family:var(--font-mono);font-size:13px;font-weight:600;color:var(--color-action-primary);text-decoration:none}
.drawer .ok{color:var(--color-status-success);margin:0}
.drawer .why{color:var(--color-text-secondary)}
/* 8. Экспорт JSON и таблица связей (включаются из меню «Ещё») */
.export{display:flex;flex-direction:column;gap:var(--space-2);margin:0 0 var(--space-2)}
.export textarea{font-family:var(--font-mono);font-size:11.5px;width:100%;box-sizing:border-box;height:180px;border:1px solid var(--line);border-radius:var(--radius-sm);background:var(--card);color:var(--fg);padding:8px;resize:vertical}
.export textarea{font-family:var(--font-mono);font-size:11.5px;width:100%;box-sizing:border-box;height:180px;border:1px solid var(--color-border-default);border-radius:var(--radius-sm);background:var(--color-bg-surface);color:var(--color-text-primary);padding:8px;resize:vertical}
.link-table{margin-top:var(--space-3)}
.tbl{max-width:100%;overflow-x:auto;margin-top:var(--space-2)}
table{border-collapse:collapse;font-size:var(--fs-body);min-width:520px}
th,td{text-align:left;padding:6px 10px;border-bottom:1px solid var(--line);vertical-align:top}
th,td{text-align:left;padding:6px 10px;border-bottom:1px solid var(--color-border-default);vertical-align:top}
th{font-size:var(--fs-caption);font-weight:600;color:var(--color-text-secondary)}
td.k{font-family:var(--font-mono);font-size:12px;white-space:nowrap;color:var(--color-action-primary)}
@ -171,8 +178,8 @@ td.k{font-family:var(--font-mono);font-size:12px;white-space:nowrap;color:var(--
@media (max-width:640px){.sticky{position:static}.dates-bar{position:static}}
/* «Открыть карточку» в заголовке карточки (R-1) */
.card .open-card{margin-left:6px;color:var(--muted);text-decoration:none;font-size:13px}
.card .open-card:hover,.card .open-card:focus-visible{color:var(--accent);outline:none}
.card .open-card{margin-left:6px;color:var(--color-text-secondary);text-decoration:none;display:inline-flex;align-items:center;border-radius:var(--radius-xs)}
.card .open-card:hover{color:var(--color-action-primary)}
/* слой измерения: карточки шириной CW с высотой по содержимому, невидим */
.measure{position:absolute;left:-100000px;top:0;visibility:hidden;pointer-events:none}
.measure .card{position:static;height:auto}

View File

@ -1,5 +1,6 @@
import { memo, type PointerEvent } from "react";
import { Link } from "react-router";
import { IconOpen } from "../../ui/icons.tsx";
import { BOARD_CARD_STATE } from "../../issues/modal.ts";
import { CW, JIRA_URL, KIND, PRIO } from "../constants.ts";
import { lineClamp, type Rect, type RelBox } from "../layout/layout.ts";
@ -34,7 +35,7 @@ export const Card = memo(function Card({ node: n, box, rel, multiSprint, sprintS
<div className="head">
<a className="key" href={`${JIRA_URL}/browse/${n.key}`} target="_blank" rel="noopener">{n.key}</a>
{!n.stub && <Link className="open-card" to={`/issues/${n.key}`} state={BOARD_CARD_STATE} title="Открыть карточку задачи"
aria-label={`Открыть карточку ${n.key}`}>ⓘ</Link>}
aria-label={`Открыть карточку ${n.key}`}><IconOpen size={16} /></Link>}
{showTag && (n.sprint
? <span className={"sprint-tag" + (sprintSel.has(n.sprint) ? " sel" : "")} title={n.sprint}>{n.sprint.replace(/^AWPCA_/, "")}</span>
: <span className="sprint-tag none">без спринта</span>)}

View File

@ -1,4 +1,5 @@
import { useRef, useState, type PointerEvent } from "react";
import { IconChevronDown } from "../../ui/icons.tsx";
import type { LaneGeom } from "../layout/layout.ts";
import { plural } from "../model/util.ts";
@ -39,12 +40,12 @@ export function Lane({ g, index, last, count, lanes, boardTop, targetClass, onTo
return (
<div className={cls} style={{ top: g.y, height: g.h }}>
<div className="lane-head">
<button type="button" className="fold" title="Свернуть или развернуть дорожку" onClick={() => onToggle(g.name)} />
<button type="button" className="fold" title="Свернуть или развернуть дорожку" onClick={() => onToggle(g.name)}><IconChevronDown size={16} /></button>
<span ref={nameRef} className="lane-name" title="Перетащить, чтобы поменять дорожки местами" onPointerDown={down}>{g.name}</span>
<span className="lane-count">{plural(count, "задача", "задачи", "задач")}</span>
<span className="lane-tools">
<button type="button" title="Выше" disabled={index === 0} onClick={() => onMove(g.name, index - 1)}>▲</button>
<button type="button" title="Ниже" disabled={index === last} onClick={() => onMove(g.name, index + 1)}>▼</button>
<button type="button" className="up" title="Выше" disabled={index === 0} onClick={() => onMove(g.name, index - 1)}><IconChevronDown size={16} /></button>
<button type="button" title="Ниже" disabled={index === last} onClick={() => onMove(g.name, index + 1)}><IconChevronDown size={16} /></button>
</span>
</div>
</div>

View File

@ -20,7 +20,7 @@ export function ProblemsDrawer({ problems, onClose }: { problems: Problems; onCl
<aside className="drawer">
<Panel>
<div className="drawer-head"><h2 className="board-h2">Проблемы на доске</h2><Button variant="ghost" onClick={onClose}>Закрыть</Button></div>
<div>
<div className="drawer-body">
{!problemCount(problems) && <div className="ok">По датам, исполнителям и статусам блокеров на видимой части доски проблем нет.</div>}
<Section title="Срок блокера не раньше срока зависимой задачи" list={problems.dates} />
<Section title="Блокер без исполнителя" list={problems.unassigned} />