mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 08:13:50 +00:00
1191 lines
31 KiB
CSS
1191 lines
31 KiB
CSS
@import "tailwindcss";
|
|
@import "tw-animate-css";
|
|
|
|
@import "./theme-inline.css";
|
|
|
|
/* The app's font stack replaces Tailwind's default --font-sans, so the
|
|
font-sans utility and body use the same stack, and a theme's
|
|
typography.fontSans (compiled to --font-sans on :root) restyles both. */
|
|
@theme {
|
|
--font-sans:
|
|
-apple-system,
|
|
BlinkMacSystemFont,
|
|
"Segoe UI",
|
|
Roboto,
|
|
"Helvetica Neue",
|
|
Arial,
|
|
"Noto Sans Thai",
|
|
"Leelawadee UI",
|
|
Tahoma,
|
|
sans-serif;
|
|
}
|
|
|
|
:root {
|
|
font-size: var(--font-size-base);
|
|
|
|
--color-border: #e2e8f0;
|
|
--color-input: #e2e8f0;
|
|
--color-ring: #94a3b8;
|
|
--color-background: #ffffff;
|
|
--color-foreground: #0f172a;
|
|
--color-primary: #3b82f6;
|
|
--color-primary-foreground: #ffffff;
|
|
--color-secondary: #f8fafc;
|
|
--color-secondary-foreground: #0f172a;
|
|
--color-muted: #f1f5f9;
|
|
--color-muted-foreground: #64748b;
|
|
--color-accent: #dbeafe;
|
|
--color-accent-foreground: #1e40af;
|
|
--color-destructive: #ef4444;
|
|
--color-destructive-foreground: #ffffff;
|
|
--color-popover: #ffffff;
|
|
--color-popover-foreground: #0f172a;
|
|
|
|
/* Sidebar */
|
|
--color-sidebar: #f8fafc;
|
|
--color-sidebar-foreground: #0f172a;
|
|
--color-sidebar-border: #e2e8f0;
|
|
--color-sidebar-accent: #f1f5f9;
|
|
--color-sidebar-accent-foreground: #0f172a;
|
|
|
|
/* Card */
|
|
--color-card: #ffffff;
|
|
--color-card-foreground: #0f172a;
|
|
|
|
/* Semantic status colors */
|
|
--color-success: #22c55e;
|
|
--color-success-foreground: #ffffff;
|
|
--color-warning: #eab308;
|
|
--color-warning-foreground: #ffffff;
|
|
--color-info: #3b82f6;
|
|
--color-info-foreground: #ffffff;
|
|
|
|
/* Selection states */
|
|
--color-selection: #dbeafe;
|
|
--color-selection-foreground: #1e40af;
|
|
|
|
/* Unread indicator */
|
|
--color-unread: #3b82f6;
|
|
|
|
/* Chart colors */
|
|
--color-chart-1: #3b82f6;
|
|
--color-chart-2: #22c55e;
|
|
--color-chart-3: #f59e0b;
|
|
--color-chart-4: #ef4444;
|
|
--color-chart-5: #8b5cf6;
|
|
|
|
/* Settings variables */
|
|
--font-size-base: 16px;
|
|
--list-item-height: 48px;
|
|
--transition-duration: 0.2s;
|
|
|
|
/* Density spacing variables (defaults = regular) */
|
|
--density-item-py: 12px;
|
|
--density-item-gap: 12px;
|
|
--density-header-py: 12px;
|
|
--density-card-p: 16px;
|
|
--density-sidebar-py: 4px;
|
|
|
|
/* Minimum height of a tappable list row below the lg breakpoint, where
|
|
* folder rows use max-lg:min-h-[44px]. Plugin frames get it as a variable
|
|
* because their own viewport is the frame, not the page. */
|
|
--touch-row-min-h: 0px;
|
|
}
|
|
|
|
@media (width < 64rem) {
|
|
:root {
|
|
--touch-row-min-h: 44px;
|
|
}
|
|
}
|
|
|
|
.dark {
|
|
--color-border: rgba(128, 128, 128, 0.3);
|
|
--color-input: #262626;
|
|
--color-ring: #d4d4d4;
|
|
--color-background: #0a0a0a;
|
|
--color-foreground: #fafafa;
|
|
--color-primary: #fafafa;
|
|
--color-primary-foreground: #171717;
|
|
--color-secondary: #262626;
|
|
--color-secondary-foreground: #fafafa;
|
|
--color-muted: #262626;
|
|
--color-muted-foreground: #a3a3a3;
|
|
--color-accent: #1e3a8a;
|
|
--color-accent-foreground: #dbeafe;
|
|
--color-destructive: #ef4444;
|
|
--color-destructive-foreground: #fafafa;
|
|
--color-popover: #1c1c1c;
|
|
--color-popover-foreground: #fafafa;
|
|
|
|
/* Sidebar */
|
|
--color-sidebar: #0a0a0a;
|
|
--color-sidebar-foreground: #fafafa;
|
|
--color-sidebar-border: #262626;
|
|
--color-sidebar-accent: #1a1a1a;
|
|
--color-sidebar-accent-foreground: #fafafa;
|
|
|
|
/* Card */
|
|
--color-card: #141414;
|
|
--color-card-foreground: #fafafa;
|
|
|
|
/* Semantic status colors */
|
|
--color-success: #16a34a;
|
|
--color-success-foreground: #ffffff;
|
|
--color-warning: #ca8a04;
|
|
--color-warning-foreground: #ffffff;
|
|
--color-info: #60a5fa;
|
|
--color-info-foreground: #ffffff;
|
|
|
|
/* Selection states */
|
|
--color-selection: rgba(59, 130, 246, 0.25);
|
|
--color-selection-foreground: #93c5fd;
|
|
|
|
/* Unread indicator */
|
|
--color-unread: #60a5fa;
|
|
|
|
/* Chart colors */
|
|
--color-chart-1: #60a5fa;
|
|
--color-chart-2: #4ade80;
|
|
--color-chart-3: #fbbf24;
|
|
--color-chart-4: #f87171;
|
|
--color-chart-5: #a78bfa;
|
|
}
|
|
|
|
|
|
* {
|
|
border-color: var(--color-border);
|
|
}
|
|
|
|
body {
|
|
background-color: var(--color-background);
|
|
color: var(--color-foreground);
|
|
font-family: var(--font-sans);
|
|
font-feature-settings:
|
|
"rlig" 1,
|
|
"calt" 1;
|
|
}
|
|
|
|
/* Native <select> option lists are painted by the browser, not by the
|
|
Tailwind classes on the control, so they only honour color-scheme and an
|
|
explicit option colour. Themes whose --color-muted is translucent (Aurora
|
|
Glass dark: rgba(255,255,255,0.05)) composited to a light popup with
|
|
near-white text (#999). Pin options to the popover tokens, which every
|
|
theme defines as an opaque surface, and let dark themes ask the browser
|
|
for a dark popup where option colours are ignored (Safari). */
|
|
option {
|
|
background-color: var(--color-popover);
|
|
color: var(--color-popover-foreground);
|
|
}
|
|
|
|
/* The group labels (the From dropdown's accounts) are painted the same way.
|
|
Without colours of their own they kept the control's text colour on the
|
|
popup's base, which a transparent control leaves light: in dark themes,
|
|
light text on a white bar. */
|
|
optgroup {
|
|
background-color: var(--color-popover);
|
|
color: var(--color-popover-foreground);
|
|
}
|
|
|
|
.dark select {
|
|
color-scheme: dark;
|
|
}
|
|
|
|
/* Tailwind v4's preflight no longer gives buttons a pointer cursor, so most
|
|
controls showed the arrow. Give everything clickable the pointer again, and
|
|
disabled controls not-allowed, as the components that style it themselves
|
|
already do. This sits in the base layer so a cursor utility on a component
|
|
(cursor-grab, cursor-text, disabled:cursor-default) still wins. */
|
|
@layer base {
|
|
button:not(:disabled),
|
|
select:not(:disabled),
|
|
input:is([type="checkbox"], [type="radio"], [type="color"]):not(:disabled),
|
|
summary,
|
|
[role="button"]:not([aria-disabled="true"]),
|
|
[role="tab"]:not([aria-disabled="true"]),
|
|
[role="switch"]:not([aria-disabled="true"]),
|
|
[role="checkbox"]:not([aria-disabled="true"]),
|
|
[role="radio"]:not([aria-disabled="true"]),
|
|
[role="option"]:not([aria-disabled="true"]),
|
|
[role="menuitem"]:not([aria-disabled="true"]),
|
|
[role="menuitemcheckbox"]:not([aria-disabled="true"]),
|
|
[role="menuitemradio"]:not([aria-disabled="true"]) {
|
|
cursor: pointer;
|
|
}
|
|
|
|
button:disabled,
|
|
select:disabled,
|
|
input:is([type="checkbox"], [type="radio"], [type="color"]):disabled,
|
|
[role="button"][aria-disabled="true"],
|
|
[role="tab"][aria-disabled="true"],
|
|
[role="switch"][aria-disabled="true"],
|
|
[role="checkbox"][aria-disabled="true"],
|
|
[role="radio"][aria-disabled="true"],
|
|
[role="option"][aria-disabled="true"],
|
|
[role="menuitem"][aria-disabled="true"],
|
|
[role="menuitemcheckbox"][aria-disabled="true"],
|
|
[role="menuitemradio"][aria-disabled="true"] {
|
|
cursor: not-allowed;
|
|
}
|
|
}
|
|
|
|
/* Minimalist scrollbar */
|
|
::-webkit-scrollbar {
|
|
width: 8px;
|
|
height: 8px;
|
|
}
|
|
|
|
::-webkit-scrollbar-track {
|
|
background-color: transparent;
|
|
}
|
|
|
|
::-webkit-scrollbar-thumb {
|
|
background-color: var(--color-border);
|
|
border-radius: 9999px;
|
|
}
|
|
|
|
::-webkit-scrollbar-thumb:hover {
|
|
background-color: var(--color-muted-foreground);
|
|
}
|
|
|
|
/* Enhanced Email Content Styling */
|
|
|
|
.email-content-wrapper {
|
|
min-width: 100%;
|
|
}
|
|
|
|
/* Forces light-theme CSS variables inside the email content area, so when
|
|
"Always Show Emails in Light Mode" is enabled in dark theme the surrounding
|
|
sender info / attachments / plain-text body don't end up with light text
|
|
on a white background. */
|
|
.email-content-light {
|
|
--color-background: #ffffff;
|
|
--color-foreground: #0f172a;
|
|
--color-muted: #f1f5f9;
|
|
--color-muted-foreground: #64748b;
|
|
--color-border: #e2e8f0;
|
|
--color-card: #ffffff;
|
|
--color-card-foreground: #0f172a;
|
|
--color-popover: #ffffff;
|
|
--color-popover-foreground: #0f172a;
|
|
--color-secondary: #f8fafc;
|
|
--color-secondary-foreground: #0f172a;
|
|
--color-accent: #dbeafe;
|
|
--color-accent-foreground: #1e40af;
|
|
--color-input: #e2e8f0;
|
|
}
|
|
|
|
.email-content-light .email-content-text a {
|
|
color: #2563eb;
|
|
}
|
|
|
|
.email-content-text {
|
|
padding: 1rem 1.25rem;
|
|
}
|
|
|
|
@media (max-width: 640px) {
|
|
.email-content-text {
|
|
padding-left: 0.75rem;
|
|
padding-right: 0.75rem;
|
|
}
|
|
}
|
|
|
|
.email-content-text a {
|
|
color: #2563eb;
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.dark .email-content-text a {
|
|
color: #60a5fa;
|
|
}
|
|
|
|
.email-content {
|
|
font-family:
|
|
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
|
|
Arial, sans-serif;
|
|
font-size: 0.9375rem;
|
|
line-height: 1.6;
|
|
color: var(--color-foreground);
|
|
max-width: none;
|
|
overflow-wrap: break-word;
|
|
word-wrap: break-word;
|
|
padding: 1rem 1.25rem;
|
|
}
|
|
|
|
/* Remove padding for rich HTML emails with table-based layouts */
|
|
.email-content:has(> table) {
|
|
padding: 0;
|
|
}
|
|
|
|
.email-content p {
|
|
margin: 0.75rem 0;
|
|
}
|
|
|
|
.email-content a {
|
|
color: var(--color-primary);
|
|
text-decoration: none;
|
|
transition: color 0.2s;
|
|
}
|
|
|
|
.email-content a:hover {
|
|
text-decoration: underline;
|
|
color: var(--color-accent-foreground);
|
|
}
|
|
|
|
.email-content img {
|
|
max-width: 100%;
|
|
height: auto;
|
|
border-radius: 0.375rem;
|
|
margin: 1rem 0;
|
|
display: block;
|
|
}
|
|
|
|
.email-content blockquote {
|
|
border-left: 3px solid var(--color-border);
|
|
padding-left: 1rem;
|
|
margin: 1rem 0;
|
|
color: var(--color-muted-foreground);
|
|
font-style: italic;
|
|
}
|
|
|
|
.email-content pre {
|
|
background-color: var(--color-muted);
|
|
border: 1px solid var(--color-border);
|
|
border-radius: 0.375rem;
|
|
padding: 1rem;
|
|
font-family: "SF Mono", Monaco, Menlo, Consolas, monospace;
|
|
font-size: 0.875rem;
|
|
overflow-x: auto;
|
|
margin: 1rem 0;
|
|
}
|
|
|
|
.email-content code {
|
|
background-color: var(--color-muted);
|
|
padding: 0.125rem 0.375rem;
|
|
border-radius: 0.25rem;
|
|
font-family: "SF Mono", Monaco, Menlo, Consolas, monospace;
|
|
font-size: 0.875rem;
|
|
color: var(--color-destructive);
|
|
}
|
|
|
|
.email-content h1,
|
|
.email-content h2,
|
|
.email-content h3,
|
|
.email-content h4 {
|
|
margin-top: 1.5rem;
|
|
margin-bottom: 0.75rem;
|
|
font-weight: 600;
|
|
line-height: 1.25;
|
|
color: var(--color-foreground);
|
|
}
|
|
|
|
.email-content h1 {
|
|
font-size: 1.5rem;
|
|
}
|
|
.email-content h2 {
|
|
font-size: 1.25rem;
|
|
}
|
|
.email-content h3 {
|
|
font-size: 1.125rem;
|
|
}
|
|
.email-content h4 {
|
|
font-size: 1rem;
|
|
}
|
|
|
|
.email-content ul,
|
|
.email-content ol {
|
|
margin: 1rem 0;
|
|
padding-left: 1.75rem;
|
|
}
|
|
|
|
.email-content li {
|
|
margin: 0.375rem 0;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
.email-content ul li {
|
|
list-style-type: disc;
|
|
}
|
|
|
|
.email-content ol li {
|
|
list-style-type: decimal;
|
|
}
|
|
|
|
/* Reply quoted HTML - preserves original inline styles/colors */
|
|
.email-reply-quote {
|
|
overflow-wrap: break-word;
|
|
word-wrap: break-word;
|
|
max-width: none;
|
|
}
|
|
|
|
.email-reply-quote p {
|
|
margin: 0.5rem 0;
|
|
}
|
|
|
|
.email-reply-quote img {
|
|
max-width: 100%;
|
|
height: auto;
|
|
}
|
|
|
|
.email-reply-quote a {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
/* Only style tables that are actual data tables, not layout tables */
|
|
.email-content table.data-table,
|
|
.email-content table[border="1"] {
|
|
border-collapse: collapse;
|
|
width: 100%;
|
|
margin: 1rem 0;
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
.email-content table.data-table th,
|
|
.email-content table.data-table td,
|
|
.email-content table[border="1"] th,
|
|
.email-content table[border="1"] td {
|
|
padding: 0.625rem;
|
|
border: 1px solid var(--color-border);
|
|
text-align: left;
|
|
}
|
|
|
|
.email-content table.data-table th,
|
|
.email-content table[border="1"] th {
|
|
background-color: var(--color-muted);
|
|
font-weight: 600;
|
|
color: var(--color-foreground);
|
|
}
|
|
|
|
/* Reset styles for layout tables (commonly used in HTML emails) */
|
|
.email-content table {
|
|
border-collapse: collapse;
|
|
border-spacing: 0;
|
|
}
|
|
|
|
/* Let HTML email's own styles take precedence */
|
|
.email-content table:not(.data-table):not([border="1"]) {
|
|
border: initial;
|
|
}
|
|
|
|
.email-content table:not(.data-table):not([border="1"]) td,
|
|
.email-content table:not(.data-table):not([border="1"]) th {
|
|
border: initial;
|
|
padding: initial;
|
|
}
|
|
|
|
.email-content hr {
|
|
border: none;
|
|
border-top: 1px solid var(--color-border);
|
|
margin: 1.5rem 0;
|
|
}
|
|
|
|
/* Email thread quoted text */
|
|
.email-content .quoted-text {
|
|
border-left: 3px solid var(--color-border);
|
|
padding-left: 1rem;
|
|
margin: 1rem 0;
|
|
color: var(--color-muted-foreground);
|
|
opacity: 0.8;
|
|
}
|
|
|
|
/* Toast animations */
|
|
@keyframes toast-enter {
|
|
from {
|
|
transform: translateX(calc(100% + 1.25rem));
|
|
opacity: 0;
|
|
}
|
|
to {
|
|
transform: translateX(0);
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
@keyframes toast-exit {
|
|
from {
|
|
transform: translateX(0);
|
|
opacity: 1;
|
|
}
|
|
to {
|
|
transform: translateX(calc(100% + 1.25rem));
|
|
opacity: 0;
|
|
}
|
|
}
|
|
|
|
@keyframes toast-progress {
|
|
from {
|
|
width: 100%;
|
|
}
|
|
to {
|
|
width: 0%;
|
|
}
|
|
}
|
|
|
|
.toast-enter {
|
|
animation: toast-enter 0.32s cubic-bezier(0.21, 1.02, 0.73, 1) forwards;
|
|
}
|
|
|
|
.toast-exit {
|
|
animation: toast-exit 0.28s cubic-bezier(0.06, 0.71, 0.55, 1) forwards;
|
|
}
|
|
|
|
/* Mobile Responsive Utilities */
|
|
|
|
/* Safe area insets for notched devices (iPhone X+, etc.) */
|
|
.safe-area-inset-top {
|
|
padding-top: env(safe-area-inset-top);
|
|
}
|
|
|
|
.safe-area-inset-bottom {
|
|
padding-bottom: env(safe-area-inset-bottom);
|
|
}
|
|
|
|
.safe-area-inset-left {
|
|
padding-left: env(safe-area-inset-left);
|
|
}
|
|
|
|
.safe-area-inset-right {
|
|
padding-right: env(safe-area-inset-right);
|
|
}
|
|
|
|
/* Minimum touch targets (44x44px recommended by Apple HIG) */
|
|
.touch-target {
|
|
min-height: 44px;
|
|
min-width: 44px;
|
|
}
|
|
|
|
/* Prevent text selection on mobile for UI elements */
|
|
.no-select {
|
|
-webkit-user-select: none;
|
|
user-select: none;
|
|
-webkit-touch-callout: none;
|
|
}
|
|
|
|
/* Smooth transitions for view switching */
|
|
.view-transition {
|
|
transition:
|
|
transform 0.3s ease-in-out,
|
|
opacity 0.3s ease-in-out;
|
|
}
|
|
|
|
/* Hide scrollbar on mobile while keeping functionality */
|
|
@media (max-width: 767px) {
|
|
.mobile-scroll-hidden::-webkit-scrollbar {
|
|
display: none;
|
|
}
|
|
|
|
.mobile-scroll-hidden {
|
|
-ms-overflow-style: none;
|
|
scrollbar-width: none;
|
|
}
|
|
}
|
|
|
|
.scroll-hidden::-webkit-scrollbar {
|
|
display: none;
|
|
}
|
|
|
|
.scroll-hidden {
|
|
-ms-overflow-style: none;
|
|
scrollbar-width: none;
|
|
}
|
|
|
|
/* Mobile backdrop blur support */
|
|
@supports (backdrop-filter: blur(8px)) {
|
|
.mobile-backdrop {
|
|
backdrop-filter: blur(8px);
|
|
-webkit-backdrop-filter: blur(8px);
|
|
}
|
|
}
|
|
|
|
/* Prevent overscroll bounce on iOS */
|
|
.no-overscroll {
|
|
overscroll-behavior: none;
|
|
}
|
|
|
|
/* iOS Safari zooms the viewport whenever a focused field computes below 16px,
|
|
* and leaves the page zoomed and panned once the keyboard closes (#838). Every
|
|
* field here is text-sm (0.875rem), so no --font-size-base setting escapes it:
|
|
* small 12.25px, medium 14px, large 15.75px. The viewport meta deliberately
|
|
* omits maximum-scale so pinch zoom keeps working, which leaves CSS as the
|
|
* only place to put the floor.
|
|
*
|
|
* max() raises the small fields to 16px while letting the "Large" setting go
|
|
* past it (18px) rather than pinning every field to the threshold, and
|
|
* pointer: coarse keeps desktop typography untouched. Checkbox and radio are
|
|
* excluded because their box is font-relative in Safari; the :not() chain also
|
|
* lifts specificity above .text-sm, so no !important is needed.
|
|
*
|
|
* Quoted replies are exempt by construction: they render inside a shadow root
|
|
* (components/email/quoted-html.ts) so they match the sent mail 1:1, and page
|
|
* CSS cannot reach in. */
|
|
@media (pointer: coarse) {
|
|
input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
|
|
textarea,
|
|
select,
|
|
[contenteditable="true"] {
|
|
font-size: max(16px, 1rem);
|
|
}
|
|
}
|
|
|
|
/* Shake animation (for rejected input) */
|
|
@keyframes shake {
|
|
0%,
|
|
100% {
|
|
transform: translateX(0);
|
|
}
|
|
20%,
|
|
60% {
|
|
transform: translateX(-4px);
|
|
}
|
|
40%,
|
|
80% {
|
|
transform: translateX(4px);
|
|
}
|
|
}
|
|
|
|
.animate-shake {
|
|
animation: shake 0.4s ease-in-out;
|
|
}
|
|
|
|
/* Fade in animation (for popovers) */
|
|
@keyframes fade-in {
|
|
from {
|
|
opacity: 0;
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
.animate-fade-in {
|
|
animation: fade-in 0.2s ease-out;
|
|
}
|
|
|
|
/* Slide in from right animation (for mobile views) */
|
|
@keyframes slide-in-from-right {
|
|
from {
|
|
transform: translateX(100%);
|
|
}
|
|
to {
|
|
transform: translateX(0);
|
|
}
|
|
}
|
|
|
|
.animate-slide-in-from-right {
|
|
animation: slide-in-from-right 0.3s ease-out;
|
|
}
|
|
|
|
/* Slide in from left animation (for sidebar) */
|
|
@keyframes slide-in-from-left {
|
|
from {
|
|
transform: translateX(-100%);
|
|
}
|
|
to {
|
|
transform: translateX(0);
|
|
}
|
|
}
|
|
|
|
.animate-slide-in-from-left {
|
|
animation: slide-in-from-left 0.3s ease-out;
|
|
}
|
|
|
|
/* Calendar swipe animations (subtler slide for view transitions) */
|
|
@keyframes cal-slide-in-right {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateX(30%);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateX(0);
|
|
}
|
|
}
|
|
|
|
@keyframes cal-slide-in-left {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateX(-30%);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateX(0);
|
|
}
|
|
}
|
|
|
|
.animate-slide-in-right {
|
|
animation: cal-slide-in-right 0.25s ease-out;
|
|
}
|
|
|
|
.animate-slide-in-left {
|
|
animation: cal-slide-in-left 0.25s ease-out;
|
|
}
|
|
|
|
/* Reduced motion: respect user OS preference */
|
|
@media (prefers-reduced-motion: reduce) {
|
|
*,
|
|
*::before,
|
|
*::after {
|
|
animation-duration: 0.01ms !important;
|
|
animation-iteration-count: 1 !important;
|
|
transition-duration: 0.01ms !important;
|
|
scroll-behavior: auto !important;
|
|
}
|
|
}
|
|
|
|
/* The same when the user turns animations off in the settings (#1215). */
|
|
[data-animations="off"] *,
|
|
[data-animations="off"] *::before,
|
|
[data-animations="off"] *::after {
|
|
animation-duration: 0.01ms !important;
|
|
animation-iteration-count: 1 !important;
|
|
transition-duration: 0.01ms !important;
|
|
scroll-behavior: auto !important;
|
|
}
|
|
|
|
/* Disable backdrop-filter on mobile for performance */
|
|
@media (max-width: 767px) {
|
|
.mobile-backdrop {
|
|
backdrop-filter: none !important;
|
|
-webkit-backdrop-filter: none !important;
|
|
}
|
|
}
|
|
|
|
/* TipTap Rich Text Editor */
|
|
.tiptap {
|
|
outline: none;
|
|
}
|
|
|
|
.tiptap p {
|
|
margin: 0.25rem 0;
|
|
}
|
|
|
|
.tiptap h1 {
|
|
font-size: 1.5rem;
|
|
font-weight: 700;
|
|
margin: 0.5rem 0;
|
|
}
|
|
|
|
.tiptap h2 {
|
|
font-size: 1.25rem;
|
|
font-weight: 600;
|
|
margin: 0.5rem 0;
|
|
}
|
|
|
|
.tiptap ul {
|
|
list-style-type: disc;
|
|
padding-inline-start: 1.5rem;
|
|
margin: 0.25rem 0;
|
|
}
|
|
|
|
.tiptap ol {
|
|
list-style-type: decimal;
|
|
padding-inline-start: 1.5rem;
|
|
margin: 0.25rem 0;
|
|
}
|
|
|
|
.tiptap li {
|
|
margin: 0.125rem 0;
|
|
}
|
|
|
|
.tiptap blockquote {
|
|
border-inline-start: 3px solid var(--color-border);
|
|
padding-inline-start: 1rem;
|
|
margin: 0.5rem 0;
|
|
color: var(--color-muted-foreground);
|
|
}
|
|
|
|
.tiptap pre {
|
|
background-color: var(--color-muted);
|
|
border: 1px solid var(--color-border);
|
|
border-radius: 0.375rem;
|
|
padding: 0.75rem;
|
|
font-family: monospace;
|
|
font-size: 0.875rem;
|
|
overflow-x: auto;
|
|
margin: 0.5rem 0;
|
|
}
|
|
|
|
.tiptap code {
|
|
background-color: var(--color-muted);
|
|
padding: 0.125rem 0.25rem;
|
|
border-radius: 0.25rem;
|
|
font-family: monospace;
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
.tiptap a {
|
|
color: var(--color-primary);
|
|
text-decoration: underline;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.tiptap img {
|
|
max-width: 100%;
|
|
height: auto;
|
|
}
|
|
|
|
.tiptap hr {
|
|
border: none;
|
|
border-top: 1px solid var(--color-border);
|
|
margin: 1rem 0;
|
|
}
|
|
|
|
.tiptap p.is-editor-empty:first-child::before {
|
|
content: attr(data-placeholder);
|
|
float: inline-start;
|
|
color: var(--color-muted-foreground);
|
|
pointer-events: none;
|
|
height: 0;
|
|
}
|
|
|
|
.tiptap .ProseMirror-selectednode img {
|
|
outline: none;
|
|
}
|
|
|
|
.tiptap table {
|
|
border-collapse: collapse;
|
|
margin: 0.5rem 0;
|
|
table-layout: fixed;
|
|
width: 100%;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.tiptap table td,
|
|
.tiptap table th {
|
|
border: 1px solid var(--color-border);
|
|
padding: 0.375rem 0.5rem;
|
|
vertical-align: top;
|
|
position: relative;
|
|
min-width: 1em;
|
|
}
|
|
|
|
.tiptap table th {
|
|
background-color: var(--color-muted) !important;
|
|
color: var(--color-foreground) !important;
|
|
font-weight: 600;
|
|
text-align: left;
|
|
}
|
|
|
|
.tiptap table p {
|
|
margin: 0;
|
|
}
|
|
|
|
.tiptap table .selectedCell::after {
|
|
background: rgba(99, 102, 241, 0.15);
|
|
content: "";
|
|
inset: 0;
|
|
pointer-events: none;
|
|
position: absolute;
|
|
z-index: 2;
|
|
}
|
|
|
|
.tiptap table .column-resize-handle {
|
|
background-color: var(--color-primary);
|
|
bottom: -2px;
|
|
pointer-events: none;
|
|
position: absolute;
|
|
right: -2px;
|
|
top: 0;
|
|
width: 4px;
|
|
}
|
|
|
|
.tiptap.resize-cursor {
|
|
cursor: col-resize;
|
|
}
|
|
|
|
/* Smooth halo applied to a setting row when the user clicks a sub-result in
|
|
the settings search. The element bg + a 10px box-shadow fill the row plus
|
|
a 10px ring of the same tint, then a 2px outline sits exactly at the outer
|
|
edge. Uses the primary theme color so it adapts to light/dark. */
|
|
@keyframes settings-search-pulse {
|
|
0% {
|
|
background-color: color-mix(in srgb, var(--color-primary) 0%, transparent);
|
|
box-shadow: 0 0 0 0 color-mix(in srgb, var(--color-primary) 0%, transparent);
|
|
outline-color: color-mix(in srgb, var(--color-primary) 0%, transparent);
|
|
}
|
|
30% {
|
|
background-color: color-mix(in srgb, var(--color-primary) 12%, transparent);
|
|
box-shadow: 0 0 0 10px color-mix(in srgb, var(--color-primary) 12%, transparent);
|
|
outline-color: color-mix(in srgb, var(--color-primary) 60%, transparent);
|
|
}
|
|
100% {
|
|
background-color: color-mix(in srgb, var(--color-primary) 0%, transparent);
|
|
box-shadow: 0 0 0 10px color-mix(in srgb, var(--color-primary) 0%, transparent);
|
|
outline-color: color-mix(in srgb, var(--color-primary) 0%, transparent);
|
|
}
|
|
}
|
|
|
|
.settings-search-highlight {
|
|
outline: 2px solid transparent;
|
|
outline-offset: 10px;
|
|
border-radius: 8px;
|
|
animation: settings-search-pulse 1.6s ease-in-out forwards;
|
|
}
|
|
|
|
/* RTL: mirror directional icons (chevrons/arrows) so prev/next, back/forward,
|
|
and panel-collapse affordances point the correct way in right-to-left layouts.
|
|
Tabler emits a `tabler-icon-<name>` class per icon, so we target the
|
|
directional ones only — vertical chevrons (up/down) are intentionally left. */
|
|
[dir="rtl"] .tabler-icon-chevron-left,
|
|
[dir="rtl"] .tabler-icon-chevron-right,
|
|
[dir="rtl"] .tabler-icon-chevrons-left,
|
|
[dir="rtl"] .tabler-icon-chevrons-right,
|
|
[dir="rtl"] .tabler-icon-arrow-left,
|
|
[dir="rtl"] .tabler-icon-arrow-right,
|
|
[dir="rtl"] .tabler-icon-arrow-big-left,
|
|
[dir="rtl"] .tabler-icon-arrow-big-right,
|
|
[dir="rtl"] .tabler-icon-layout-sidebar-left,
|
|
[dir="rtl"] .tabler-icon-layout-sidebar-right {
|
|
transform: scaleX(-1);
|
|
}
|
|
|
|
/* ==========================================================================
|
|
Interface layouts
|
|
--------------------------------------------------------------------------
|
|
`data-interface-layout` is set on <html> by the settings store
|
|
(`applyInterfaceLayout`). The default layout declares no overrides, so
|
|
everything below is additive and switched off by a single attribute.
|
|
Mountain View sets its own geometry - bar heights, rail widths, pill shapes,
|
|
row rhythm - while colour, iconography and type keep coming from the active
|
|
theme.
|
|
========================================================================== */
|
|
|
|
[data-interface-layout="mountain-view"] {
|
|
--mv-topbar-h: 64px;
|
|
--mv-rail-w: 72px;
|
|
--mv-rail-peek-w: 256px;
|
|
--mv-nav-row-h: 32px;
|
|
--mv-nav-radius: 9999px;
|
|
--mv-compose-h: 56px;
|
|
--mv-search-h: 48px;
|
|
--mv-search-radius: 8px;
|
|
--mv-popover-w: 352px;
|
|
--mv-popover-radius: 28px;
|
|
--mv-card-radius: 16px;
|
|
/* Surfaces, tinted from the theme's primary so any theme keeps the same
|
|
relationships: a faintly tinted canvas behind the chrome, a plain card
|
|
for the content, a slightly deeper search field, a selected-row pill and
|
|
a soft Compose button. */
|
|
--mv-canvas: color-mix(in oklab, var(--color-primary) 4%, var(--color-background));
|
|
--mv-search-bg: color-mix(in oklab, var(--color-primary) 9%, var(--color-background));
|
|
--mv-nav-hover: color-mix(in oklab, var(--color-foreground) 7%, var(--mv-canvas));
|
|
--mv-nav-selected: color-mix(in oklab, var(--color-primary) 18%, var(--color-background));
|
|
--mv-compose-bg: color-mix(in oklab, var(--color-primary) 24%, var(--color-background));
|
|
--mv-compose-fg: color-mix(in oklab, var(--color-primary) 30%, var(--color-foreground));
|
|
--mv-row-read: color-mix(in oklab, var(--color-primary) 3%, var(--color-background));
|
|
}
|
|
|
|
/* The content card: everything that is not chrome sits on one rounded sheet
|
|
inset from the canvas. */
|
|
[data-interface-layout="mountain-view"] [data-mv-card] {
|
|
border-radius: var(--mv-card-radius);
|
|
background-color: var(--color-background);
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* Search: a filled 48px field that lifts onto a card while focused, rather
|
|
than the outlined input the default layout uses. */
|
|
[data-interface-layout="mountain-view"] .mv-topbar input[data-search-input] {
|
|
height: var(--mv-search-h);
|
|
border-radius: var(--mv-search-radius);
|
|
border-color: transparent;
|
|
background-color: var(--mv-search-bg);
|
|
padding-inline-start: 3rem;
|
|
font-size: 1rem;
|
|
}
|
|
|
|
[data-interface-layout="mountain-view"] .mv-topbar input[data-search-input]:hover {
|
|
border-color: transparent;
|
|
background-color: var(--mv-search-bg);
|
|
box-shadow: 0 1px 1px 0 rgb(0 0 0 / 0.08), 0 1px 3px 1px rgb(0 0 0 / 0.04);
|
|
}
|
|
|
|
[data-interface-layout="mountain-view"] .mv-topbar input[data-search-input]:focus-visible {
|
|
background-color: var(--color-background);
|
|
border-color: transparent;
|
|
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.16), 0 4px 8px 3px rgb(0 0 0 / 0.08);
|
|
}
|
|
|
|
/* The filter button sits inside the field's trailing end: keep typed text and
|
|
the clear button clear of it. */
|
|
[data-interface-layout="mountain-view"] .mv-topbar input[data-search-input] {
|
|
padding-inline-end: 5.5rem;
|
|
}
|
|
|
|
[data-interface-layout="mountain-view"] .mv-topbar-search > div > form > button {
|
|
inset-inline-end: 3.25rem;
|
|
}
|
|
|
|
/* The magnifier sits on the field's baseline grid, one 48px hit area in. */
|
|
[data-interface-layout="mountain-view"] .mv-topbar-search > div > form > svg {
|
|
inset-inline-start: 1rem;
|
|
width: 1.25rem;
|
|
height: 1.25rem;
|
|
}
|
|
|
|
/* Folder rows: 32px pills flush with the rail's leading edge and rounded only
|
|
on the trailing side, so the selected label reads as one continuous shape
|
|
with the rail. The default layout's leading accent stripe goes away with it. */
|
|
[data-interface-layout="mountain-view"] [data-mv-nav-row] {
|
|
min-height: var(--mv-nav-row-h);
|
|
margin-inline-end: 12px;
|
|
padding-inline-end: 16px;
|
|
border-inline-start-width: 0;
|
|
border-start-start-radius: 0;
|
|
border-end-start-radius: 0;
|
|
border-start-end-radius: var(--mv-nav-radius);
|
|
border-end-end-radius: var(--mv-nav-radius);
|
|
}
|
|
|
|
/* Selected and hovered rows fill the pill; a folder with unread mail reads
|
|
bold, the way the message rows do. Icons are 20px with an 18px gap. */
|
|
[data-interface-layout="mountain-view"] [data-mv-nav-row] {
|
|
font-size: 0.875rem;
|
|
color: var(--color-foreground);
|
|
}
|
|
[data-interface-layout="mountain-view"] [data-mv-nav-row]:hover {
|
|
background-color: var(--mv-nav-hover);
|
|
}
|
|
[data-interface-layout="mountain-view"] [data-mv-nav-row][data-selected="true"] {
|
|
background-color: var(--mv-nav-selected);
|
|
color: var(--color-foreground);
|
|
font-weight: 700;
|
|
}
|
|
[data-interface-layout="mountain-view"] [data-mv-nav-row][data-has-unread] {
|
|
font-weight: 700;
|
|
}
|
|
[data-interface-layout="mountain-view"] [data-mv-nav-row] > button {
|
|
gap: 18px;
|
|
}
|
|
[data-interface-layout="mountain-view"] [data-mv-nav-row] > button > span:first-child,
|
|
[data-interface-layout="mountain-view"] [data-mv-nav-row] > button > span:first-child > svg {
|
|
width: 20px;
|
|
height: 20px;
|
|
}
|
|
|
|
/* Compose: a 56px button with a 16px corner, which shrinks to a square with
|
|
the rail. */
|
|
[data-interface-layout="mountain-view"] [data-mv-compose] {
|
|
height: var(--mv-compose-h);
|
|
border-radius: 16px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
/* Message rows: read mail sits on a faint tint and unread on the plain card,
|
|
a ticked row takes the selection colour, and the row under the pointer
|
|
lifts on a shadow instead of changing colour. */
|
|
[data-interface-layout="mountain-view"] [data-mv-row] {
|
|
background-color: var(--mv-row-read);
|
|
box-shadow: none;
|
|
}
|
|
[data-interface-layout="mountain-view"] [data-mv-row][data-unread="true"] {
|
|
background-color: var(--color-background);
|
|
}
|
|
[data-interface-layout="mountain-view"] [data-mv-row][data-checked] {
|
|
background-color: var(--mv-nav-selected);
|
|
}
|
|
[data-interface-layout="mountain-view"] [data-mv-row][aria-current="true"] {
|
|
background-color: var(--color-accent);
|
|
}
|
|
[data-interface-layout="mountain-view"] [data-mv-row]:hover {
|
|
z-index: 1;
|
|
box-shadow:
|
|
inset 1px 0 0 var(--color-border),
|
|
inset -1px 0 0 var(--color-border),
|
|
0 1px 2px 0 rgb(60 64 67 / 0.3),
|
|
0 1px 3px 1px rgb(60 64 67 / 0.15);
|
|
}
|
|
|
|
/* The open message's toolbar: round 40px icon buttons in the muted colour,
|
|
20px icons. Menus that drop from it keep their own rows. */
|
|
[data-interface-layout="mountain-view"] [data-mv-viewer-toolbar] button:not([role="menu"] *):not([data-mv-pager] *) {
|
|
height: 40px;
|
|
min-width: 40px;
|
|
padding-inline: 10px;
|
|
border-radius: 9999px;
|
|
color: var(--color-muted-foreground);
|
|
}
|
|
[data-interface-layout="mountain-view"] [data-mv-viewer-toolbar] button:not([role="menu"] *):not([data-mv-pager] *):hover {
|
|
color: var(--color-foreground);
|
|
}
|
|
[data-interface-layout="mountain-view"] [data-mv-viewer-toolbar] button:not([role="menu"] *) > svg {
|
|
width: 20px;
|
|
height: 20px;
|
|
}
|
|
|
|
/* The compose window's bottom bar: its two halves fold into one row, Send
|
|
leads as a round split button without its icon, the tools follow as bare
|
|
icons, and the bin for Discard sits at the far end. */
|
|
[data-mv-compose-footer] > * {
|
|
display: contents;
|
|
}
|
|
[data-mv-compose-footer] > * > * {
|
|
order: 1;
|
|
}
|
|
[data-mv-compose-footer] [data-mv-send] {
|
|
order: 0;
|
|
margin-inline-end: 0.5rem;
|
|
}
|
|
[data-mv-compose-footer] [data-mv-discard] {
|
|
order: 2;
|
|
margin-inline-start: auto;
|
|
}
|
|
[data-mv-compose-footer] [data-mv-send] .tabler-icon-send {
|
|
display: none;
|
|
}
|
|
[data-mv-compose-footer] button[data-mv-send],
|
|
[data-mv-compose-footer] [data-mv-send] > button:first-child {
|
|
height: 36px;
|
|
padding-inline: 1.25rem 1rem;
|
|
border-radius: 18px;
|
|
}
|
|
[data-mv-compose-footer] [data-mv-send] > button:first-child:not(:only-child) {
|
|
border-start-end-radius: 0;
|
|
border-end-end-radius: 0;
|
|
}
|
|
[data-mv-compose-footer] [data-mv-send] > button:nth-child(2) {
|
|
height: 36px;
|
|
border-radius: 0 18px 18px 0;
|
|
}
|
|
[dir="rtl"] [data-mv-compose-footer] [data-mv-send] > button:nth-child(2) {
|
|
border-radius: 18px 0 0 18px;
|
|
}
|
|
[data-mv-compose-footer] button:not([data-mv-send] *):not([data-mv-send]) > span.text-sm,
|
|
[data-mv-compose-footer] button[data-testid$="-menu"] > .tabler-icon-chevron-down {
|
|
display: none;
|
|
}
|
|
[data-mv-compose-footer] button[data-testid$="-menu"] {
|
|
position: relative;
|
|
width: 36px;
|
|
padding-inline: 0;
|
|
justify-content: center;
|
|
border-radius: 9999px;
|
|
}
|
|
[data-mv-compose-footer] button[data-testid$="-menu"] > span:not(.text-sm) {
|
|
position: absolute;
|
|
top: 0;
|
|
inset-inline-end: 0;
|
|
}
|
|
|
|
/* The account popover: a wide card with a very round corner, rather
|
|
than the narrow menu the rail and the sidebar switchers drop. */
|
|
[data-interface-layout="mountain-view"] [data-mv-account-popover] {
|
|
width: var(--mv-popover-w);
|
|
border-radius: var(--mv-popover-radius);
|
|
}
|
|
|
|
/* The advanced filters drop from the search field with a small gap and take
|
|
its corner, so the two read as one control. Position and width come from
|
|
the field's own box, measured by FilterPanelHost. */
|
|
[data-interface-layout="mountain-view"] [data-mv-filter-panel] {
|
|
border-radius: var(--mv-search-radius);
|
|
}
|
|
|
|
/* The collapsed rail's hover peek. Only on desktop: below that breakpoint the
|
|
sidebar is already a drawer, opened by a tap rather than by a pointer that
|
|
does not exist. The width beats Tailwind's own `lg:w-full` on specificity,
|
|
which is what keeps the floating panel from filling the 72px column. */
|
|
@media (min-width: 1024px) {
|
|
[data-interface-layout="mountain-view"] [data-mv-rail-peek] {
|
|
width: var(--mv-rail-peek-w);
|
|
}
|
|
}
|