@meistrari/minuta-nuxt 0.1.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,6 +5,8 @@ import { sanitizeRichHtml } from '../utils/sanitizeRichHtml'
5
5
  import { vaultImageHtml } from '../composables/vaultImages'
6
6
  import { sanitizeFootnoteHtml } from '../utils/footnoteHtml'
7
7
  import MinutaIcon from './MinutaIcon.vue'
8
+ import MinutaVariablePicker from './MinutaVariablePicker.vue'
9
+ import type { MinutaVariableDef } from '../utils/templateVariables'
8
10
  import type { Editor } from '@tiptap/core'
9
11
  import type { PocPageConfig, PocPageNote } from '../composables/paginationPlugin'
10
12
  import { POC_ASSET_SWAP, buildPocExtensions, renumberPocFootnotes } from '../composables/editorExtensions'
@@ -29,6 +31,13 @@ const props = defineProps<{
29
31
  * falha), a imagem vira data-URI embutida no documento.
30
32
  */
31
33
  uploadImage?: ((file: File) => Promise<{ src: string, vaultSrc: string } | null>) | null
34
+ /**
35
+ * Catálogo de variáveis do app hospedeiro. Com ele o editor vira editor de
36
+ * TEMPLATE: `/` e o botão da toolbar inserem placeholders. Vazio (o padrão),
37
+ * nada muda — placeholders já existentes no documento continuam sendo
38
+ * renderizados, só não há como inserir novos.
39
+ */
40
+ variables?: MinutaVariableDef[]
32
41
  }>()
33
42
 
34
43
  const emit = defineEmits<{
@@ -141,7 +150,10 @@ onMounted(() => {
141
150
  element: editorHost.value!,
142
151
  content: props.initialHtml,
143
152
  editable: props.editable,
144
- extensions: buildPocExtensions(pageConfig),
153
+ extensions: buildPocExtensions(pageConfig, {
154
+ variables: () => props.variables ?? [],
155
+ handleKeyDown: onEditorKeyDown,
156
+ }),
145
157
  onCreate: ({ editor }) => {
146
158
  emit('ready', editor as Editor)
147
159
  nextTick(() => {
@@ -153,6 +165,7 @@ onMounted(() => {
153
165
  },
154
166
  onTransaction: ({ transaction }) => {
155
167
  uiTick.value++
168
+ syncVariableTrigger(transaction.docChanged)
156
169
  if (transaction.docChanged) {
157
170
  // troca de imagem fria (load progressivo) não é edição do
158
171
  // advogado: repagina, mas não marca o doc como sujo — senão
@@ -189,7 +202,10 @@ watch(containerRef, (el) => {
189
202
  if (!el)
190
203
  return
191
204
  resizeObserver = new ResizeObserver(() => {
192
- zoom.value = Math.min(1, (el.clientWidth - 48) / A4_WIDTH_PX)
205
+ // respiro lateral da folha: 48px dá moldura no desktop, mas em tela de
206
+ // celular custa ~13% do zoom e o texto fica ilegível
207
+ const breathing = el.clientWidth < 640 ? 12 : 48
208
+ zoom.value = Math.min(1, (el.clientWidth - breathing) / A4_WIDTH_PX)
193
209
  })
194
210
  resizeObserver.observe(el)
195
211
  }, { immediate: true })
@@ -658,6 +674,177 @@ function clearColor() {
658
674
  editor.value?.chain().focus().unsetMark('inlineStyle').run()
659
675
  }
660
676
 
677
+ // --- variáveis de template: gatilho `/`, botão da toolbar e seletor ---
678
+
679
+ const hasVariables = computed(() => (props.variables?.length ?? 0) > 0)
680
+ const variablePickerRef = ref<any>(null)
681
+ /**
682
+ * `from` = posição do `/` que abriu o seletor (apagado na inserção); `null`
683
+ * quando veio do botão da toolbar, que não digitou nada no documento.
684
+ * `query === undefined` faz o popover usar o próprio campo de busca.
685
+ */
686
+ const variablePicker = ref<{ top: number, left: number, from: number | null, query: string | undefined } | null>(null)
687
+
688
+ /**
689
+ * Coordenada do documento → coordenada dentro da folha.
690
+ *
691
+ * A folha tem `zoom` CSS: `coordsAtPos` devolve px de TELA e o `top`/`left` do
692
+ * popover é interpretado em px de LAYOUT lá dentro. Sem dividir pelo zoom o
693
+ * seletor desgruda do cursor em qualquer zoom diferente de 100%.
694
+ */
695
+ function sheetCoords(pos: number): { top: number, left: number } | null {
696
+ const ed = editor.value
697
+ const sheet = sheetRef.value
698
+ if (!ed || ed.isDestroyed || !sheet)
699
+ return null
700
+ let coords
701
+ try {
702
+ coords = ed.view.coordsAtPos(pos)
703
+ }
704
+ catch {
705
+ return null
706
+ }
707
+ const rect = sheet.getBoundingClientRect()
708
+ const z = zoom.value || 1
709
+ return {
710
+ top: (coords.bottom - rect.top) / z + 6,
711
+ left: Math.max(8, Math.min((coords.left - rect.left) / z, A4_WIDTH_PX - 330)),
712
+ }
713
+ }
714
+
715
+ /**
716
+ * `/` no início de palavra abre o seletor; o que vem depois filtra.
717
+ *
718
+ * Exige delimitador à esquerda para não disparar dentro de uma data ("12/03")
719
+ * nem de uma referência de artigo ("art. 5º, II/CF") — onde a barra é conteúdo,
720
+ * não comando.
721
+ */
722
+ const VARIABLE_TRIGGER_RE = /(?:^|[\s(\[«"'])\/([\p{L}\d_]*)$/u
723
+ /** acima disto não é mais busca, é texto — o gatilho desiste */
724
+ const VARIABLE_TRIGGER_MAX_QUERY = 24
725
+
726
+ function closeVariablePicker() {
727
+ variablePicker.value = null
728
+ }
729
+
730
+ /**
731
+ * Reavalia o gatilho a cada transação. Só ABRE quando o documento mudou (foi
732
+ * alguém digitando); mover o cursor para perto de uma barra que já estava no
733
+ * texto não abre nada, mas fecha o que estiver aberto.
734
+ */
735
+ function syncVariableTrigger(docChanged: boolean) {
736
+ const ed = editor.value
737
+ if (!ed || ed.isDestroyed || !hasVariables.value || !props.editable) {
738
+ if (variablePicker.value)
739
+ closeVariablePicker()
740
+ return
741
+ }
742
+ // seletor aberto pela toolbar não tem gatilho no texto para reavaliar
743
+ if (variablePicker.value && variablePicker.value.from === null)
744
+ return
745
+
746
+ const { from, empty } = ed.state.selection
747
+ if (!empty) {
748
+ closeVariablePicker()
749
+ return
750
+ }
751
+
752
+ const blockStart = ed.state.doc.resolve(from).start()
753
+ // '\ufffc' (object replacement) para nós atômicos: um chip antes do cursor
754
+ // conta como 1 caractere e não some do texto, senão o `^` casaria errado
755
+ const textBefore = ed.state.doc.textBetween(blockStart, from, '\n', '\uFFFC')
756
+ const match = textBefore.match(VARIABLE_TRIGGER_RE)
757
+
758
+ if (!match || (match[1]?.length ?? 0) > VARIABLE_TRIGGER_MAX_QUERY) {
759
+ closeVariablePicker()
760
+ return
761
+ }
762
+ if (!variablePicker.value && !docChanged)
763
+ return
764
+
765
+ const slashPos = from - match[0].length + match[0].indexOf('/')
766
+ const coords = sheetCoords(slashPos)
767
+ if (!coords) {
768
+ closeVariablePicker()
769
+ return
770
+ }
771
+ variablePicker.value = { ...coords, from: slashPos, query: match[1] ?? '' }
772
+ }
773
+
774
+ /**
775
+ * Botão da toolbar: mesmo seletor, ancorado no cursor, com campo de busca
776
+ * próprio.
777
+ *
778
+ * NÃO devolve o foco ao editor aqui. O `focus()` do Tiptap é adiado por um
779
+ * `requestAnimationFrame`, então ele chegava DEPOIS do popover montar e roubava
780
+ * o foco do campo de busca — o que a pessoa digitava virava texto do documento
781
+ * e o Enter inseria o primeiro item de uma lista que nunca chegou a ser
782
+ * filtrada. A posição do cursor não depende do foco: `state.selection`
783
+ * continua onde estava (ou no começo, se ninguém nunca clicou no documento).
784
+ */
785
+ function openVariablePicker() {
786
+ const ed = editor.value
787
+ if (!ed || ed.isDestroyed || !hasVariables.value)
788
+ return
789
+ const coords = sheetCoords(ed.state.selection.from)
790
+ if (!coords)
791
+ return
792
+ variablePicker.value = { ...coords, from: null, query: undefined }
793
+ }
794
+
795
+ function insertVariable(variable: MinutaVariableDef) {
796
+ const ed = editor.value
797
+ if (!ed || ed.isDestroyed)
798
+ return
799
+ const trigger = variablePicker.value
800
+ const chain = ed.chain().focus()
801
+ // some com o `/consulta` que abriu o seletor — o placeholder toma o lugar dele
802
+ if (trigger?.from != null)
803
+ chain.deleteRange({ from: trigger.from, to: ed.state.selection.from })
804
+ chain.insertContent({
805
+ type: 'templateVariable',
806
+ attrs: { varKey: variable.key, label: variable.label },
807
+ }).run()
808
+ closeVariablePicker()
809
+ }
810
+
811
+ /**
812
+ * Com o seletor aberto o foco continua NO EDITOR (o usuário está digitando o
813
+ * texto do filtro), então as setas e o Enter têm que ser interceptados aqui —
814
+ * senão andam o cursor e quebram o parágrafo.
815
+ */
816
+ function onEditorKeyDown(_view: unknown, event: KeyboardEvent): boolean {
817
+ const picker = variablePickerRef.value
818
+ if (!variablePicker.value || !picker)
819
+ return false
820
+ if (event.key === 'ArrowDown') {
821
+ picker.move(1)
822
+ return true
823
+ }
824
+ if (event.key === 'ArrowUp') {
825
+ picker.move(-1)
826
+ return true
827
+ }
828
+ if (event.key === 'Escape') {
829
+ closeVariablePicker()
830
+ return true
831
+ }
832
+ if (event.key === 'Enter' || event.key === 'Tab') {
833
+ // sem resultado o Enter é Enter: quebrar o parágrafo é o que a pessoa quis
834
+ if (!picker.hasResults())
835
+ return false
836
+ picker.choose()
837
+ return true
838
+ }
839
+ return false
840
+ }
841
+
842
+ // catálogo esvaziado (ou editor virou read-only) com o seletor aberto
843
+ watch(() => [hasVariables.value, props.editable], () => {
844
+ if (variablePicker.value && (!hasVariables.value || !props.editable))
845
+ closeVariablePicker()
846
+ })
847
+
661
848
  defineExpose({ editor, schedulePaginate, refreshChrome, containerRef })
662
849
  </script>
663
850
 
@@ -759,6 +946,15 @@ defineExpose({ editor, schedulePaginate, refreshChrome, containerRef })
759
946
  <MinutaIcon name="i-ph-image" />
760
947
  </button>
761
948
  <input ref="imageInput" type="file" accept="image/*" class="hidden" @change="onImageFile">
949
+ <button
950
+ v-if="hasVariables"
951
+ class="poc-tool"
952
+ :class="{ 'poc-tool--on': !!variablePicker }"
953
+ title="Inserir variável (ou digite / no texto)"
954
+ @click="openVariablePicker"
955
+ >
956
+ <MinutaIcon name="i-ph-brackets-curly" />
957
+ </button>
762
958
  <button class="poc-tool" title="Inserir nota de rodapé" @click="insertFootnote">
763
959
  <MinutaIcon name="i-ph-note" />
764
960
  </button>
@@ -770,7 +966,7 @@ defineExpose({ editor, schedulePaginate, refreshChrome, containerRef })
770
966
  </button>
771
967
  </div>
772
968
 
773
- <div ref="containerRef" class="relative min-h-0 w-full flex-1 overflow-y-auto bg-gray-120 py-24px" @click="openPalette = null">
969
+ <div ref="containerRef" class="relative min-h-0 w-full flex-1 overflow-y-auto bg-gray-120 py-24px" @click="openPalette = null; closeVariablePicker()">
774
970
  <div ref="sheetRef" class="poc-sheet relative mx-auto bg-white shadow-md" :style="sheetStyle" @click="onNotesClick">
775
971
  <!-- cabeçalho da 1ª página (na margem superior da folha) -->
776
972
  <div class="poc-first-header" :style="firstHeaderStyle" title="Clique para editar o cabeçalho" @click="editChrome('header')">
@@ -817,6 +1013,17 @@ defineExpose({ editor, schedulePaginate, refreshChrome, containerRef })
817
1013
  <div v-else class="poc-chrome-text" />
818
1014
  <span v-if="chromeText.showPageNumber !== false" class="ml-auto">Página {{ totalPages }} de {{ totalPages }}</span>
819
1015
  </div>
1016
+ <MinutaVariablePicker
1017
+ v-if="variablePicker"
1018
+ ref="variablePickerRef"
1019
+ :variables="props.variables ?? []"
1020
+ :top="variablePicker.top"
1021
+ :left="variablePicker.left"
1022
+ :query="variablePicker.query"
1023
+ @select="insertVariable"
1024
+ @close="closeVariablePicker"
1025
+ />
1026
+
820
1027
  <slot name="gutter" />
821
1028
 
822
1029
  <!-- popover de edição da nota de rodapé (ancorado na chamada) -->
@@ -933,7 +1140,7 @@ defineExpose({ editor, schedulePaginate, refreshChrome, containerRef })
933
1140
  </template>
934
1141
 
935
1142
  <style scoped>
936
- :deep(.poc-ins){background:rgba(34,197,94,.16);text-decoration:underline;text-decoration-color:#16a34a}:deep(.poc-del){background:rgba(239,68,68,.12);color:#b91c1c;text-decoration:line-through}.poc-bubble-btn{border-radius:6px;font-size:13px;padding:4px 8px;transition:all .15s;transition-timing-function:ease}.poc-toolbar{background:#fafbfc;border-bottom:.5px solid #dee3e7;flex-wrap:wrap;gap:2px;padding:4px 12px}.poc-tool,.poc-toolbar{align-items:center;display:flex}.poc-tool{border-radius:6px;font-size:13px;height:28px;justify-content:center;min-width:28px;padding-left:6px;padding-right:6px;position:relative;transition:all .15s;transition-timing-function:ease}.poc-tool-sep{background:#dee3e7;height:20px;margin:0 6px;width:1px}.poc-tool-select{background:#fff;border-radius:6px;font-size:12px;height:28px;padding-left:6px;padding-right:6px}.poc-color-underline{background:#dc2626;border-radius:1px;bottom:4px;height:3px;left:7px;position:absolute;right:7px}.poc-palette{background:#fff;border:.5px solid #dee3e7;border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,.12);display:flex;flex-wrap:wrap;gap:2px;left:0;padding:8px;position:absolute;top:32px;width:230px;z-index:50}.poc-palette-swatch{border:1px solid rgba(0,0,0,.1);border-radius:3px;cursor:pointer;height:19px;width:19px}.poc-palette-swatch:hover{transform:scale(1.2)}.poc-palette-custom{align-items:center;color:#565d69;cursor:pointer;display:flex;font-size:12px;margin-top:6px;width:100%}.poc-color-swatch{border:1px solid rgba(0,0,0,.12);border-radius:4px;cursor:pointer;height:16px;margin:0 2px;width:16px}.poc-color-swatch:hover{transform:scale(1.15)}.poc-first-header{align-items:center;cursor:pointer;display:flex;gap:12px;position:absolute;top:0}.poc-first-header[style*=center]{justify-content:center}.poc-first-header[style*=right]{justify-content:flex-end}.poc-first-header:hover,.poc-last-footer:hover,:deep(.poc-page-footerarea:hover),:deep(.poc-page-headerarea:hover){background:rgba(99,102,241,.06);outline:1px dashed #c7d2fe}.poc-last-footer{align-items:center;bottom:0;color:#94a3b8;cursor:pointer;display:flex;font-size:11px;justify-content:space-between;position:absolute}.poc-chrome-text,:deep(.poc-chrome-text){color:#64748b;font-size:11px}.poc-chrome-text :deep(img),:deep(.poc-chrome-text img){display:inline-block;max-height:48px;max-width:200px;vertical-align:middle}:deep(.poc-pagebreak-marker){border-top:1px dashed #a5b4fc;font-size:0!important;height:16px;line-height:0!important;margin:6px 0!important;position:relative}:deep(.poc-pagebreak-marker):after{background:#fff;color:#818cf8;content:"quebra de página do template";font-size:10px;left:50%;line-height:16px;padding:0 8px;position:absolute;top:-8px;transform:translateX(-50%)}:deep(.poc-page-chrome){pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}:deep(.poc-page-footerarea),:deep(.poc-page-headerarea){cursor:pointer;pointer-events:auto}:deep(.poc-page-footerarea){align-items:center;color:#94a3b8;display:flex;font-size:11px;justify-content:space-between}:deep(.poc-page-gapband){background:#eceef1;box-shadow:inset 0 6px 6px -6px rgba(0,0,0,.25),inset 0 -6px 6px -6px rgba(0,0,0,.25)}:deep(.poc-page-headerarea){align-items:center;display:flex}:deep(.poc-page-headerarea[style*=center]){justify-content:center}:deep(.poc-page-headerarea[style*=right]){justify-content:flex-end}:deep(.ProseMirror){outline:none;white-space:pre-wrap;word-wrap:break-word}:deep(h1){color:#0f172a;font-size:16px;font-weight:540;letter-spacing:-.2px;line-height:20px;margin-bottom:20px}:deep(h2){color:#0f172a;font-size:14px;font-size:24px;font-weight:540;letter-spacing:-.15px;line-height:16px;margin-bottom:20px}:deep(h3){color:#0f172a;font-size:12px;font-size:20px;font-weight:540;letter-spacing:-.1px;line-height:14px;margin-bottom:20px}:deep(strong){font-weight:540}:deep(p){color:#000;font-size:18px;font-weight:400;letter-spacing:-.4px;line-height:28px;line-height:1.85em;margin-bottom:2em}:deep(blockquote){font-size:18px;font-weight:400;letter-spacing:-.4px;line-height:28px;margin-bottom:2em;margin-left:35%}:deep(ol),:deep(ul){margin-bottom:2em}:deep(li){list-style-position:inside;list-style-type:disc;margin-bottom:16px;margin-left:48px}:deep(table){border:.5px solid #d4d9df;max-width:100%;table-layout:fixed;width:100%}:deep(th){color:inherit;font-size:inherit;font-weight:700;overflow-wrap:anywhere;padding:12px;text-align:left}:deep(td){color:inherit;font-size:inherit;line-height:inherit;overflow-wrap:anywhere;padding:12px;text-align:left}:deep(td p),:deep(th p){color:inherit;font-size:inherit;line-height:inherit;margin-bottom:0}:deep(img){display:block;max-width:100%}:deep(.poc-island--img>img),:deep(.poc-island>img:only-child){margin-left:auto;margin-right:auto}:deep(.poc-chrome-canvas){overflow:hidden}:deep(.poc-chrome-canvas [data-el]){position:absolute}:deep(.poc-chrome-canvas [data-el=text]){color:#64748b;font-size:11px;line-height:1.4;text-align:left;white-space:pre-wrap}:deep(img[data-vault-src]){animation:poc-img-loading 1.4s ease-in-out infinite}@keyframes poc-img-loading{0%,to{opacity:.5}50%{opacity:.9}}:deep(.poc-lone-row){justify-content:center!important}:deep(.poc-lone-row>:not(.poc-lone-cell)){flex:0 0 0!important;min-width:0!important;padding:0!important}:deep(.poc-lone-row>.poc-lone-cell){align-items:center!important;flex:0 0 auto!important;max-width:100%;min-width:0}:deep([data-horizontal-align=left] .poc-island--img>img),:deep([data-horizontal-align=left] .poc-island>img:only-child){margin-left:0}:deep([data-horizontal-align=right] .poc-island--img>img),:deep([data-horizontal-align=right] .poc-island>img:only-child){margin-right:0}.poc-review-cursor :deep(.poc-minuta-block){cursor:pointer}:deep(.poc-minuta-block){border-radius:4px;position:relative}:deep(.poc-minuta-block:hover){outline:1px dashed #c7d2fe;outline-offset:6px}.poc-no-block-origin :deep(.poc-minuta-block:hover){outline:none}.poc-no-block-origin :deep(.poc-minuta-block:hover):before{content:none}:deep(.poc-minuta-block:hover):before{background:#eef2ff;border:1px solid #c7d2fe;border-radius:4px;color:#6366f1;content:attr(data-block-title);font-size:10px;letter-spacing:.04em;padding:1px 8px;pointer-events:none;position:absolute;right:0;text-transform:uppercase;top:-22px;z-index:3}:deep(.poc-varchip){background:rgba(59,130,246,.06);border-radius:3px;box-shadow:inset 0 0 0 1px rgba(59,130,246,.25);cursor:pointer;transition:background .15s ease}:deep(.poc-varchip:hover){background:rgba(59,130,246,.16)}:deep(.poc-missing-image .missing-image-warning:hover){background-color:rgba(254,174,0,.09);border-color:rgba(223,139,13,.85)}:deep(.poc-missing-image__remove){background:hsla(0,0%,100%,.75);border:none;border-radius:999px;color:#773613;cursor:pointer;font-size:16px;height:22px;line-height:1;opacity:0;position:absolute;right:4px;top:4px;transition:opacity .15s ease,background .15s ease;width:22px}:deep(.poc-missing-image:hover .poc-missing-image__remove){opacity:1}:deep(.poc-missing-image__remove:hover){background:rgba(254,174,0,.35)}:deep(.poc-missing-image.ProseMirror-selectednode .missing-image-warning){outline:2px solid rgba(223,139,13,.9);outline-offset:2px}.poc-page-notes,:deep(.poc-page-notes){color:#1c242d;font-size:10.67px;line-height:1.25;padding-top:6px}.poc-page-notes:before,:deep(.poc-page-notes):before{border-top:.5px solid #9aa4ae;content:"";display:block;margin-bottom:6px;width:30%}.poc-page-note,:deep(.poc-page-note){color:#1c242d;font-size:10.67px;font-weight:400;line-height:1.25;margin:0;text-align:justify}.poc-page-note sup,:deep(.poc-page-note sup){font-size:.8em;line-height:0;vertical-align:super}.poc-page-note a,:deep(.poc-page-note a){color:#0563c1;text-decoration:underline}:deep(.poc-footnote){color:#1d4ed8;cursor:help;font-size:.7em;line-height:0;vertical-align:super}:deep(.poc-island){position:relative}:deep(.poc-island--img){cursor:grab}:deep(.poc-img-handle){background:#3b82f6;border:2px solid #fff;border-radius:3px;bottom:-6px;box-shadow:0 1px 3px rgba(0,0,0,.3);cursor:nwse-resize;height:12px;opacity:0;position:absolute;right:-6px;transition:opacity .12s ease;width:12px}:deep(.poc-island--img.ProseMirror-selectednode .poc-img-handle),:deep(.poc-island--img:hover .poc-img-handle){opacity:1}:deep(.poc-island.ProseMirror-selectednode){outline:2px solid #93c5fd;outline-offset:2px}
1143
+ :deep(.poc-ins){background:rgba(34,197,94,.16);text-decoration:underline;text-decoration-color:#16a34a}:deep(.poc-del){background:rgba(239,68,68,.12);color:#b91c1c;text-decoration:line-through}.poc-bubble-btn{border-radius:6px;font-size:13px;padding:4px 8px;transition:all .15s;transition-timing-function:ease}.poc-toolbar{align-items:center;background:#fafbfc;border-bottom:.5px solid #dee3e7;display:flex;flex-wrap:wrap;gap:2px;padding:4px 12px}@media (max-width:640px){.poc-toolbar{flex-wrap:nowrap;overflow-x:auto;padding:4px 8px;scrollbar-width:none}.poc-toolbar::-webkit-scrollbar{display:none}.poc-toolbar>*{flex:none}}.poc-tool{align-items:center;border-radius:6px;display:flex;font-size:13px;height:28px;justify-content:center;min-width:28px;padding-left:6px;padding-right:6px;position:relative;transition:all .15s;transition-timing-function:ease}.poc-tool-sep{background:#dee3e7;height:20px;margin:0 6px;width:1px}.poc-tool-select{background:#fff;border-radius:6px;font-size:12px;height:28px;padding-left:6px;padding-right:6px}.poc-color-underline{background:#dc2626;border-radius:1px;bottom:4px;height:3px;left:7px;position:absolute;right:7px}.poc-palette{background:#fff;border:.5px solid #dee3e7;border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,.12);display:flex;flex-wrap:wrap;gap:2px;left:0;padding:8px;position:absolute;top:32px;width:230px;z-index:50}.poc-palette-swatch{border:1px solid rgba(0,0,0,.1);border-radius:3px;cursor:pointer;height:19px;width:19px}.poc-palette-swatch:hover{transform:scale(1.2)}.poc-palette-custom{align-items:center;color:#565d69;cursor:pointer;display:flex;font-size:12px;margin-top:6px;width:100%}.poc-color-swatch{border:1px solid rgba(0,0,0,.12);border-radius:4px;cursor:pointer;height:16px;margin:0 2px;width:16px}.poc-color-swatch:hover{transform:scale(1.15)}.poc-first-header{align-items:center;cursor:pointer;display:flex;gap:12px;position:absolute;top:0}.poc-first-header[style*=center]{justify-content:center}.poc-first-header[style*=right]{justify-content:flex-end}.poc-first-header:hover,.poc-last-footer:hover,:deep(.poc-page-footerarea:hover),:deep(.poc-page-headerarea:hover){background:rgba(99,102,241,.06);outline:1px dashed #c7d2fe}.poc-last-footer{align-items:center;bottom:0;color:#94a3b8;cursor:pointer;display:flex;font-size:11px;justify-content:space-between;position:absolute}.poc-chrome-text,:deep(.poc-chrome-text){color:#64748b;font-size:11px}.poc-chrome-text :deep(img),:deep(.poc-chrome-text img){display:inline-block;max-height:48px;max-width:200px;vertical-align:middle}:deep(.poc-pagebreak-marker){border-top:1px dashed #a5b4fc;font-size:0!important;height:16px;line-height:0!important;margin:6px 0!important;position:relative}:deep(.poc-pagebreak-marker):after{background:#fff;color:#818cf8;content:"quebra de página do template";font-size:10px;left:50%;line-height:16px;padding:0 8px;position:absolute;top:-8px;transform:translateX(-50%)}:deep(.poc-page-chrome){display:block;pointer-events:none;text-align:initial;text-indent:0;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:100%}:deep(.poc-page-footerarea),:deep(.poc-page-headerarea){cursor:pointer;pointer-events:auto}:deep(.poc-page-footerarea){align-items:center;color:#94a3b8;display:flex;font-size:11px;justify-content:space-between}:deep(.poc-page-gapband){background:#eceef1;box-shadow:inset 0 6px 6px -6px rgba(0,0,0,.25),inset 0 -6px 6px -6px rgba(0,0,0,.25)}:deep(.poc-page-headerarea){align-items:center;display:flex}:deep(.poc-page-headerarea[style*=center]){justify-content:center}:deep(.poc-page-headerarea[style*=right]){justify-content:flex-end}:deep(.ProseMirror){outline:none;white-space:pre-wrap;word-wrap:break-word}:deep(h1){color:#0f172a;font-size:24px;font-weight:640;letter-spacing:-.3px;line-height:32px;margin:0 0 20px}:deep(h2){color:#0f172a;font-size:19px;font-weight:620;letter-spacing:-.2px;line-height:26px;margin:28px 0 14px}:deep(h3){color:#0f172a;font-size:17px;font-weight:600;letter-spacing:-.1px;line-height:24px;margin:24px 0 12px}:deep(h1):first-child,:deep(h2):first-child,:deep(h3):first-child{margin-top:0}:deep(strong){font-weight:540}:deep(p){color:#000;font-size:18px;font-weight:400;letter-spacing:-.4px;line-height:28px;line-height:1.7em;margin-bottom:.75em;text-align:justify}:deep(blockquote){font-size:18px;font-weight:400;letter-spacing:-.4px;line-height:28px;margin-bottom:2em;margin-left:35%}:deep(ol),:deep(ul){margin-bottom:2em}:deep(li){list-style-position:inside;margin-bottom:16px;margin-left:48px}:deep(ul)>li{list-style-type:disc}:deep(ol)>li{list-style-type:decimal}:deep(table){border:.5px solid #d4d9df;max-width:100%;table-layout:fixed;width:100%}:deep(th){color:inherit;font-size:inherit;font-weight:700;overflow-wrap:anywhere;padding:12px;text-align:left}:deep(td){color:inherit;font-size:inherit;line-height:inherit;overflow-wrap:anywhere;padding:12px;text-align:left}:deep(td p),:deep(th p){color:inherit;font-size:inherit;line-height:inherit;margin-bottom:0}:deep(img){display:block;max-width:100%}:deep(.poc-island--img>img),:deep(.poc-island>img:only-child){margin-left:auto;margin-right:auto}:deep(.poc-chrome-canvas){overflow:hidden}:deep(.poc-chrome-canvas [data-el]){position:absolute}:deep(.poc-chrome-canvas [data-el=text]){color:#64748b;font-size:11px;line-height:1.4;text-align:left;white-space:pre-wrap}:deep(img[data-vault-src]){animation:poc-img-loading 1.4s ease-in-out infinite}@keyframes poc-img-loading{0%,to{opacity:.5}50%{opacity:.9}}:deep(.poc-lone-row){justify-content:center!important}:deep(.poc-lone-row>:not(.poc-lone-cell)){flex:0 0 0!important;min-width:0!important;padding:0!important}:deep(.poc-lone-row>.poc-lone-cell){align-items:center!important;flex:0 0 auto!important;max-width:100%;min-width:0}:deep([data-horizontal-align=left] .poc-island--img>img),:deep([data-horizontal-align=left] .poc-island>img:only-child){margin-left:0}:deep([data-horizontal-align=right] .poc-island--img>img),:deep([data-horizontal-align=right] .poc-island>img:only-child){margin-right:0}.poc-review-cursor :deep(.poc-minuta-block){cursor:pointer}:deep(.poc-minuta-block){border-radius:4px;position:relative}:deep(.poc-minuta-block:hover){outline:1px dashed #c7d2fe;outline-offset:6px}.poc-no-block-origin :deep(.poc-minuta-block:hover){outline:none}.poc-no-block-origin :deep(.poc-minuta-block:hover):before{content:none}:deep(.poc-minuta-block:hover):before{background:#eef2ff;border:1px solid #c7d2fe;border-radius:4px;color:#6366f1;content:attr(data-block-title);font-size:10px;letter-spacing:.04em;padding:1px 8px;pointer-events:none;position:absolute;right:0;text-transform:uppercase;top:-22px;z-index:3}:deep(.poc-varchip){background:rgba(59,130,246,.06);border-radius:3px;box-shadow:inset 0 0 0 1px rgba(59,130,246,.25);cursor:pointer;transition:background .15s ease}:deep(.poc-varchip:hover){background:rgba(59,130,246,.16)}:deep(.poc-tplvar){background:rgba(251,191,36,.16);border-radius:3px;box-shadow:inset 0 0 0 1px rgba(180,83,9,.35);color:#78350f;display:inline;padding:0 2px;white-space:pre-wrap}:deep(.poc-tplvar--unknown){background:rgba(244,63,94,.12);box-shadow:inset 0 0 0 1px rgba(190,18,60,.45);color:#881337;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}:deep(.ProseMirror-selectednode.poc-tplvar){box-shadow:inset 0 0 0 2px rgba(180,83,9,.75)}:deep(.poc-missing-image .missing-image-warning:hover){background-color:rgba(254,174,0,.09);border-color:rgba(223,139,13,.85)}:deep(.poc-missing-image__remove){background:hsla(0,0%,100%,.75);border:none;border-radius:999px;color:#773613;cursor:pointer;font-size:16px;height:22px;line-height:1;opacity:0;position:absolute;right:4px;top:4px;transition:opacity .15s ease,background .15s ease;width:22px}:deep(.poc-missing-image:hover .poc-missing-image__remove){opacity:1}:deep(.poc-missing-image__remove:hover){background:rgba(254,174,0,.35)}:deep(.poc-missing-image.ProseMirror-selectednode .missing-image-warning){outline:2px solid rgba(223,139,13,.9);outline-offset:2px}.poc-page-notes,:deep(.poc-page-notes){color:#1c242d;font-size:10.67px;line-height:1.25;padding-top:6px}.poc-page-notes:before,:deep(.poc-page-notes):before{border-top:.5px solid #9aa4ae;content:"";display:block;margin-bottom:6px;width:30%}.poc-page-note,:deep(.poc-page-note){color:#1c242d;font-size:10.67px;font-weight:400;line-height:1.25;margin:0;text-align:justify}.poc-page-note sup,:deep(.poc-page-note sup){font-size:.8em;line-height:0;vertical-align:super}.poc-page-note a,:deep(.poc-page-note a){color:#0563c1;text-decoration:underline}:deep(.poc-footnote){color:#1d4ed8;cursor:help;font-size:.7em;line-height:0;vertical-align:super}:deep(.poc-island){position:relative}:deep(.poc-island--img){cursor:grab}:deep(.poc-img-handle){background:#3b82f6;border:2px solid #fff;border-radius:3px;bottom:-6px;box-shadow:0 1px 3px rgba(0,0,0,.3);cursor:nwse-resize;height:12px;opacity:0;position:absolute;right:-6px;transition:opacity .12s ease;width:12px}:deep(.poc-island--img.ProseMirror-selectednode .poc-img-handle),:deep(.poc-island--img:hover .poc-img-handle){opacity:1}:deep(.poc-island.ProseMirror-selectednode){outline:2px solid #93c5fd;outline-offset:2px}
937
1144
  </style>
938
1145
 
939
1146
  <style scoped>
@@ -1017,17 +1224,6 @@ defineExpose({ editor, schedulePaginate, refreshChrome, containerRef })
1017
1224
  border: 0.5px solid #d4d9df;
1018
1225
  }
1019
1226
 
1020
- /* no CDB os tokens de heading vencem os overrides text-24px/text-20px — replica */
1021
- .poc-editor-root :deep(h2) {
1022
- font-size: 14px;
1023
- line-height: 16px;
1024
- }
1025
-
1026
- .poc-editor-root :deep(h3) {
1027
- font-size: 12px;
1028
- line-height: 14px;
1029
- }
1030
-
1031
1227
  /* popover de nota de rodapé (ancorado na chamada, estilo comentário) */
1032
1228
  .poc-footnote-pop {
1033
1229
  position: absolute;
@@ -251,6 +251,12 @@ async function exportDocx() {
251
251
  // ==========================================
252
252
 
253
253
  const historyOpen = ref(false)
254
+ /**
255
+ * Painel de análise em tela estreita. Declarado aqui, e não junto do resto do
256
+ * layout responsivo, porque `toggleHistory` precisa fechá-lo: os dois painéis
257
+ * são mutuamente exclusivos.
258
+ */
259
+ const showAnalysisPanel = ref(false)
254
260
  const versions = ref<MinutaVersion[]>([])
255
261
  const loadingVersions = ref(false)
256
262
  const savingCheckpoint = ref(false)
@@ -296,6 +302,8 @@ function toggleHistory() {
296
302
  closeHistory()
297
303
  return
298
304
  }
305
+ // só cabe um painel: dois de 320px lado a lado esmagam o documento
306
+ showAnalysisPanel.value = false
299
307
  historyOpen.value = true
300
308
  void loadVersions()
301
309
  }
@@ -446,6 +454,44 @@ async function applyChrome({ html, scope, showPageNumber }: { html: string, scop
446
454
  }
447
455
  }
448
456
 
457
+ /**
458
+ * Comentários de margem só cabem quando há folha (≈794px) + coluna (264px) +
459
+ * respiro. Abaixo disso a margem fica fora da tela — então os cards viram um
460
+ * painel lateral que o usuário abre e fecha.
461
+ */
462
+ const GUTTER_MIN_WIDTH = 1120
463
+ const bodyRef = ref<HTMLElement | null>(null)
464
+ const bodyWidth = ref(0)
465
+ let bodyResizeObserver: ResizeObserver | null = null
466
+
467
+ const narrowLayout = computed(() => bodyWidth.value > 0 && bodyWidth.value < GUTTER_MIN_WIDTH)
468
+
469
+ function openAnalysisPanel() {
470
+ // mutuamente exclusivo com o histórico (ver toggleHistory)
471
+ closeHistory()
472
+ showAnalysisPanel.value = true
473
+ }
474
+ const analysisCount = computed(() => {
475
+ const a = minuta.value?.analysis
476
+ if (!a)
477
+ return 0
478
+ return (a.teses?.length ?? 0) + (a.jurisprudencias?.length ?? 0) + (a.citacoes?.length ?? 0) + (a.fatos?.length ?? 0)
479
+ })
480
+
481
+ watch(bodyRef, (el) => {
482
+ bodyResizeObserver?.disconnect()
483
+ bodyResizeObserver = null
484
+ if (!el || typeof ResizeObserver === 'undefined')
485
+ return
486
+ bodyWidth.value = el.clientWidth
487
+ bodyResizeObserver = new ResizeObserver(() => {
488
+ bodyWidth.value = el.clientWidth
489
+ })
490
+ bodyResizeObserver.observe(el)
491
+ }, { immediate: true })
492
+
493
+ onUnmounted(() => bodyResizeObserver?.disconnect())
494
+
449
495
  onMounted(() => window.addEventListener('poc:edit-chrome', onEditChrome))
450
496
  onUnmounted(() => window.removeEventListener('poc:edit-chrome', onEditChrome))
451
497
 
@@ -523,10 +569,22 @@ const editorCompRef = ref<any>(null)
523
569
  const docPreviewOpen = ref(false)
524
570
  const docPreviewUrl = ref<string | null>(null)
525
571
  const docPreviewPage = ref<number | null>(null)
526
-
527
- function openDocPreview(url?: string | null) {
572
+ /** trecho que o card de fato pediu para destacar no documento-fonte */
573
+ const docPreviewHighlight = ref<string | null>(null)
574
+
575
+ /**
576
+ * `fromCard` separa os dois chamadores: o botão Documentos abre a lista limpa,
577
+ * e o card de fato extraído mantém página e destaque mesmo quando o documento
578
+ * que ele nomeia não casa com nenhum anexo (aí abre o primeiro arquivo, mas a
579
+ * localização que o card pediu continua valendo).
580
+ */
581
+ function openDocPreview(url?: string | null, fromCard = false) {
528
582
  if (!minuta.value?.files.length)
529
583
  return
584
+ if (!fromCard) {
585
+ docPreviewPage.value = null
586
+ docPreviewHighlight.value = null
587
+ }
530
588
  docPreviewUrl.value = url ?? minuta.value.files[0].url
531
589
  docPreviewOpen.value = true
532
590
  }
@@ -555,7 +613,8 @@ function onOpenDocument(payload: { documento: string, pagina?: number, highlight
555
613
  const file = minuta.value?.files.find(f => f.name === payload.documento)
556
614
  ?? minuta.value?.files.find(f => f.name.includes(payload.documento) || payload.documento.includes(f.name))
557
615
  docPreviewPage.value = payload.pagina ?? null
558
- openDocPreview(file?.url)
616
+ docPreviewHighlight.value = payload.highlight ?? null
617
+ openDocPreview(file?.url, true)
559
618
  }
560
619
 
561
620
  const hasAnalysis = computed(() => {
@@ -592,32 +651,41 @@ defineExpose({ saveNow, exportDocx, editor })
592
651
  >
593
652
  <span v-else class="minuta-embed__name">{{ minuta.name }}</span>
594
653
  <span class="minuta-embed__save-state">{{ SAVE_LABEL[saveState] }}</span>
595
- <button
654
+ <TelaButton
596
655
  v-if="minuta.status === 'READY' && minuta.files.length"
597
- class="minuta-embed__ghost-btn"
598
- type="button"
656
+ variant="ghost"
657
+ size="sm"
658
+ icon="i-ph-files"
659
+ leading
660
+ class="minuta-embed__toolbar-btn"
599
661
  @click="openDocPreview()"
600
662
  >
601
- Documentos
602
- </button>
603
- <button
663
+ <span class="minuta-embed__btn-label">Documentos</span>
664
+ </TelaButton>
665
+ <TelaButton
604
666
  v-if="minuta.status === 'READY'"
605
- class="minuta-embed__ghost-btn"
606
- :class="{ 'minuta-embed__ghost-btn--on': historyOpen }"
607
- type="button"
667
+ :variant="historyOpen ? 'secondary' : 'ghost'"
668
+ size="sm"
669
+ icon="i-ph-clock-counter-clockwise"
670
+ leading
671
+ class="minuta-embed__toolbar-btn"
608
672
  @click="toggleHistory"
609
673
  >
610
- Histórico
611
- </button>
612
- <button
674
+ <span class="minuta-embed__btn-label">Histórico</span>
675
+ </TelaButton>
676
+ <TelaButton
613
677
  v-if="minuta.status === 'READY'"
614
- class="minuta-embed__export"
678
+ variant="primary"
679
+ size="sm"
680
+ icon="i-ph-download-simple"
681
+ leading
682
+ :loading="isExporting"
615
683
  :disabled="isExporting || !!versionPreview"
616
- type="button"
684
+ class="minuta-embed__toolbar-btn"
617
685
  @click="exportDocx"
618
686
  >
619
- {{ isExporting ? 'Exportando…' : 'Baixar DOCX' }}
620
- </button>
687
+ <span class="minuta-embed__btn-label">{{ isExporting ? 'Exportando…' : 'Baixar DOCX' }}</span>
688
+ </TelaButton>
621
689
  </div>
622
690
 
623
691
  <div v-if="isLoading" class="minuta-embed__status">
@@ -636,7 +704,7 @@ defineExpose({ saveNow, exportDocx, editor })
636
704
  A geração falhou: {{ minuta.error || 'erro desconhecido' }}
637
705
  </div>
638
706
 
639
- <div v-else-if="minuta" class="minuta-embed__body">
707
+ <div v-else-if="minuta" ref="bodyRef" class="minuta-embed__body">
640
708
  <div class="minuta-embed__document">
641
709
  <MinutaDocxEditor
642
710
  ref="editorCompRef"
@@ -652,7 +720,7 @@ defineExpose({ saveNow, exportDocx, editor })
652
720
  >
653
721
  <template #gutter>
654
722
  <MinutaAnnotationsGutter
655
- v-if="hasAnalysis && minuta.analysis"
723
+ v-if="hasAnalysis && minuta.analysis && !narrowLayout"
656
724
  :editor="editor"
657
725
  :analysis="minuta.analysis"
658
726
  :scroll-container="editorCompRef?.containerRef ?? null"
@@ -662,6 +730,17 @@ defineExpose({ saveNow, exportDocx, editor })
662
730
  </template>
663
731
  </MinutaDocxEditor>
664
732
 
733
+ <!-- tela estreita: os comentários viram painel lateral -->
734
+ <button
735
+ v-if="hasAnalysis && narrowLayout && !showAnalysisPanel"
736
+ type="button"
737
+ class="minuta-embed__analysis-toggle"
738
+ @click="openAnalysisPanel"
739
+ >
740
+ Análise
741
+ <span class="minuta-embed__analysis-count">{{ analysisCount }}</span>
742
+ </button>
743
+
665
744
  <!-- edição de cabeçalho/rodapé (canvas estilo Word) -->
666
745
  <div v-if="chromeEditor" class="minuta-embed__chrome-overlay">
667
746
  <MinutaChromeEditor
@@ -703,6 +782,26 @@ defineExpose({ saveNow, exportDocx, editor })
703
782
  </div>
704
783
  </div>
705
784
 
785
+ <!-- painel: análise (tela estreita — sem margem para ancorar os cards) -->
786
+ <aside v-if="hasAnalysis && narrowLayout && showAnalysisPanel && minuta.analysis" class="minuta-embed__panel minuta-embed__panel--analysis">
787
+ <div class="minuta-embed__panel-head">
788
+ <span>Análise da peça</span>
789
+ <button type="button" @click="showAnalysisPanel = false">
790
+ ×
791
+ </button>
792
+ </div>
793
+ <div class="minuta-embed__panel-scroll">
794
+ <MinutaAnnotationsGutter
795
+ :editor="editor"
796
+ :analysis="minuta.analysis"
797
+ :scroll-container="editorCompRef?.containerRef ?? null"
798
+ compact
799
+ @open-document="onOpenDocument"
800
+ @update-analysis="onAnalysisUpdate"
801
+ />
802
+ </div>
803
+ </aside>
804
+
706
805
  <!-- painel: histórico -->
707
806
  <aside v-if="historyOpen" class="minuta-embed__panel">
708
807
  <div class="minuta-embed__panel-head">
@@ -771,6 +870,7 @@ defineExpose({ saveNow, exportDocx, editor })
771
870
  :files="minuta.files"
772
871
  :initial-url="docPreviewUrl"
773
872
  :initial-page="docPreviewPage"
873
+ :highlight="docPreviewHighlight"
774
874
  @close="docPreviewOpen = false"
775
875
  @error="msg => emit('error', msg)"
776
876
  />
@@ -779,5 +879,5 @@ defineExpose({ saveNow, exportDocx, editor })
779
879
  </template>
780
880
 
781
881
  <style scoped>
782
- .minuta-embed{background:#f7f7f8;display:flex;flex-direction:column;height:100%}.minuta-embed__toolbar{align-items:center;background:#fff;border-bottom:1px solid #e5e7eb;display:flex;gap:8px;padding:8px 16px}.minuta-embed__name--input{background:transparent;border:0;font-family:inherit;outline:none}.minuta-embed__name--input:focus{text-decoration:underline}.minuta-embed__name{color:#111827;flex:1;font-size:15px;font-weight:500;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.minuta-embed__save-state{color:#9ca3af;font-size:12px;margin-right:4px}.minuta-embed__ghost-btn{background:#fff;border:1px solid #e5e7eb;border-radius:6px;color:#374151;cursor:pointer;font-size:13px;padding:6px 12px}.minuta-embed__ghost-btn--on{background:#f3f4f6}.minuta-embed__export{background:#111827;border:none;border-radius:6px;color:#fff;cursor:pointer;font-size:13px;padding:6px 12px}.minuta-embed__export:disabled{cursor:default;opacity:.6}.minuta-embed__chrome-overlay{bottom:16px;position:absolute;right:16px;z-index:30}.minuta-embed__status{color:#6b7280;font-size:14px;padding:48px 24px;text-align:center}.minuta-embed__status--error{color:#b91c1c}.minuta-embed__body,.minuta-embed__document{display:flex;flex:1;min-height:0}.minuta-embed__document{min-width:0;position:relative}.minuta-embed__banner{align-items:center;background:#fffbeb;border:1px solid #fcd34d;border-radius:16px;bottom:16px;box-shadow:0 4px 6px -1px rgba(0,0,0,.1);display:flex;flex-wrap:wrap;gap:8px 12px;justify-content:center;left:50%;max-width:calc(100% - 32px);padding:8px 16px;position:absolute;transform:translateX(-50%);width:-moz-max-content;width:max-content;z-index:30}.minuta-embed__banner-text{color:#92400e;font-size:12px;font-weight:500}.minuta-embed__banner-actions{display:flex;gap:8px}.minuta-embed__banner-actions button{background:#fff;border:1px solid #fcd34d;border-radius:6px;color:#92400e;cursor:pointer;font-size:12px;padding:4px 10px;white-space:nowrap}.minuta-embed__banner-actions button:disabled{cursor:default;opacity:.6}.minuta-embed__panel{background:#fff;border-left:1px solid #e5e7eb;display:flex;flex-direction:column;width:320px}.minuta-embed__panel-head{align-items:center;border-bottom:1px solid #e5e7eb;color:#111827;display:flex;font-size:14px;font-weight:600;justify-content:space-between;padding:10px 16px}.minuta-embed__panel-head button{background:none;border:none;color:#6b7280;cursor:pointer;font-size:18px;line-height:1}.minuta-embed__checkpoint{border-bottom:1px solid #e5e7eb;display:flex;flex-direction:column;gap:8px;padding:12px}.minuta-embed__checkpoint input{border:1px solid #e5e7eb;border-radius:6px;font-size:13px;padding:6px 10px}.minuta-embed__checkpoint button{background:#fff;border:1px solid #e5e7eb;border-radius:6px;cursor:pointer;font-size:13px;padding:6px 12px}.minuta-embed__versions{flex:1;min-height:0;overflow-y:auto;padding:8px}.minuta-embed__versions-empty{color:#6b7280;font-size:12px;margin:0;padding:8px 12px}.minuta-embed__version{background:#fff;border:1px solid #e5e7eb;border-radius:10px;cursor:pointer;display:block;margin-bottom:6px;padding:10px;text-align:left;width:100%}.minuta-embed__version:hover{background:#faf5ff;border-color:#c4b5fd}.minuta-embed__version--active{background:#f5f3ff;border-color:#a78bfa}.minuta-embed__version:disabled{cursor:wait;opacity:.7}.minuta-embed__version-row{align-items:center;color:#111827;display:flex;font-size:12px;gap:6px}.minuta-embed__version-type{background:#f3f4f6;border-radius:999px;color:#4b5563;font-size:11px;font-style:normal;padding:1px 8px}.minuta-embed__version-type[data-type=auto]{background:#f5f3ff;color:#6d28d9}.minuta-embed__version-type[data-type=manual]{background:#eff6ff;color:#1d4ed8}.minuta-embed__version-date{color:#9ca3af;font-size:11px;margin-left:auto}.minuta-embed__version-comment{color:#111827;display:block;font-size:12px;margin-top:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.minuta-embed__version-author{color:#9ca3af;display:block;font-size:11px;margin-top:2px}
882
+ .minuta-embed{background:#f7f7f8;display:flex;flex-direction:column;height:100%}.minuta-embed__toolbar{align-items:center;background:#fff;border-bottom:1px solid #e5e7eb;display:flex;gap:8px;padding:8px 16px}@media (max-width:640px){.minuta-embed__toolbar{gap:4px;padding:6px 10px}.minuta-embed__btn-label{clip-path:inset(50%);height:1px;overflow:hidden;position:absolute;white-space:nowrap;width:1px}.minuta-embed__save-state{display:none}}.minuta-embed__name--input{background:transparent;border:0;font-family:inherit;outline:none}.minuta-embed__name--input:focus{text-decoration:underline}.minuta-embed__name{color:#111827;flex:1;font-size:15px;font-weight:500;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.minuta-embed__save-state{color:#9ca3af;font-size:12px;margin-right:4px}.minuta-embed__chrome-overlay{bottom:16px;position:absolute;right:16px;z-index:30}.minuta-embed__status{color:#6b7280;font-size:14px;padding:48px 24px;text-align:center}.minuta-embed__status--error{color:#b91c1c}.minuta-embed__body,.minuta-embed__document{display:flex;flex:1;min-height:0}.minuta-embed__document{min-width:0;position:relative}.minuta-embed__banner{align-items:center;background:#fffbeb;border:1px solid #fcd34d;border-radius:16px;bottom:16px;box-shadow:0 4px 6px -1px rgba(0,0,0,.1);display:flex;flex-wrap:wrap;gap:8px 12px;justify-content:center;left:50%;max-width:calc(100% - 32px);padding:8px 16px;position:absolute;transform:translateX(-50%);width:-moz-max-content;width:max-content;z-index:30}.minuta-embed__banner-text{color:#92400e;font-size:12px;font-weight:500}.minuta-embed__banner-actions{display:flex;gap:8px}.minuta-embed__banner-actions button{background:#fff;border:1px solid #fcd34d;border-radius:6px;color:#92400e;cursor:pointer;font-size:12px;padding:4px 10px;white-space:nowrap}.minuta-embed__banner-actions button:disabled{cursor:default;opacity:.6}.minuta-embed__analysis-toggle{align-items:center;background:#fff;border:1px solid #d4d9df;border-radius:999px;bottom:16px;box-shadow:0 2px 10px rgba(15,23,42,.12);color:#334155;cursor:pointer;display:inline-flex;font-size:13px;font-weight:540;gap:8px;padding:8px 14px;position:absolute;right:16px;z-index:20}.minuta-embed__analysis-toggle:hover{border-color:#b8bfc9;color:#0f172a}.minuta-embed__analysis-count{background:#eef1f5;border-radius:999px;color:#64748b;font-size:11px;font-variant-numeric:tabular-nums;padding:1px 7px}.minuta-embed__panel--analysis .minuta-embed__panel-scroll{flex:1;min-height:0;overflow-y:auto;padding:12px}.minuta-embed__panel{background:#fff;border-left:1px solid #e5e7eb;display:flex;flex-direction:column;flex-shrink:0;max-width:55%;width:320px}.minuta-embed__panel-head{align-items:center;border-bottom:1px solid #e5e7eb;color:#111827;display:flex;font-size:14px;font-weight:600;justify-content:space-between;padding:10px 16px}.minuta-embed__panel-head button{background:none;border:none;color:#6b7280;cursor:pointer;font-size:18px;line-height:1}.minuta-embed__checkpoint{border-bottom:1px solid #e5e7eb;display:flex;flex-direction:column;gap:8px;padding:12px}.minuta-embed__checkpoint input{border:1px solid #e5e7eb;border-radius:6px;font-size:13px;padding:6px 10px}.minuta-embed__checkpoint button{background:#fff;border:1px solid #e5e7eb;border-radius:6px;cursor:pointer;font-size:13px;padding:6px 12px}.minuta-embed__versions{flex:1;min-height:0;overflow-y:auto;padding:8px}.minuta-embed__versions-empty{color:#6b7280;font-size:12px;margin:0;padding:8px 12px}.minuta-embed__version{background:#fff;border:1px solid #e5e7eb;border-radius:10px;cursor:pointer;display:block;margin-bottom:6px;padding:10px;text-align:left;width:100%}.minuta-embed__version:hover{background:#faf5ff;border-color:#c4b5fd}.minuta-embed__version--active{background:#f5f3ff;border-color:#a78bfa}.minuta-embed__version:disabled{cursor:wait;opacity:.7}.minuta-embed__version-row{align-items:center;color:#111827;display:flex;font-size:12px;gap:6px}.minuta-embed__version-type{background:#f3f4f6;border-radius:999px;color:#4b5563;font-size:11px;font-style:normal;padding:1px 8px}.minuta-embed__version-type[data-type=auto]{background:#f5f3ff;color:#6d28d9}.minuta-embed__version-type[data-type=manual]{background:#eff6ff;color:#1d4ed8}.minuta-embed__version-date{color:#9ca3af;font-size:11px;margin-left:auto}.minuta-embed__version-comment{color:#111827;display:block;font-size:12px;margin-top:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.minuta-embed__version-author{color:#9ca3af;display:block;font-size:11px;margin-top:2px}
783
883
  </style>
@@ -5,6 +5,7 @@ import {
5
5
  PhArrowUUpRight,
6
6
  PhArrowsOutLineHorizontal,
7
7
  PhArrowsOutLineVertical,
8
+ PhBracketsCurly,
8
9
  PhEraser,
9
10
  PhHighlighter,
10
11
  PhImage,
@@ -29,6 +30,7 @@ const ICONS: Record<string, unknown> = {
29
30
  'i-ph-arrow-u-up-right': PhArrowUUpRight,
30
31
  'i-ph-arrows-out-line-horizontal': PhArrowsOutLineHorizontal,
31
32
  'i-ph-arrows-out-line-vertical': PhArrowsOutLineVertical,
33
+ 'i-ph-brackets-curly': PhBracketsCurly,
32
34
  'i-ph-eraser': PhEraser,
33
35
  'i-ph-highlighter': PhHighlighter,
34
36
  'i-ph-image': PhImage,