Files
webmail-client/frontend/src/index.css
T
lukasandClaude Opus 5.5 fba3c947bf Attachment previews, forwarded attachments, row dividers
Attachments in the reading pane are tiles with a preview: images and a PDF's
first page, rendered on the server (Pillow, pypdfium2) as WebP of at most
480 px, cached beside the blob store and in the browser. Tiles are all one
size — at most 230 px wide, narrower so three still fit in a row — and frame
each preview by its shape: close to the tile's it fills it, very tall / wide /
square ones show whole over a blurred copy, small ones are not enlarged, PDFs
show the top of the page. The name bar (checkbox, type, name, size, download)
slides in on hover and adapts to the tile's width; files without a preview
get a large type icon. Previews of opened conversations are kept offline.

Forward now carries the original's attachments: new compose attachments
sharing the stored blobs (POST /messages/{id}/forward-attachments).

Mailbox display / Settings → Appearance: "Show a dividing line between
rows" (off by default, per device), with a new switch. Settings drop the
"On a phone…" note, and "Language" reads "Spellchecking language".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 20:15:17 +02:00

274 lines
6.7 KiB
CSS

@tailwind base;
@tailwind components;
@tailwind utilities;
/* Lato (SIL OFL) — the UI font. Browsers only fetch the weights/styles actually
rendered, so declaring the full family is cheap. */
@font-face {
font-family: "Lato";
src: url("../fonts/Lato-Thin.ttf") format("truetype");
font-weight: 100;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Lato";
src: url("../fonts/Lato-ThinItalic.ttf") format("truetype");
font-weight: 100;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: "Lato";
src: url("../fonts/Lato-Light.ttf") format("truetype");
font-weight: 300;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Lato";
src: url("../fonts/Lato-LightItalic.ttf") format("truetype");
font-weight: 300;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: "Lato";
src: url("../fonts/Lato-Regular.ttf") format("truetype");
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Lato";
src: url("../fonts/Lato-Italic.ttf") format("truetype");
font-weight: 400;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: "Lato";
src: url("../fonts/Lato-Bold.ttf") format("truetype");
font-weight: 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Lato";
src: url("../fonts/Lato-BoldItalic.ttf") format("truetype");
font-weight: 700;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: "Lato";
src: url("../fonts/Lato-Black.ttf") format("truetype");
font-weight: 900;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Lato";
src: url("../fonts/Lato-BlackItalic.ttf") format("truetype");
font-weight: 900;
font-style: italic;
font-display: swap;
}
/* Semantic theme tokens as "R G B" triplets (consumed by tailwind.config.js as
rgb(var(--x) / <alpha>)). Light is the default; .dark on <html> flips them. */
:root {
--app-bg: 243 244 246; /* app chrome behind panels */
--app-surface: 255 255 255; /* panels */
--app-elevated: 255 255 255; /* popovers, composer */
--app-border: 229 231 235;
--app-fg: 24 27 32;
--app-muted: 107 114 128;
--app-faint: 156 163 175;
--app-accent: 37 99 235;
--app-accent-fg: 255 255 255;
--app-hover: 243 244 246;
--app-active: 224 233 254; /* selected row */
color-scheme: light;
}
:root.dark {
--app-bg: 27 30 32; /* main background */
--app-surface: 33 36 39; /* panels (slightly elevated) */
--app-elevated: 39 42 46; /* popovers, composer */
--app-border: 58 62 68; /* dark dividers (brighter) */
--app-fg: 226 228 231;
--app-muted: 150 156 164;
--app-faint: 108 114 122;
--app-accent: 59 130 246;
--app-accent-fg: 255 255 255;
--app-hover: 42 46 50;
--app-active: 44 54 70;
color-scheme: dark;
}
html,
body,
#root {
height: 100%;
margin: 0;
}
body {
font-family: "Lato", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
background-color: rgb(var(--app-bg));
color: rgb(var(--app-fg));
-webkit-font-smoothing: antialiased;
}
/* Rich-text compose editor: quoted replies get depth-colored left borders
(blue → green → amber), matching a Fastmail-style nested quote. */
.wmc-editor {
line-height: 1.55;
}
.wmc-editor:empty::before {
content: "";
}
.wmc-editor a {
color: rgb(var(--app-accent));
text-decoration: underline;
}
.wmc-editor ul {
list-style: disc;
padding-left: 1.4em;
}
.wmc-editor ol {
list-style: decimal;
padding-left: 1.4em;
}
.wmc-editor img {
max-width: 100%;
height: auto;
}
.wmc-editor blockquote {
margin: 0.5em 0;
padding: 0.35em 0.9em;
border-left: 2px solid #5b7fb9;
border-radius: 2px;
background: rgba(120, 140, 170, 0.06);
}
.wmc-editor blockquote blockquote {
border-left-color: #6a9955;
background: rgba(120, 160, 120, 0.06);
}
.wmc-editor blockquote blockquote blockquote {
border-left-color: #c08a4a;
background: rgba(180, 150, 100, 0.06);
}
/* The reply's quoted original folds behind a "•••" pill, as in the reading
pane. Only a quote the pill directly precedes is hidden, so deleting the pill
can never leave a quote hidden with no way back to it. */
.wmc-editor .wmc-quote-toggle {
display: inline-block;
margin: 6px 0;
padding: 1px 9px;
border: none;
border-radius: 8px;
background: rgb(var(--app-hover));
color: rgb(var(--app-muted));
cursor: pointer;
font: inherit;
font-size: 15px;
line-height: 1.5;
user-select: none;
}
.wmc-editor .wmc-quote-toggle:hover {
color: rgb(var(--app-fg));
}
.wmc-editor.wmc-quote-collapsed .wmc-quote-toggle + blockquote {
display: none;
}
/* The text a new link will wrap, kept visible while the URL field has focus
(RichTextEditor registers the range; browsers without the Highlight API
simply show nothing extra). */
::highlight(wmc-link-target) {
background-color: rgb(var(--app-accent) / 0.25);
}
/* Spelling (red) and grammar/style (blue) marks from the compose editor's
LanguageTool check, drawn as CSS highlights over the text — never written
into the message (components/compose/spellcheck.ts). */
::highlight(wmc-spell) {
text-decoration: underline wavy #e5484d;
text-decoration-thickness: 1.5px;
text-underline-offset: 3px;
}
::highlight(wmc-grammar) {
text-decoration: underline wavy rgb(var(--app-accent));
text-decoration-thickness: 1.5px;
text-underline-offset: 3px;
}
/* Thin, unobtrusive scrollbars that match the theme. */
* {
scrollbar-width: thin;
scrollbar-color: rgb(var(--app-border)) transparent;
}
*::-webkit-scrollbar {
width: 10px;
height: 10px;
}
*::-webkit-scrollbar-thumb {
background-color: rgb(var(--app-border));
border-radius: 6px;
border: 2px solid transparent;
background-clip: content-box;
}
/* Offline (components/Offline.tsx): buttons that need the server look
unavailable, and their clicks are stopped with a "Not available offline". */
html[data-offline] .wm-online-only {
opacity: 0.4;
cursor: not-allowed;
}
html[data-offline] .wm-online-only:hover {
background-color: transparent;
}
.wm-offline-notice {
animation: wm-offline-in 180ms ease-out;
}
@keyframes wm-offline-in {
from {
opacity: 0;
transform: translate(-50%, 8px);
}
to {
opacity: 1;
transform: translate(-50%, 0);
}
}
/* Attachment tiles (thread/Attachments.tsx) shrink with the reading column so
three fit in a row; their name bar adapts to the tile's own width. */
.wm-tile {
container-type: inline-size;
}
@container (max-width: 160px) {
.wm-tile-icon,
.wm-tile-size {
display: none;
}
.wm-tile-bar {
height: 34px;
gap: 6px;
padding-inline: 6px;
}
}
@container (max-width: 80px) {
/* Too small for a name: the picture (or icon) alone; a click still opens it. */
.wm-tile-bar {
display: none;
}
.wm-tile-art {
bottom: 0;
}
}