feat(ui): botões 'rich' em CSS puro + toolbar mobile em 3 linhas organizadas

Sistema de botões (mesma linguagem do Rich Button do spell.sh — que é
React/Tailwind e não se aplica a um app vanilla com CSP script-src 'self'):
- gradiente vertical com gloss de vidro (::after), borda e sombra derivadas
  dos tokens, lift no hover, press no active, focus ring, glow no primário
- variantes primary/danger/icon/small, selects e segmented control ricos
- 100% via color-mix() + light-dark() sobre os tokens: adapta-se sozinho aos
  21 temas (color-scheme declarado em todos, incl. nativos agora)
- cores antes fixas (#cfe2fa/#e9e6e2/#dcd7d2/#dbe9fb/#e5effb) tokenizadas

Toolbar responsiva (≤760px):
- fim do scroll horizontal: quebra em 3 linhas — arquivo+formato+versões /
  alternador de modo full-width / ferramentas de formatação full-width com
  flex-wrap de segurança; alvos ≥40px
- verificado em 393px (última borda 385) e 320px (311), uma linha de ícones

sw.js: CACHE notas-shell-v8; README atualizado
This commit is contained in:
lgcosta committed 2026-11-05 09:28:20 -03:00
1 parent cb8feac6ff
commit ea6b3b915d
3 files changed
+181 -42

No files matched your search

+14 -5
View File
@@ -117,11 +117,20 @@ ocupa a tela inteira; menus e toolbar rolam horizontalmente com botões só de
guardado em **cookie HttpOnly/SameSite=Lax** (30 dias). A API também aceita
`Authorization: Bearer <token>` para scripts.
- **Responsivo para celular** (≤760px): barra lateral vira gaveta com backdrop (☰ fecha ao
tocar uma nota), painel de versões ocupa a tela toda, toolbar e menus rolam horizontalmente
sem rótulos, **dropdowns dos menus posicionados como `fixed`** (o `overflow-x` da menubar não
os corta mais em telas pequenas), alvos de toque ampliados, diálogos em largura total, fonte
do editor com mínimo de 16px (evita zoom automático do iOS), altura em `dvh` e
`safe-area-inset` para notch.
tocar uma nota), painel de versões ocupa a tela toda, **toolbar quebra em 3 linhas
organizadas** — ① ações de arquivo + formato + versões, ② alternador de modo em largura
total, ③ ferramentas de formatação distribuídas (cabe em 393px **e** 320px sem corte) —,
alvos de toque ≥40px, diálogos em largura total, fonte do editor com mínimo de 16px (evita
zoom automático do iOS), altura em `dvh` e `safe-area-inset` para notch; **dropdowns dos
menus em `position:fixed`** (o `overflow-x` da menubar não os corta mais) e menus rolam
horizontalmente sem rótulos.
- **Botões "rich"** (linguagem visual do Rich Button/spell.sh, mas em CSS puro — o componente
deles é React+Tailwind, incompatível com o build-free + CSP do app): gradiente vertical com
brilho de vidro, reflexo interno superior, sombra dupla e glow no primário, micro-interações
de hover (eleva) / active (pressiona) / focus ring. Tudo derivado dos tokens com
`color-mix()` + `light-dark()`, então **os 21 temas** (claros e escuros) ganham botões
coerentes automaticamente; o mesmo vale para o controle segmentado Editar/WYSIWYG/Preview,
selects da toolbar e chips.
- **Estilo Mousepad**: tema claro Greybird-like, menus nativos (Arquivo/Editar/Exibir/Ajuda),
atalhos `Ctrl+S`, `Ctrl+N`, `Ctrl+E/M/R` (edição/wysiwyg/preview), `Ctrl+H` (histórico), `F9`
(barra lateral), `Ctrl+B/I/K` (formatação), `Ctrl+,` (preferências), `Alt+W`/`Alt+←/→`/
+166 -36
View File
@@ -1,6 +1,7 @@
/* Notas — tema claro sóbrio inspirado no Mousepad (XFCE/Greybird). */
:root {
color-scheme: light;
--bg: #f6f5f4;
--panel: #ffffff;
--panel-2: #f0eeeb;
@@ -119,7 +120,7 @@ code { font-family: var(--mono); font-size: 0.92em; }
white-space: nowrap;
}
.menu-dropdown li:hover { background: var(--accent); color: #fff; }
.menu-dropdown li:hover .accel { color: #e5effb; }
.menu-dropdown li:hover .accel { color: color-mix(in oklab, var(--accent), white 78%); }
.menu-dropdown .accel { color: var(--muted); font-size: 11px; }
.menu-sep { height: 0; padding: 0; margin: 4px 8px; border-top: 1px solid var(--border-soft); cursor: default; }
.menu-sep:hover { background: none; }
@@ -141,49 +142,158 @@ code { font-family: var(--mono); font-size: 0.92em; }
.tb-grow { flex: 1; }
.tb-label { font-size: 12px; margin-left: 4px; }
/* ---------------------------------------------------------------------------
Sistema de botões "rich" (mesma linguagem visual do Rich Button do
spell.sh — gradientes, brilho interno, glow e micro-interações — porém 100%
CSS puro, sem React/Tailwind, derivado dos tokens de tema via color-mix +
light-dark, portanto compatível com os 21 temas e com CSP estrita).
--------------------------------------------------------------------------- */
.btn {
position: relative;
isolation: isolate;
font: inherit;
font-size: 13px;
color: var(--fg);
background: var(--panel-2);
border: 1px solid var(--border);
border-radius: 3px;
padding: 3px 10px;
border: 1px solid color-mix(in oklab, var(--border) 70%, var(--fg) 30%);
border-radius: 8px;
padding: 4px 12px;
cursor: pointer;
background:
linear-gradient(180deg,
color-mix(in oklab, var(--panel-2), white 55%) 0%,
var(--panel-2) 48%,
color-mix(in oklab, var(--panel-2), var(--fg) 10%) 100%);
box-shadow:
inset 0 1px 0 light-dark(rgba(255, 255, 255, .85), rgba(255, 255, 255, .14)),
inset 0 -1px 0 color-mix(in oklab, var(--panel-2), var(--fg) 14%),
0 1px 2px rgba(0, 0, 0, .12),
0 2px 6px -2px rgba(0, 0, 0, .10);
text-shadow: 0 1px 0 light-dark(rgba(255, 255, 255, .7), rgba(0, 0, 0, .35));
transition: transform .12s ease, box-shadow .16s ease, filter .16s ease;
}
.btn:hover { background: #e6e2dd; }
.btn:active { border-color: var(--accent-dark); }
.btn:disabled { opacity: .45; cursor: default; }
.btn-icon { display: inline-flex; align-items: center; padding: 3px 8px; }
.btn-primary { background: var(--accent); border-color: var(--accent-dark); color: #fff; }
.btn-primary:hover { background: var(--accent-dark); }
.btn-danger { background: var(--danger); border-color: #9c1720; color: #fff; }
.btn-small { font-size: 12px; padding: 2px 8px; }
.fmt { min-width: 30px; justify-content: center; font-size: 13px; }
.btn::after { /* reflexo superior estilo "vidro" */
content: '';
position: absolute;
inset: 1px 1px 50% 1px;
z-index: -1;
border-radius: 7px 7px 10px 10px;
background: linear-gradient(180deg, light-dark(rgba(255, 255, 255, .5), rgba(255, 255, 255, .07)), transparent);
pointer-events: none;
}
.btn:hover {
transform: translateY(-1px);
filter: brightness(1.04) saturate(1.05);
box-shadow:
inset 0 1px 0 light-dark(rgba(255, 255, 255, .9), rgba(255, 255, 255, .18)),
inset 0 -1px 0 color-mix(in oklab, var(--panel-2), var(--fg) 16%),
0 3px 8px -1px rgba(0, 0, 0, .18),
0 6px 16px -6px rgba(0, 0, 0, .14);
}
.btn:active {
transform: translateY(1px) scale(.98);
filter: brightness(.97);
box-shadow:
inset 0 2px 5px rgba(0, 0, 0, .22),
inset 0 -1px 0 light-dark(rgba(255, 255, 255, .5), rgba(255, 255, 255, .08));
}
.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.btn:disabled {
opacity: .45;
cursor: default;
transform: none;
filter: saturate(.4);
box-shadow: inset 0 1px 0 light-dark(rgba(255, 255, 255, .6), rgba(255, 255, 255, .1));
}
.btn-icon { display: inline-flex; align-items: center; justify-content: center; padding: 4px 10px; }
.btn-primary {
color: #fff;
text-shadow: 0 1px 1px rgba(0, 0, 0, .25);
border-color: color-mix(in oklab, var(--accent), black 28%);
background:
linear-gradient(180deg,
color-mix(in oklab, var(--accent), white 26%) 0%,
var(--accent) 48%,
color-mix(in oklab, var(--accent), black 14%) 100%);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, .38),
inset 0 -1px 0 color-mix(in oklab, var(--accent), black 24%),
0 2px 8px -1px color-mix(in oklab, var(--accent) 55%, transparent),
0 1px 2px rgba(0, 0, 0, .18);
}
.btn-primary::after { background: linear-gradient(180deg, rgba(255, 255, 255, .28), transparent); }
.btn-primary:hover {
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, .45),
inset 0 -1px 0 color-mix(in oklab, var(--accent), black 26%),
0 4px 14px -2px color-mix(in oklab, var(--accent) 65%, transparent),
0 2px 4px rgba(0, 0, 0, .2);
}
.btn-danger {
color: #fff;
text-shadow: 0 1px 1px rgba(0, 0, 0, .25);
border-color: color-mix(in oklab, var(--danger), black 26%);
background:
linear-gradient(180deg,
color-mix(in oklab, var(--danger), white 24%) 0%,
var(--danger) 48%,
color-mix(in oklab, var(--danger), black 12%) 100%);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, .35),
inset 0 -1px 0 color-mix(in oklab, var(--danger), black 22%),
0 2px 8px -1px color-mix(in oklab, var(--danger) 50%, transparent);
}
.btn-danger::after { background: linear-gradient(180deg, rgba(255, 255, 255, .25), transparent); }
.btn-small { font-size: 12px; padding: 2px 9px; border-radius: 6px; }
.fmt { min-width: 32px; justify-content: center; font-size: 13px; }
#fmt-tools.disabled { opacity: .45; pointer-events: none; }
#toolbar select {
font: inherit;
font-size: 13px;
color: var(--fg);
background: var(--panel);
border: 1px solid var(--border);
border-radius: 3px;
padding: 3px 6px;
background:
linear-gradient(180deg,
color-mix(in oklab, var(--panel), white 40%) 0%,
var(--panel) 50%,
color-mix(in oklab, var(--panel), var(--fg) 6%) 100%);
border: 1px solid color-mix(in oklab, var(--border) 70%, var(--fg) 30%);
border-radius: 8px;
padding: 4px 8px;
box-shadow: inset 0 1px 0 light-dark(rgba(255, 255, 255, .8), rgba(255, 255, 255, .12)), 0 1px 2px rgba(0, 0, 0, .1);
}
.segmented { display: inline-flex; border: 1px solid var(--border); border-radius: 4px; overflow: hidden; }
.segmented {
display: inline-flex;
border: 1px solid color-mix(in oklab, var(--border) 70%, var(--fg) 30%);
border-radius: 9px;
overflow: hidden;
background: color-mix(in oklab, var(--panel-2), var(--fg) 6%);
box-shadow: inset 0 1px 3px rgba(0, 0, 0, .12);
padding: 2px;
gap: 2px;
}
.segmented .seg {
border: 0;
background: var(--panel-2);
background: transparent;
font: inherit;
font-size: 12px;
padding: 4px 12px;
padding: 4px 14px;
cursor: pointer;
color: var(--fg);
color: var(--muted);
border-radius: 7px;
transition: background .15s ease, color .15s ease, box-shadow .15s ease;
}
.segmented .seg:hover { color: var(--fg); background: color-mix(in oklab, var(--panel), transparent 35%); }
.segmented .seg.active {
background:
linear-gradient(180deg,
color-mix(in oklab, var(--accent), white 22%) 0%,
var(--accent) 55%,
color-mix(in oklab, var(--accent), black 10%) 100%);
color: #fff;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .35), 0 1px 4px color-mix(in oklab, var(--accent) 50%, transparent);
text-shadow: 0 1px 1px rgba(0, 0, 0, .25);
}
.segmented .seg + .seg { border-left: 1px solid var(--border); }
.segmented .seg.active { background: var(--accent); color: #fff; }
/* ---------------------------------------------------------------- layout */
#layout { display: flex; flex: 1 1 auto; min-height: 0; }
@@ -233,7 +343,7 @@ body.sidebar-off #sidebar { display: none; }
.tag-chip:hover { border-color: var(--accent); }
.tag-chip.active { background: var(--accent); border-color: var(--accent-dark); color: #fff; }
.tag-chip .tag-n { color: var(--muted); margin-left: 3px; font-size: 10px; }
.tag-chip.active .tag-n { color: #dbe9fb; }
.tag-chip.active .tag-n { color: color-mix(in oklab, var(--accent), white 75%); }
.side-notes { flex: 1 1 auto; border-bottom: 0; display: flex; flex-direction: column; }
.side-listhead { display: flex; justify-content: space-between; align-items: baseline; }
@@ -244,8 +354,8 @@ body.sidebar-off #sidebar { display: none; }
border-bottom: 1px solid var(--border-soft);
cursor: pointer;
}
#notes-list li:hover { background: #e9e6e2; }
#notes-list li.active { background: #cfe2fa; box-shadow: inset 3px 0 0 var(--accent); }
#notes-list li:hover { background: color-mix(in oklab, var(--panel-2), var(--fg) 9%); }
#notes-list li.active { background: color-mix(in oklab, var(--panel), var(--accent) 24%); box-shadow: inset 3px 0 0 var(--accent); }
.n-title { font-size: 13px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.n-sub { font-size: 11px; color: var(--muted); margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.n-ext { color: var(--accent-dark); font-family: var(--mono); font-size: 10px; }
@@ -415,8 +525,8 @@ body.no-gutter #gutter { display: none; }
.panel-head .side-label { margin: 0; }
#revisions-list { list-style: none; margin: 0; padding: 0; overflow-y: auto; flex: 0 1 40%; }
#revisions-list li { padding: 7px 10px; border-bottom: 1px solid var(--border-soft); cursor: pointer; }
#revisions-list li:hover { background: #e9e6e2; }
#revisions-list li.active { background: #cfe2fa; box-shadow: inset 3px 0 0 var(--accent); }
#revisions-list li:hover { background: color-mix(in oklab, var(--panel-2), var(--fg) 9%); }
#revisions-list li.active { background: color-mix(in oklab, var(--panel), var(--accent) 24%); box-shadow: inset 3px 0 0 var(--accent); }
.rev-top { display: flex; justify-content: space-between; font-size: 12px; gap: 8px; }
.rev-hash { font-family: var(--mono); color: var(--accent-dark); font-size: 11px; }
.rev-msg { font-size: 12px; margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@@ -594,8 +704,8 @@ body.no-gutter #gutter { display: none; }
font-size: 13px;
color: var(--fg);
}
.tree-row:hover { background: #e9e6e2; }
.tree-row.active { background: #cfe2fa; box-shadow: inset 3px 0 0 var(--accent); }
.tree-row:hover { background: color-mix(in oklab, var(--panel-2), var(--fg) 9%); }
.tree-row.active { background: color-mix(in oklab, var(--panel), var(--accent) 24%); box-shadow: inset 3px 0 0 var(--accent); }
.tree-caret {
border: 0;
background: none;
@@ -625,7 +735,7 @@ body.no-gutter #gutter { display: none; }
visibility: hidden;
}
.tree-row:hover .tree-menu, .tree-row.active .tree-menu { visibility: visible; }
.tree-menu:hover { background: #dcd7d2; color: var(--fg); }
.tree-menu:hover { background: color-mix(in oklab, var(--panel-2), var(--fg) 14%); color: var(--fg); }
/* ------------------------------------------------- popover de categorias */
#note-meta { position: relative; }
@@ -692,11 +802,28 @@ body.no-gutter #gutter { display: none; }
.menu-btn { padding: 0 9px; }
.menu-dropdown li { padding: 9px 14px; }
#toolbar { overflow-x: auto; scrollbar-width: none; padding: 0 6px; }
#toolbar::-webkit-scrollbar { display: none; }
#toolbar {
height: auto;
min-height: 0;
flex-wrap: wrap;
align-content: flex-start;
row-gap: 6px;
padding: 6px 8px 8px;
overflow: visible;
}
#toolbar .tb-sep, #toolbar .tb-grow { display: none; }
.tb-label, .tb-caption { display: none; }
.btn-icon { padding: 6px 9px; min-height: 32px; }
.segmented .seg { padding: 7px 12px; }
#toolbar .tb-group { display: contents; }
#toolbar #fmt-tools { display: flex; } /* grupo de formatação permanece unido */
/* linha 1: ações de arquivo, botão de versões e seletor de formato */
#btn-new, #btn-save, #btn-export, #btn-versions { min-height: 40px; }
#toolbar select { min-height: 40px; }
/* linha 2: alternador de modo ocupa a largura toda */
#mode-toggle { order: 40; flex: 1 1 100%; }
#mode-toggle .seg { flex: 1; min-height: 38px; font-size: 13px; }
/* linha 3: ferramentas de formatação distribuídas, alvos de toque */
#fmt-tools { order: 50; flex: 1 1 100%; min-width: 0; flex-wrap: wrap; justify-content: space-between; }
#fmt-tools .fmt { flex: 1 1 0; min-width: 0; min-height: 40px; margin: 1px; padding: 4px 2px; }
/* sidebar vira gaveta */
#sidebar {
@@ -746,6 +873,7 @@ body.no-gutter #gutter { display: none; }
/* ---------------------------------------------------- temas / esquemas */
html[data-theme="escuro"] {
color-scheme: dark;
--bg: #232323;
--panel: #1e1e1e;
--panel-2: #262626;
@@ -788,6 +916,7 @@ html[data-theme="escuro"] #notes-list li:hover,
html[data-theme="escuro"] #revisions-list li:hover { background: #303030; }
html[data-theme="solar-claro"] {
color-scheme: light;
--bg: #eee8d5;
--panel: #fdf6e3;
--panel-2: #f4efdc;
@@ -825,6 +954,7 @@ html[data-theme="solar-claro"] {
}
html[data-theme="solar-escuro"] {
color-scheme: dark;
--bg: #073642;
--panel: #002b36;
--panel-2: #0a3d4a;
+1 -1
View File
@@ -1,7 +1,7 @@
/* Service Worker do Notas — caches versionados.
* BUMP de CACHE em todo deploy que mude shell/assets (o activate limpa os antigos).
*/
const CACHE = 'notas-shell-v4';
const CACHE = 'notas-shell-v8';
const SHELL = [
'/',
'/css/app.css',