skillok meeting
169ce64e1705fd64e48ee357a816bbdb90bab01d
Перейти к файлу
Код
meeting srcstylesapp.css
anc Этап 1: запись встречи в Zoom, живая расшифровка и разбор - crates/audio: захват звука процесса Zoom и микрофона (WASAPI, process loopback), формат 16 кГц моно, нарезка на куски по паузам с отсевом тишины, распознавание начала и конца встречи по звуковой сессии Zoom. - Ядро: запись двух дорожек → Whisper через OpenRouter → расшифровка, живой конспект раз в три минуты, по окончании — разбор сам; звук удаляется после разбора, если не включено хранение. - Контроллер: слежу за Zoom → прошу предупредить участников → пишу; запись вручную, пауза, стоп; прерванная запись помечается при запуске. - Трей, уведомления, крестик прячет окно, запуск вместе с Windows. - Интерфейс: главный экран с карточкой состояния (задача #1), живая расшифровка и конспект на экране встречи, новые настройки. - reqwest на системном TLS: под Windows без C-кода ring. - 67 тестов; код под Windows проверен компиляцией и clippy с Mac, на Windows не запускался. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> 169ce64 30 сентября 2026 г.
app.css
/* Раскладка приложения: шапка как у skillok.org и одна колонка контента. */

.header {
  position: sticky; top: 0; z-index: 10;
  display: flex; align-items: center; gap: 24px;
  height: 56px; padding: 0 24px;
  background: var(--background);
  border-bottom: 1px solid var(--color-border);
}
.brand { display: flex; align-items: center; gap: 10px; color: var(--color-text-primary); font-weight: 700; font-size: var(--text-lg); }
.brand:hover { text-decoration: none; }
.brand-mark { width: 24px; height: 24px; }
.header-nav { display: flex; gap: 4px; margin-left: auto; }
.header-nav a {
  display: inline-flex; align-items: center; height: 32px; padding: 0 10px;
  color: var(--color-text-primary); font-size: var(--text-md);
}
.header-nav a:hover { background: var(--color-bg-hover); text-decoration: none; }
.header-nav a[aria-current="page"] { box-shadow: inset 0 -2px 0 var(--color-accent); }

.page { max-width: 960px; margin: 0 auto; padding: 32px 24px 64px; display: grid; gap: 24px; }
.page-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.page-head .meta { margin-top: 6px; }
.actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }

/* Список встреч */
.meeting-row { cursor: pointer; color: var(--color-text-primary); }
.meeting-row:hover { text-decoration: none; }
.meeting-row:hover .title { color: var(--color-accent-text); }
.meeting-row .title { font-weight: 700; flex: 1; min-width: 0; }
.meeting-row .when { width: 200px; white-space: nowrap; }
.meeting-row .status { width: 170px; text-align: right; }

/* Расшифровка */
.segment { display: grid; grid-template-columns: 72px 1fr; gap: 12px; padding: 8px 16px; border-top: 1px solid var(--kv-line-2); }
.segment:first-child { border-top: 0; }
.segment .at { color: var(--color-text-secondary); padding-top: 2px; }
.segment .who { font-weight: 700; }
.segment.is-target { background: var(--color-bg-secondary); box-shadow: inset 2px 0 0 var(--color-accent); }

/* Конспект и задачи */
.prose { display: grid; gap: 12px; max-width: 72ch; }
.prose ul { margin: 0; padding-left: 20px; display: grid; gap: 4px; }
.section { display: grid; gap: 8px; }
.task { display: grid; gap: 4px; padding: 12px 16px; border-top: 1px solid var(--color-border); }
.task:first-child { border-top: 0; }
.task .text { font-weight: 700; }
.task .quote { border-left: 2px solid var(--color-border-light); padding-left: 10px; color: var(--color-text-secondary); font-size: var(--text-sm); }
.jump { font-family: var(--font-mono); font-size: var(--text-xs); }

.empty { padding: 40px 16px; text-align: center; color: var(--color-text-secondary); }
.busy { color: var(--color-text-secondary); font-size: var(--text-sm); }

@media (max-width: 640px) {
  .meeting-row { flex-wrap: wrap; }
  .meeting-row .when, .meeting-row .status { width: auto; text-align: left; }
}

/* ── Карточка состояния (главный экран, задача #1) ── */
.status-card {
  display: grid; gap: 12px;
  padding: 24px;
  border: 1px solid var(--color-border);
  background: var(--color-bg-elevated);
}
.status-card.is-attention, .status-card.is-recording { border-color: var(--color-accent); box-shadow: inset 3px 0 0 var(--color-accent); }
.status-kicker { display: flex; align-items: center; gap: 8px; font-size: var(--text-sm); color: var(--color-text-secondary); }
.status-title { font-size: var(--text-3xl); }
.status-text { max-width: 60ch; color: var(--kv-body); }
.mono-num { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

/* Индикатор — квадрат: круг в продукте только у аватаров. */
.dot { display: inline-block; width: 8px; height: 8px; background: var(--color-text-secondary); }
.dot-accent { background: var(--color-accent); }
.dot-live { background: var(--color-accent); animation: blink 1.4s steps(2, jump-none) infinite; }
.dot-live.is-paused { animation: none; background: var(--color-text-secondary); }
@keyframes blink { 50% { opacity: 0.25; } }
@media (prefers-reduced-motion: reduce) { .dot-live { animation: none; } }

.warning-text {
  margin: 0; padding: 12px 16px; max-width: 72ch;
  border-left: 2px solid var(--color-accent);
  background: var(--color-bg-secondary);
}

.meters { display: grid; gap: 6px; max-width: 420px; }
.meter { display: grid; grid-template-columns: 110px 1fr; align-items: center; gap: 12px; }
.meter-bar { height: 8px; background: var(--color-bg-secondary); overflow: hidden; }
.meter-fill { display: block; height: 100%; width: 0; background: var(--color-accent); transition: width 0.18s linear; }

.live-lines { display: grid; gap: 4px; max-width: 72ch; font-size: var(--text-sm); min-height: 60px; }
.live-lines .who { font-weight: 700; }

/* Второстепенное действие — ссылкой, а не второй кнопкой (одна контурная кнопка на экран). */
.link-btn {
  padding: 0 4px; min-height: 32px;
  font: inherit; font-size: var(--text-sm);
  color: var(--color-accent-text); background: none; border: 0; cursor: pointer;
  text-decoration: underline; text-underline-offset: 3px;
}
.link-btn:hover { color: var(--color-text-primary); }
.link-btn[disabled] { opacity: 0.5; cursor: default; }
a.ui-btn { text-decoration: none; }
a.ui-btn:hover { text-decoration: none; }

.section-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }

/* Расшифровка: свои реплики отмечены полосой слева. */
.segment.is-me { box-shadow: inset 2px 0 0 var(--color-border-light); }
.segment.is-me.is-target { box-shadow: inset 2px 0 0 var(--color-accent); }

.live-summary .box-head { align-items: baseline; }
.pre { white-space: pre-wrap; }
select.input { appearance: auto; }