Files
odysseus/static/css/email-calendar-notes-tasks.css
T
Léo 3807cb22e7 refactor(css): move cookbook, research, memory and settings styles to their own file
Last step of splitting static/style.css by panel. This moves the cookbook,
deep research, memory and settings rules that can move into
static/css/cookbook-research-memory-settings.css.

After the three steps style.css still holds the shell and every rule whose
move would change the cascade, which on this branch is most of the file.
Eager ordered links, verbatim moves, no rendering change.
2026-09-23 16:12:53 +02:00

1574 lines
58 KiB
CSS

/* Email, calendar, notes and tasks panels.
*
* Split out of style.css for maintainability, not for performance: this file
* is loaded eagerly from index.html immediately after style.css, so the rules
* land in the cascade in exactly the order they had inside the single file.
* Nothing here is lazy-loaded.
*
* LOAD ORDER IS PART OF THE CONTRACT. The <link> tags in static/index.html and
* the PRECACHE list in static/sw.js must both list the stylesheets in this
* order:
* 1. static/style.css
* 2. static/css/documents-gallery-editor.css
* 3. static/css/email-calendar-notes-tasks.css
* 4. static/css/cookbook-research-memory-settings.css
*
* Rules were moved verbatim. A rule stays in style.css when its selector group
* covers more than one panel, when the class is shared design language used by
* modules outside this panel, or when moving it would change which of two
* equally specific rules wins.
*/
@keyframes email-notif-breathe {
0%, 100% {
box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent, var(--red)) 60%, transparent);
opacity: 0.85;
}
50% {
box-shadow: 0 0 6px 2px color-mix(in srgb, var(--accent, var(--red)) 55%, transparent);
opacity: 1;
}
}
.memory-item.task-paused:hover {
opacity: 0.85 !important;
filter: saturate(0.9);
}
.email-folder-picker .email-folder-select-control {
padding-left: 26px;
}
/* Email bulk-select mode owns the whole card. Sender names and tags must not
open search/filter actions until Select is cancelled. */
#email-lib-grid.email-selecting .email-meta-sender,
#email-lib-grid.email-selecting .email-card-tags,
#email-lib-grid.email-selecting .email-card-favorite,
#email-lib-grid.email-selecting .email-card-nav-arrows,
#email-lib-grid.email-selecting .email-card-status,
#email-lib-grid.email-selecting .email-card-actions {
pointer-events: none !important;
}
.email-reader-actions-inline > .email-reader-more-wrap { order: 5; }
/* ── Tasks ── */
#tool-tasks-btn.task-failure-pending,
#rail-tasks.task-failure-pending {
color: var(--red, #f87171);
}
#tool-tasks-btn.task-completion-pending,
#rail-tasks.task-completion-pending {
color: var(--accent, var(--green, #86efac));
}
#tool-tasks-btn.task-failure-pending::after,
#rail-tasks.task-failure-pending::after,
#tool-tasks-btn.task-completion-pending::after,
#rail-tasks.task-completion-pending::after {
content: '';
width: 7px;
height: 7px;
border-radius: 999px;
background: var(--red, #f87171);
box-shadow: 0 0 7px var(--red, #f87171), 0 0 3px var(--red, #f87171);
flex: 0 0 auto;
}
#tool-tasks-btn.task-completion-pending::after,
#rail-tasks.task-completion-pending::after {
background: var(--accent, var(--green, #86efac));
box-shadow: 0 0 7px var(--accent, var(--green, #86efac)), 0 0 3px var(--accent, var(--green, #86efac));
}
#tool-tasks-btn.task-failure-pending.task-completion-pending,
#rail-tasks.task-failure-pending.task-completion-pending {
color: var(--red, #f87171);
}
#tool-tasks-btn.task-failure-pending.task-completion-pending::after,
#rail-tasks.task-failure-pending.task-completion-pending::after {
background: var(--red, #f87171);
box-shadow: 0 0 7px var(--red, #f87171), 0 0 3px var(--red, #f87171);
}
#rail-tasks.task-failure-pending::after,
#rail-tasks.task-completion-pending::after {
position: absolute;
right: 8px;
top: 8px;
}
.tasks-activity-card,
.tasks-activity-list {
min-height: 0;
}
.tasks-activity-list {
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
.task-log-row.task-log-row-error .task-log-task-icon svg {
opacity: 0.82 !important;
}
.task-log-running-inline .task-log-running-label { font-weight: 500; }
.task-log-running-elapsed {
margin-left: 6px;
opacity: 0.6;
font-variant-numeric: tabular-nums;
}
.task-log-time {
opacity: 0.5;
font-variant-numeric: tabular-nums;
}
@keyframes task-log-pulse {
0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, #60a5fa 60%, transparent); }
50% { box-shadow: 0 0 0 4px color-mix(in srgb, #60a5fa 0%, transparent); }
}
.task-log-row-body li { margin: 2px 0; }
.task-log-row-body hr {
border: none;
border-top: 1px solid color-mix(in srgb, var(--fg) 12%, transparent);
margin: 8px 0;
}
.task-log-row-toggle::before {
color: color-mix(in srgb, var(--accent, var(--fg)) 80%, transparent);
opacity: 0.8;
}
.task-log-row-toggle:hover::before { opacity: 1; }
/* A <select>'s closed width otherwise grows to its widest option (the long
action descriptions), overflowing the modal — clip it to the field width. */
select.task-form-input { text-overflow: ellipsis; }
.task-form-textarea { resize: vertical; min-height: 60px; }
.task-form-notif-switch::after {
content: "";
position: absolute;
width: 10px;
height: 10px;
left: 3px;
top: 2px;
border-radius: 50%;
background: color-mix(in srgb, var(--fg) 62%, transparent);
transition: transform 0.15s, background 0.15s;
}
.task-form-notif-toggle input:checked + .task-form-notif-switch::after {
transform: translateX(13px);
background: var(--accent, var(--red));
}
.email-list { overflow-y: auto; max-height: 400px; }
.email-item:hover .email-menu-btn { opacity: 0.5; }
.email-item-spam { opacity: 0.4; }
/* Auto-done flash: when a row gets marked done because the user just sent
a reply, briefly tint the whole row with the accent and pop the check
so the state change is hard to miss. Self-removes after ~1.2s. */
@keyframes email-auto-done-row {
0% { background: color-mix(in srgb, var(--accent, var(--red)) 22%, transparent); }
60% { background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); }
100% { background: transparent; }
}
@keyframes email-auto-done-check {
0% { transform: scale(1); box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent, var(--red)) 45%, transparent); }
40% { transform: scale(1.4); box-shadow: 0 0 0 6px color-mix(in srgb, var(--accent, var(--red)) 0%, transparent); }
100% { transform: scale(1); }
}
.email-item.email-auto-done-flash { animation: email-auto-done-row 1.2s ease-out; }
.email-item.email-auto-done-flash .email-done-check { animation: email-auto-done-check 0.6s ease-out; }
/* Email titles that carry a cached AI summary get a thin underline-dot hint
so the user knows a hover-preview is available. */
.memory-item-title.email-card-has-summary {
cursor: help;
text-decoration: underline dotted color-mix(in srgb, var(--accent) 60%, transparent) 1px;
text-underline-offset: 3px;
}
#email-lib-grid .doclib-card.email-card-unread:not(.email-card-expanded) {
border-color: color-mix(in srgb, var(--accent, var(--red)) 58%, var(--border)) !important;
box-shadow:
0 0 0 1px color-mix(in srgb, var(--accent, var(--red)) 24%, transparent),
0 0 10px -4px color-mix(in srgb, var(--accent, var(--red)) 55%, transparent);
}
#email-lib-grid .doclib-card.email-card-unread:not(.email-card-expanded):hover {
border-color: color-mix(in srgb, var(--accent, var(--red)) 76%, var(--border)) !important;
box-shadow:
0 0 0 1px color-mix(in srgb, var(--accent, var(--red)) 32%, transparent),
0 0 12px -3px color-mix(in srgb, var(--accent, var(--red)) 65%, transparent);
}
.email-card-favorite.active:hover,
.email-card-favorite.active:focus-visible { opacity: 1; }
.email-card-favorite:active { transform: scale(0.88); }
.email-card-favorite svg { pointer-events: none; }
.email-card-done.just-checked {
animation: check-pop 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
}
/* Reverse animation when un-checking — shrink-and-fade so the click is
obviously felt even when hover styling otherwise keeps the icon visible. */
.email-card-done.just-unchecked {
animation: check-unpop 0.42s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.email-card-reader-loading .spinner-whirlpool,
.email-card-reader-loading .ai-spinner-whirlpool {
opacity: 0.85;
}
.email-skeleton-row.compact { min-height: 42px; opacity: 0.72; }
.email-skeleton-line.title { width: min(88%, 520px); height: 9px; }
.email-skeleton-line.meta { width: min(72%, 420px); height: 7px; opacity: 0.78; }
.email-skeleton-line.date { width: 72px; height: 7px; flex: 0 0 auto; opacity: 0.72; }
@keyframes email-skeleton-shimmer {
0% { background-position: 120% 0; opacity: 0.68; }
50% { opacity: 1; }
100% { background-position: -120% 0; opacity: 0.68; }
}
@keyframes email-search-progress-pulse {
0%, 100% { transform: scale(0.9); opacity: 0.55; box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent-primary, var(--red)) 35%, transparent); }
50% { transform: scale(1.1); opacity: 1; box-shadow: 0 0 0 5px transparent; }
}
@keyframes email-inline-image-spin {
to { transform: rotate(360deg); }
}
@keyframes email-card-unread-breathe {
0%, 100% {
box-shadow: 0 0 0 0 color-mix(in srgb, currentColor 0%, transparent);
opacity: 0.85;
transform: scale(1);
}
50% {
/* currentColor here is unreliable (background, not color) — use a
subtle white-tinted halo so the glow shows on any sender hue. */
box-shadow: 0 0 6px 2px rgba(255, 255, 255, 0.18);
opacity: 1;
transform: scale(1.15);
}
}
@media (prefers-reduced-motion: reduce) {
.email-card-unread-dot { animation: none; }
}
#email-lib-pills::-webkit-scrollbar { display: none; }
.email-reader-meta-details[hidden] { display: none; }
.email-reader-header > .email-reader-meta {
flex: 1; min-width: 0;
opacity: 0.85; line-height: 1.7; font-size: 11px;
}
/* When rendering HTML emails, override pre-wrap so layout works */
.email-reader-body.html-body {
white-space: normal;
}
.email-reader-body .email-inline-image-placeholder.is-error {
border-color: color-mix(in srgb, var(--red, #ff5555) 45%, var(--border));
}
.email-inline-image-placeholder.is-loading .email-inline-image-icon {
width: 16px;
height: 16px;
border: 2px solid color-mix(in srgb, var(--fg) 20%, transparent);
border-top-color: var(--accent, var(--red));
border-radius: 50%;
animation: email-inline-image-spin 0.75s linear infinite;
}
.email-inline-image-frame.is-loading {
animation: email-skeleton-shimmer 1.35s ease-in-out infinite;
}
.email-inline-image-frame .email-inline-image-loaded.is-visible {
opacity: 1;
}
/* Preserve highlights — sender's intent (snark, callouts) gets through, but
rendered with a theme-aware accent so it stays legible in any theme. The
sanitizer rewrites `<span style="background:yellow">…</span>` to <mark>. */
.email-reader-body mark {
background: color-mix(in srgb, var(--accent, var(--red)) 22%, transparent);
color: inherit;
padding: 0 2px;
border-radius: 2px;
}
.email-summary-toggle:hover { opacity: 1; }
.email-summary-panel.collapsed .email-summary-header { margin-bottom: 0; }
.email-summary-panel.collapsed .email-summary-content { display: none; }
.email-summary-panel.collapsed .email-summary-chevron { transform: rotate(-90deg); }
.email-translation-panel { margin-top: 0; }
.email-attachments-download-all.is-loading {
opacity: 0.82;
pointer-events: none;
}
.email-reader-atts-wrap.collapsed > .email-reader-atts,
.email-reader-atts-wrap.collapsed > .email-reader-atts-hidden-note { display: none; }
.email-reader-atts-wrap.collapsed .email-summary-chevron { transform: rotate(-90deg); }
.email-thread-turn-body .email-thread-turn .email-fold-summary {
padding: 5px 10px;
}
.email-thread-turn-body .email-thread-turn .email-thread-turn-body {
padding: 6px 10px 2px;
font-size: 11px;
}
.email-quote-fold .email-fold-summary::-webkit-details-marker,
.email-quote-fold .email-fold-summary::marker { display: none; content: none; }
.email-quote-fold .email-fold-summary:hover { opacity: 1; }
.email-quote-fold[open] .email-summary-chevron { transform: rotate(180deg); }
/* Suppress the global `summary::before { content: '▶' }` rule from
leaking the chevron-emoji onto our header — we use the trailing SVG
chevron only. */
.email-sig-fold .email-fold-summary::before,
.email-quote-fold .email-fold-summary::before { content: none !important; }
.email-sig-fold .email-fold-summary::-webkit-details-marker,
.email-sig-fold .email-fold-summary::marker { display: none; content: none; }
.email-sig-fold .email-fold-summary:hover { opacity: 1; }
.email-sig-fold[open] .email-summary-chevron { transform: rotate(180deg); }
.email-bubble-row.email-bubble-mine {
flex-direction: row-reverse;
justify-content: flex-start;
}
.email-bubble-row.email-bubble-theirs {
/* Avatar lives at the END of the row (right side) so the bubble has
the full left edge to start from — gives bigger reading width and
a cleaner left margin. */
flex-direction: row-reverse;
justify-content: flex-end;
}
.email-bubble-mine .email-bubble-head { justify-content: flex-end; }
.email-bubble-body *:not(code):not(pre):not(kbd):not(samp) {
line-height: inherit !important;
}
.email-bubble-body img {
max-width: 100%;
height: auto;
}
.email-bubble-body table {
max-width: 100%;
}
.email-bubble-body .email-sig-fold .email-fold-summary,
.email-bubble-body .email-quote-fold .email-fold-summary {
padding: 4px 0;
}
.email-sender-clickable { cursor: pointer; }
.email-sender-clickable:hover { text-decoration: underline; }
.email-filter-chip-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.email-filter-chip-clear:hover { opacity: 1; }
@media (max-width: 768px) {
.email-search-row {
flex-wrap: wrap;
}
}
.email-tags-collapsed .email-tag-extra { display: none; }
.email-tags-expanded .email-tag-extra { display: inline-flex; align-items: center; }
.email-card-tags.email-tags-mini:not(.email-tags-expanded) > .email-tags-more:not(.email-tags-more-single) {
display: none;
}
.email-card-tags.email-tags-mini:not(.email-tags-expanded) .email-tags-more-single {
display: inline-flex;
}
.email-card-tags.email-tags-expanded .email-tags-more-single {
display: none;
}
.email-tags-more:hover { opacity: 0.92; }
.email-tag-clickable:hover {
filter: brightness(1.12);
}
@keyframes email-spin { to { transform: rotate(360deg); } }
/* Compose + button: show on section hover, spin on button hover */
#email-compose-btn {
opacity: 0.4 !important;
transition: opacity 0.15s ease;
}
/* Satisfying send effect: email slides out to the right and fades */
.email-send-fx {
animation: email-send-slide 0.5s cubic-bezier(0.4, 0, 0.2, 1) forwards;
pointer-events: none;
}
@keyframes email-send-slide {
0% {
transform: translateX(0);
opacity: 1;
filter: blur(0);
}
60% {
filter: blur(1px);
}
100% {
transform: translateX(110%);
opacity: 0;
filter: blur(3px);
}
}
.email-lib-unread-badge:hover { filter: brightness(1.08); }
.email-lib-auto-reply-badge:hover {
filter: brightness(1.08);
}
.email-card-nav-btn:disabled {
opacity: 0.25;
cursor: default;
pointer-events: none;
}
.email-send-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.email-draft-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.email-attachment-chip .att-size { opacity: 0.5; font-size: 10px; flex-shrink: 0; }
.email-attachment-chip-muted { opacity: 0.65; }
.email-attachment-chip-muted:hover { opacity: 1; }
.email-attachment-open.is-loading {
pointer-events: none;
opacity: 0.82;
}
.email-attachment-open.is-loading .spinner-whirlpool,
.email-attachment-open.is-loading .ai-spinner-whirlpool {
width: 12px !important;
height: 12px !important;
margin: 0 !important;
position: relative;
top: 0;
}
.email-compose-chip .att-size { opacity: 0.5; font-size: 10px; flex-shrink: 0; }
.email-odysseus-attach-thumb img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.email-odysseus-attach-selected:disabled {
opacity: 0.45;
cursor: default;
}
.email-odysseus-attach-selected.is-loading {
opacity: 0.72;
pointer-events: none;
}
@media (max-width: 768px) {
.email-odysseus-attach-menu {
left: 8px !important;
right: 8px;
bottom: 8px;
top: auto !important;
width: auto;
max-height: 58vh;
}
.email-odysseus-attach-list {
max-height: calc(58vh - 88px);
}
}
.email-ai-reply-btn:disabled { opacity: 0.5; cursor: not-allowed; }
#email-lib-settings-btn { top: -1px; }
#email-lib-settings-btn svg { position: relative; top: 2px; }
/* New (Compose) button gets a slightly taller, slightly lower variant
on desktop so it reads as the primary action of the row. Scoped to
desktop so the mobile touch-friendly variant (further down) stays in
charge for ≤768px. */
@media (min-width: 769px) {
#email-lib-compose-btn { height: 28px; top: 0; }
}
/* Envelope icon picks up the user's accent so the primary action is
easy to spot at any size. The SVG already uses stroke=currentColor,
so a color: rule on the svg propagates. */
#email-lib-compose-btn svg { color: var(--accent, var(--red)); }
@media (max-width: 768px) {
/* On mobile they're 1px higher than desktop. */
#email-lib-select-btn, #email-lib-refresh-btn, #email-lib-settings-btn { top: -3px; }
#email-lib-settings-btn { top: -2px; }
}
.email-settings-page-host > .email-settings-body {
overflow: visible;
}
.email-settings-page-head:empty {
display: none;
}
.email-settings-section > summary {
list-style: none;
padding: 0;
border: 0;
background: transparent;
color: inherit;
font: inherit;
user-select: none;
}
.email-settings-section > summary::-webkit-details-marker {
display: none;
}
.email-settings-section > summary::before {
content: none;
display: none;
}
.email-settings-section > .email-settings-section-body {
padding: 0;
font-size: inherit;
line-height: normal;
animation: none;
}
.email-settings-section-toggle:focus-visible {
outline: 1px solid var(--accent, var(--red));
outline-offset: 3px;
border-radius: 3px;
}
.email-settings-section.is-expanded .email-settings-section-chevron {
transform: rotate(180deg);
}
.email-settings-section[open] .email-settings-section-chevron {
transform: rotate(180deg);
}
.email-settings-section-body > .email-settings-enabled-control {
align-self: flex-end;
}
.email-settings-auto-reply-section > summary,
.email-settings-cleanup-section > summary {
padding-bottom: 6px;
border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent);
}
.email-settings-auto-reply-title-row {
top: 0;
}
.email-settings-clean-actions:has(.email-settings-clean-status.is-busy) {
flex-direction: row;
align-items: center;
}
.email-settings-clean-status:not(:empty) {
display: inline-flex;
align-items: center;
}
.email-unsub-card.is-expanded .email-unsub-card-toggle > svg {
transform: rotate(180deg);
}
.email-unsub-card.is-unsubscribed {
opacity: .58;
}
.email-settings-grid > label > span:first-child,
.email-settings-field > span {
color: var(--accent, var(--red));
}
@media (max-width: 768px) {
.email-settings-section[open] > .email-settings-section-body {
height: auto;
max-height: none;
}
.email-settings-auto-reply-section[open] > .email-settings-section-body,
.email-settings-cleanup-section[open]:has(.email-unsub-card) > .email-settings-section-body {
height: 100%;
max-height: 100%;
}
}
/* On mobile, the "New" (compose) button deserves a touch-friendly size
so it stands out from the smaller toolbar buttons next to it. Lift
it slightly visually with a bigger SVG, padding, and font. */
@media (max-width: 768px) {
#email-lib-compose-btn {
padding: 8px 14px !important;
font-size: 13px !important;
min-height: 36px;
border-radius: 8px;
font-weight: 600;
top: -2px;
}
#email-lib-compose-btn svg {
width: 14px !important;
height: 14px !important;
margin-right: 5px !important;
}
}
/* Checklist rows stay tappable on mobile; only destructive hover affordances
stay hidden. Select mode below still disables checklist toggling. */
body.notes-mobile-mode .note-card .note-checkbox-rm {
pointer-events: none;
}
body.notes-mobile-mode .note-card .note-checkbox {
pointer-events: auto;
user-select: none;
-webkit-user-select: none;
-webkit-touch-callout: none;
touch-action: manipulation;
}
body.notes-mobile-mode .note-card .note-check-dot,
body.notes-mobile-mode .note-card .note-checkbox-dot,
body.notes-mobile-mode .note-card .note-cl-dot {
opacity: 0.55;
cursor: pointer !important;
}
body.notes-mobile-mode .note-card:active {
transform: scale(0.985);
}
body.notes-drag-mode .note-card::after {
content: '';
position: absolute;
inset: 0;
pointer-events: none;
background: linear-gradient(
115deg,
transparent 30%,
color-mix(in srgb, var(--fg) 14%, transparent) 50%,
transparent 70%
);
background-size: 250% 100%;
animation: notes-drag-shimmer 2.1s linear infinite;
border-radius: inherit;
z-index: 1;
mix-blend-mode: overlay;
}
@keyframes notes-drag-shimmer {
0% { background-position: 250% 0; }
100% { background-position: -150% 0; }
}
/* The grabbed card lifts above its siblings; suppress the shimmer on
it so it reads as the "live" element being moved. */
body.notes-drag-mode .note-card.note-card-dragging {
transform: scale(1.06);
box-shadow: 0 16px 40px rgba(0,0,0,0.45);
opacity: 1;
filter: none;
z-index: 10001;
cursor: grabbing;
transition: box-shadow 0.18s ease;
}
body.notes-drag-mode .note-card.note-card-dragging::after { display: none; }
body.notes-drag-mode .note-card .note-card-pin {
visibility: hidden !important;
opacity: 0 !important;
pointer-events: none !important;
}
body.notes-drag-mode .note-card-pinned .note-card-pin,
body.notes-drag-mode .note-card-pin.active,
body.notes-drag-mode .note-card-pin svg {
visibility: hidden !important;
opacity: 0 !important;
}
@media (prefers-reduced-motion: reduce) {
body.notes-drag-mode .note-card::after { animation: none; }
}
.note-fullscreen-overlay .note-form-type-pill {
padding: 10px 16px;
font-size: 14px;
}
.note-fullscreen-overlay .note-cl-grip:active { cursor: grabbing; opacity: 0.9; }
.note-fullscreen-overlay .note-cl-dot::before {
content: '';
position: absolute;
inset: -12px;
}
.note-fullscreen-overlay .note-cl-text {
font-size: 14px;
padding: 7px 6px;
min-height: 32px;
}
.note-fullscreen-overlay .note-cl-rm:active { opacity: 1; }
/* + Add (new checklist item) was tiny — bump for thumbs */
.note-fullscreen-overlay .note-cl-add {
display: inline-flex;
align-items: center;
padding: 10px 18px;
font-size: 15px;
font-weight: 600;
border-radius: 8px;
letter-spacing: -0.01em;
opacity: 0.85;
}
.note-fullscreen-overlay .note-cl-add:active { opacity: 1; }
.note-fullscreen-overlay .note-cl-add-row .note-cl-add { margin: 0; }
.note-fullscreen-overlay .note-cl-add-row .note-form-photo-btn::before {
content: '+';
font-size: 17px;
font-weight: 600;
line-height: 1;
opacity: 0.85;
}
.note-fullscreen-overlay .note-cl-add-row .note-form-photo-btn svg {
width: 16px;
height: 16px;
}
.note-form-content-reader a {
color: var(--accent, var(--red));
text-decoration: underline;
text-underline-offset: 2px;
}
.note-form-content-reader a:active { opacity: 0.7; }
.note-fullscreen-overlay .note-cl-text-reader a {
color: var(--accent, var(--red));
text-decoration: underline;
text-underline-offset: 2px;
}
.note-fullscreen-overlay .note-cl-text-reader a:active { opacity: 0.7; }
/* Reminder bell + other header icon-buttons. Tightened down 2px so
the bell sits more proportionally next to the title field. */
.note-fullscreen-overlay .note-form-icon-btn {
width: 36px;
height: 36px;
}
/* The plain-note textarea was rows="4" — fine for an in-grid card but
cramped in fullscreen. Let it grow to fill the available height so
writing feels like a real notepad, not a tweet box. The read-mode
reader inherits the same minimum so the layout doesn't jump when
the user taps to edit. */
.note-fullscreen-overlay .note-form-content,
.note-fullscreen-overlay .note-form-content-reader {
min-height: 55vh;
font-size: 15px;
line-height: 1.55;
}
/* Reorganized meta-row layout for fullscreen: color picker pinned to
the LEFT, type-toggle (Note/Todo/Draw) shoved to the RIGHT, with
the photo button next to the color picker. Tags are moved up into
the form header (next to the reminder bell) via JS. */
.note-fullscreen-overlay .note-form-meta {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
padding-top: 8px;
}
.note-fullscreen-overlay .note-form-photo-btn { order: 2; }
.note-fullscreen-overlay .note-form-actions-group .note-form-cancel,
.note-fullscreen-overlay .note-form-actions-group .note-form-save {
flex: 0 0 auto;
}
@keyframes notes-backdrop-fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes notes-pane-enter {
from { transform: scale(0.96); opacity: 0; }
to { transform: scale(1); opacity: 1; }
}
.notes-pane.notes-pane-leaving {
animation: notes-pane-leave 160ms cubic-bezier(0.4, 0, 1, 1) both;
pointer-events: none;
}
@keyframes notes-pane-leave {
from { transform: scale(1); opacity: 1; }
to { transform: scale(0.96); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
.notes-pane,
.notes-pane.notes-pane-leaving { animation: none; }
}
/* Hide the "Add a to-do…" quick-add bar in archive view — you can't add
new items from the archive. */
.notes-pane-archive .notes-quick-add { display: none !important; }
.note-checkbox:hover .note-checkbox-agent { opacity: 0.55; }
@keyframes note-reminder-pulse {
0%, 100% {
box-shadow:
inset 0 2px 0 0 color-mix(in srgb, var(--fg) 14%, transparent),
0 0 0 0 color-mix(in srgb, var(--accent, var(--red)) 0%, transparent);
}
50% {
box-shadow:
inset 0 2px 0 0 color-mix(in srgb, var(--fg) 14%, transparent),
0 0 0 5px color-mix(in srgb, var(--accent, var(--red)) 26%, transparent),
0 0 18px color-mix(in srgb, var(--accent, var(--red)) 30%, transparent);
}
}
@keyframes note-reminder-glow {
0%, 100% {
box-shadow:
inset 0 2px 0 0 color-mix(in srgb, var(--fg) 14%, transparent),
0 0 0 3px color-mix(in srgb, var(--accent, var(--red)) 14%, transparent),
0 0 14px color-mix(in srgb, var(--accent, var(--red)) 18%, transparent);
}
50% {
box-shadow:
inset 0 2px 0 0 color-mix(in srgb, var(--fg) 14%, transparent),
0 0 0 6px color-mix(in srgb, var(--accent, var(--red)) 22%, transparent),
0 0 26px color-mix(in srgb, var(--accent, var(--red)) 30%, transparent);
}
}
.note-card-selected {
outline: 2px solid var(--accent);
outline-offset: -2px;
}
.note-card-select { left: -8px; }
.note-card-pin { right: -8px; }
/* Pin button on mobile — only reveal once the user has long-pressed to
enter rearrange (drag) mode. Outside drag mode the card stays clean. */
body.notes-mobile-mode.notes-drag-mode .note-card-pin {
display: flex !important;
opacity: 0.7;
}
body.notes-mobile-mode.notes-drag-mode .note-card-pin.active {
opacity: 1;
}
/* Edit + Done are now side-by-side at top-right by default — title-only
fallback no longer needed. */
.note-card-selectmode .note-card-done { display: none !important; }
.note-card:hover .note-card-corner-copy { visibility: visible; }
/* Copy stays on every active card regardless of body shape — Edit + Done
live at the top-right pair, so the bottom-right copy slot is always
free. Hidden only in select mode. */
.note-card-selectmode .note-card-corner-copy { display: none !important; }
.note-card-corner-trash { left: 6px; }
.note-card-corner-unarchive { right: 6px; }
.note-card:hover .note-card-corner-trash,
.note-card:hover .note-card-corner-unarchive { visibility: visible; }
@keyframes note-ai-shine {
0%, 100% { opacity: 0.85; filter: drop-shadow(0 0 0 transparent); }
50% { opacity: 1; filter: drop-shadow(0 0 4px color-mix(in srgb, var(--accent, var(--red)) 60%, transparent)); }
}
.note-form-improve-btn:disabled,
.note-form-improve-btn.busy {
opacity: 0.6;
cursor: wait;
}
/* In select mode the whole card is a checkbox — kill every hover affordance
that would otherwise suggest "preview" or "open" so users know exactly
what their tap will do (toggle selection). */
.note-card-selectmode .note-card-pin,
.note-card-selectmode .note-card-actions { pointer-events: none; opacity: 0.4; }
.note-card-selectmode .note-card-edit-corner,
.note-card-selectmode .note-card-pin {
display: none !important;
}
.note-card-selectmode .note-card-title:hover { opacity: 1 !important; }
.note-card-selectmode .note-card-edit-corner,
.note-card-selectmode:hover .note-card-edit-corner { opacity: 0 !important; }
/* The familiar home-screen edit-mode cue: mobile note cards gently jiggle
while Select mode is active, making it clear that the bar is now selecting
items rather than opening them. */
@keyframes notes-select-jiggle {
0%, 100% { transform: rotate(-0.3deg) translateY(0); }
50% { transform: rotate(0.3deg) translateY(-0.25px); }
}
.note-card-action svg { transform: translateY(2px); }
@keyframes notes-quick-pulse {
0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent, var(--red)) 0%, transparent); }
50% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); }
}
.notes-quick-input::placeholder {
color: color-mix(in srgb, var(--fg) 45%, transparent);
}
/* Blinking text caret hint before the placeholder so empty + unfocused
reads as a live input. Sits on the wrapper because <input> elements
can't carry ::before. The pseudo-element is ALWAYS rendered (so its
width is reserved and the placeholder doesn't jump left when the
hint hides); only the colour/animation toggle, not the layout. */
.notes-quick-add::before {
content: '|';
color: var(--accent, var(--red));
margin-right: 6px;
font-weight: 500;
font-size: 13px;
animation: notes-quick-caret 1s steps(2, jump-none) infinite;
pointer-events: none;
}
/* Hide visually (keep space) on hover, focus, or once the input has any
text — prevents the layout shift the user reported when the hint
collapsed and the placeholder slid left. */
.notes-quick-add:hover::before,
.notes-quick-add:focus-within::before,
.notes-quick-add:not(:has(.notes-quick-input:placeholder-shown))::before {
visibility: hidden;
animation: none;
}
@keyframes notes-quick-caret {
0%, 49% { opacity: 1; }
50%, 100%{ opacity: 0; }
}
.notes-quick-type-seg.is-todo::before { transform: translateX(100%); }
.notes-skeleton-card.short { height: 46px; }
@keyframes notes-skeleton-shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
@keyframes cal-bell-jingle {
0% { transform: rotate(0deg); }
15% { transform: rotate(-22deg); }
30% { transform: rotate(18deg); }
45% { transform: rotate(-14deg); }
60% { transform: rotate(10deg); }
75% { transform: rotate(-6deg); }
88% { transform: rotate(3deg); }
100% { transform: rotate(0deg); }
}
.note-form-reminder-tags:empty { display: none; }
.note-reminder-day-chip.wide { padding: 0 6px; }
.note-form-type-seg::before {
content: '';
position: absolute;
top: 0;
left: 0;
/* 3 pills now (note / todo / draw) — Goal was removed. Slider takes
1/3 of the track and translates by full multiples of itself. */
width: 33.3333%;
height: 100%;
background: color-mix(in srgb, var(--fg) 10%, transparent);
border-radius: 9px;
transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
z-index: 0;
}
.note-form-type-seg.is-todo::before { transform: translateX(100%); }
.note-form-type-seg.is-draw::before { transform: translateX(200%); }
/* Bulk-bar appears under the search bar — inset its sides to match the
search bar's own 8px horizontal padding so the two visually align. */
#notes-bulk-bar.memory-bulk-bar {
margin: 0 8px 4px;
position: relative;
top: 4px;
}
#tasks-filter-chips {
top: 0 !important;
}
.note-card-goal .note-card-header {
/* Goal cards reserve space for the pill so the title doesn't collide. */
padding-left: 56px;
}
/* In select mode the left-side checkbox takes the pill's spot — drop it
so the row stays legible. */
.note-card-selectmode.note-card-goal .note-goal-pill { display: none; }
.note-card-selectmode.note-card-goal .note-card-header { padding-left: 0; }
.note-form-goal-ai:disabled,
.note-form-goal-ai.busy {
opacity: 0.6;
cursor: wait;
}
/* Fresh goal form: hide the title input + tag input + reminder button so the
user only sees the single "what do you want to achieve?" textarea. */
.note-form-goal-fresh ~ .note-form-actions-group { display: none; }
.note-form:has(.note-form-goal-fresh) .note-form-title,
.note-form:has(.note-form-goal-fresh) .note-form-label,
.note-form:has(.note-form-goal-fresh) .note-form-remind-btn,
.note-form-bespoke:has(.note-form-goal-fresh) .note-form-title,
.note-form-bespoke:has(.note-form-goal-fresh) .note-form-label,
.note-form-bespoke:has(.note-form-goal-fresh) .note-form-remind-btn {
display: none !important;
}
.note-form-goal-fresh .note-form-goal-desc {
font-size: 15px;
min-height: 90px;
padding: 12px 14px;
}
.note-form-goal-fresh.building .note-form-goal-desc {
opacity: 0.55;
pointer-events: none;
}
.note-form-goal-fresh.building::after {
content: 'AI is planning your goal…';
display: block;
text-align: center;
font-size: 12px;
opacity: 0.7;
margin-top: 6px;
animation: note-goal-pulse 1.4s ease-in-out infinite;
}
@keyframes note-goal-pulse {
0%, 100% { opacity: 0.5; }
50% { opacity: 1; }
}
.notes-today-row.done {
opacity: 0.35;
transform: scale(0.98);
}
.notes-today-title:hover { text-decoration: underline; }
.notes-bulk-btn {
opacity: 0.6;
}
.notes-bulk-btn:hover { opacity: 1; }
.note-form-label.flash-once {
animation: noteLabelFlash 0.6s ease-out;
}
.note-form-label:not([value=""]) { opacity: 0.9; border-style: solid; }
.note-card-title:hover { opacity: 0.7; }
.note-card-title.empty::before {
content: 'No title';
font-weight: 500;
opacity: 0.3;
font-style: italic;
}
/* Note card drag */
.note-card { cursor: grab; }
.note-cl-row:hover .note-cl-grip { opacity: 0.6; }
.note-archive-btn:hover { opacity: 0.7; }
.note-checklist-preview > .note-checkbox:last-child .note-check-text {
/* Reserve room on the LAST row only for the bottom-right copy button so it
doesn't cover the final item's tail. Applied to the text — not to the
row — so the X delete button still reaches the same edge it does on
every other row. */
padding-right: 30px;
}
.note-card.doclib-card-expanded .note-checklist-preview {
/* When the card is the expanded one in the grid, let the list use the
whole available height instead of the compact cap. */
max-height: none;
}
.note-checklist-preview:empty::before {
content: 'No todos';
font-size: 10px;
opacity: 0.3;
font-style: italic;
}
.note-card:hover .note-cl-quickadd,
.note-cl-quickadd:focus-within {
opacity: 1;
pointer-events: auto;
}
.note-cl-quickadd-input::placeholder { color: var(--fg); opacity: 0.5; }
.note-card-selectmode .note-cl-quickadd { display: none !important; }
.note-checkbox-rm { margin-left: auto; }
.note-checkbox-edit { margin-left: auto; }
.note-checkbox:hover .note-checkbox-edit,
.note-checkbox:hover .note-checkbox-rm { opacity: 0.55; }
.note-card-selectmode .note-checkbox-edit,
.note-card-selectmode .note-checkbox-rm { display: none; }
.note-check-dot::after {
content: '';
position: absolute;
left: 50%;
top: 45%;
width: 7px;
height: 3.5px;
border-left: 1.75px solid #fff;
border-bottom: 1.75px solid #fff;
transform: translate(-50%, -50%) rotate(-45deg) scale(0);
transform-origin: center;
transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.notes-select-mode .note-checkbox { cursor: default; pointer-events: none; }
.note-checkbox:active .note-check-dot {
transform: scale(0.9);
}
.note-checkbox.done .note-check-dot::after {
transform: translate(-50%, -50%) rotate(-45deg) scale(1);
}
@keyframes note-check-pop {
0% { transform: scale(1); }
40% { transform: scale(1.35); }
100% { transform: scale(1); }
}
.note-checkbox.done .note-check-text {
position: relative;
opacity: 0.4;
}
.note-checkbox.done .note-check-text::after {
content: '';
position: absolute;
left: 0;
right: 0;
top: calc(50% - 1px);
height: 1px;
background: currentColor;
animation: note-strike 0.3s ease-out forwards;
transform-origin: left center;
}
@keyframes note-strike {
0% { transform: scaleX(0); }
100% { transform: scaleX(1); }
}
/* 3D top-edge highlight — an inset 2px stripe inside every note card that
reads as a lifted "glassy" lip. Each colored note gets a stronger stripe
in its own hue so the colour reads even before you focus on the card. */
.note-card { box-shadow: inset 0 2px 0 0 color-mix(in srgb, var(--fg) 14%, transparent); }
.note-color-red { box-shadow: inset 0 2px 0 0 color-mix(in srgb, var(--red) 55%, transparent); }
.note-color-orange { box-shadow: inset 0 2px 0 0 color-mix(in srgb, #d19a66 60%, transparent); }
.note-color-yellow { box-shadow: inset 0 2px 0 0 color-mix(in srgb, var(--hl-string) 60%, transparent); }
.note-color-green { box-shadow: inset 0 2px 0 0 color-mix(in srgb, #98c379 60%, transparent); }
.note-color-blue { box-shadow: inset 0 2px 0 0 color-mix(in srgb, var(--hl-function) 60%, transparent); }
.note-color-purple { box-shadow: inset 0 2px 0 0 color-mix(in srgb, var(--hl-keyword) 60%, transparent); }
.note-card.note-standard-v0 { --note-accent: #db8576; }
.note-card.note-standard-v1 { --note-accent: #d2a45f; }
.note-card.note-standard-v2 { --note-accent: #72ad8d; }
.note-card.note-standard-v3 { --note-accent: #72a5cf; }
.note-card.note-standard-v4 { --note-accent: #aa86c7; }
.note-type-btn:hover { opacity: 0.8; }
.note-form-meta .note-form-label {
width: 112px;
flex: 0 1 140px;
}
/* After attachColorPicker swaps the input to type=text + .cp-swatch-input,
re-pin the swatch to a 24px circle (otherwise it inherits text-input
sizing and becomes a giant rectangle). */
.note-form-draw-color.cp-swatch-input {
width: 24px !important;
height: 24px !important;
min-width: 24px !important;
max-width: 24px !important;
border-radius: 50%;
padding: 0;
box-sizing: border-box;
}
.note-form-draw-color::-webkit-color-swatch-wrapper { padding: 0; border-radius: 50%; }
.note-form-draw-color::-webkit-color-swatch { border: none; border-radius: 50%; }
.note-form-draw-color::-moz-color-swatch { border: none; border-radius: 50%; }
.note-form-draw-size:hover,
.note-form-draw-size:focus,
.note-form-draw-size:active { height: 10px; }
.note-form-draw-size::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 12px; height: 12px;
border-radius: 50%;
background: var(--red);
border: none;
cursor: pointer;
transition: width 0.12s ease, height 0.12s ease;
}
.note-form-draw-size::-moz-range-thumb {
width: 12px; height: 12px;
border-radius: 50%;
background: var(--red);
border: none;
cursor: pointer;
transition: width 0.12s ease, height 0.12s ease;
}
.note-form-draw-size:hover::-webkit-slider-thumb,
.note-form-draw-size:focus::-webkit-slider-thumb,
.note-form-draw-size:active::-webkit-slider-thumb { width: 18px; height: 18px; }
.note-form-draw-size:hover::-moz-range-thumb,
.note-form-draw-size:focus::-moz-range-thumb,
.note-form-draw-size:active::-moz-range-thumb { width: 18px; height: 18px; }
.note-form-draw-be::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 50%;
height: 100%;
background: var(--accent);
border-radius: 9px;
transition: transform 0.28s cubic-bezier(0.34, 1.56, 0.64, 1);
z-index: 0;
}
.note-form-draw-be.is-eraser::before { transform: translateX(100%); }
.note-form-draw-be-btn:focus { outline: none; }
.note-form-draw-text-badge:empty { display: none; }
.note-form-draw-shape-badge:empty { display: none; }
/* Reflect the chosen size in the icon itself, so the user sees at a glance
which size (S/M/L) is active without needing the tiny badge. */
.note-form-draw-line.size-s svg line,
.note-form-draw-circle.size-s svg circle { stroke-width: 1.2; }
.note-form-draw-line.size-m svg line,
.note-form-draw-circle.size-m svg circle { stroke-width: 3; }
.note-form-draw-line.size-l svg line,
.note-form-draw-circle.size-l svg circle { stroke-width: 5; }
.note-form-draw-text.size-s { font-size: 13px; }
.note-form-draw-text.size-m { font-size: 18px; }
.note-form-draw-text.size-l { font-size: 23px; line-height: 1; }
/* Narrow widths: shrink the tag input to make room for the action group;
the hashtag-in-content shortcut still works, so the input can be tiny. */
@media (max-width: 600px) {
.note-form-meta .note-form-label { width: 78px; flex-shrink: 1; min-width: 0; }
}
.note-form-due:not([value=""]) { opacity: 0.85; border-style: solid; }
.note-cl-add:hover { opacity: 0.7; }
/* Matching ↵ hint at the right of the event title while it's empty; hidden once
editing (the ✓ Done button takes over then). */
/* Title placeholder overlay: the prompt text with a ↵ enter glyph right after
it (accent), so it's clear the field submits on Enter. Shown only while the
title is empty; the native placeholder is hidden in favour of this. */
.cal-hero-title::placeholder { color: transparent; }
.cal-quickadd-input::placeholder { color: transparent; }
button.cal-nav.cal-toolbar-arrow:hover {
opacity: 1;
background: color-mix(in srgb, var(--bg) 76%, var(--fg) 24%);
}
.cal-side-nav-prev { left: 8px; }
.cal-side-nav-next { right: 8px; }
.cal-navigation-hidden .cal-side-nav {
display: none !important;
}
#cal-add { margin-left: auto; }
/* Mobile tap-spin: a gentle quarter-turn nudge before the form opens.
`.cal-add-plus` has a `padding-bottom: 2px` (and a translateY on the
small variant) that offsets the glyph for optical centering — those
shift the rotation pivot off the visual centre. Force the box to
match the glyph and pin the transform-origin during the spin. */
.cal-add-plus.cal-add-spinning {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0;
width: 1em;
height: 1em;
line-height: 1;
transform-origin: 50% 50%;
animation: cal-add-spin 0.28s ease-out forwards;
}
@keyframes cal-add-spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(90deg); }
}
button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm:hover {
padding: 0 8px 0 5px;
gap: 3px;
}
.email-reminder-toggle-inline:hover svg {
animation: none !important;
transform: none !important;
}
.email-search-options-menu[hidden],
.email-search-options-menu button.hidden {
display: none !important;
}
.email-search-date-panel[hidden] { display: none !important; }
.email-search-date-panel label {
display: grid;
grid-template-columns: 38px 1fr;
align-items: center;
gap: 6px;
margin: 0 0 6px;
color: color-mix(in srgb, var(--fg) 72%, transparent);
font-size: 10px;
}
.email-lib-filter-pill-icon-only .email-lib-filter-pill-label { display: none; }
.email-lib-filter-pill.email-lib-filter-pill-icon-only { padding-left: 5px !important; }
.email-search-options-menu .email-search-date-actions button {
width: auto;
min-width: 48px;
justify-content: center;
padding: 5px 8px;
}
.email-attach-toggle:not(.email-attach-toggle-inline):hover svg {
animation: email-undone-jiggle 0.45s ease-in-out;
transform-origin: 50% 50%;
}
@keyframes email-undone-jiggle {
0% { transform: rotate(0deg); }
20% { transform: rotate(-14deg); }
40% { transform: rotate(10deg); }
60% { transform: rotate(-6deg); }
80% { transform: rotate(3deg); }
100% { transform: rotate(0deg); }
}
.email-undone-toggle:not(.email-undone-toggle-inline):hover svg,
.email-compose-jiggle:hover svg {
animation: email-undone-jiggle 0.45s ease-in-out;
transform-origin: 50% 50%;
}
/* Account chips collapse to a single horizontally-scrollable strip
at every breakpoint, instead of stacking onto multiple rows when
there are several accounts. The compose "New" button stays outside
the scroller (it's a sibling of #email-lib-accounts inside
.email-accounts-row) so it remains pinned on the right. */
#email-lib-accounts {
justify-content: flex-start;
flex-wrap: nowrap;
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: none;
-ms-overflow-style: none;
scroll-snap-type: x proximity;
-webkit-overflow-scrolling: touch;
}
#email-lib-accounts::-webkit-scrollbar { display: none; height: 0; }
#email-lib-accounts > * { flex-shrink: 0; scroll-snap-align: start; }
.email-account-chip.email-account-has-unread:not(.active) {
border-color: var(--border);
background: transparent;
color: var(--fg);
}
#email-lib-grid { position: relative; }
#email-lib-grid .date-section-header.email-date-gap-break {
margin-top: 12px;
padding-top: 16px;
}
#email-lib-modal.email-reading .email-lib-sync-status {
display: none;
}
button.cal-view-btn {
display: inline-flex;
align-items: center;
justify-content: center;
background: transparent;
border: none;
color: var(--fg);
font-size: 11px;
font-family: inherit;
font-weight: 500;
padding: 2px 12px 2px 12px;
margin: 0;
cursor: pointer;
opacity: 0.45;
line-height: 1;
height: 24px;
box-sizing: border-box;
}
.cal-s-color::-webkit-color-swatch-wrapper { padding: 0; border-radius: 50%; }
.cal-s-color::-webkit-color-swatch { border: none; border-radius: 50%; }
.cal-s-color::-moz-color-swatch { border: none; border-radius: 50%; }
.cal-filter-item.cal-filter-off { opacity:0.25; text-decoration:line-through; }
.cal-filter-item.cal-filter-important.cal-filter-active { background:color-mix(in srgb, #e5a33a 18%, transparent); border-color:#e5a33a; }
.cal-week-row > .cal-day { padding-top: calc(4px + var(--bars, 0) * 12px); }
@keyframes cal-slide-from-right {
from { opacity:0; transform:translateX(20px); }
to { opacity:1; transform:translateX(0); }
}
@keyframes cal-slide-from-left {
from { opacity:0; transform:translateX(-20px); }
to { opacity:1; transform:translateX(0); }
}
@keyframes cal-event-enter {
from { opacity:0; transform:translateY(3px) scale(0.985); }
to { opacity:1; transform:translateY(0) scale(1); }
}
.cal-slide-in-right { animation: cal-slide-from-right 0.22s cubic-bezier(0.25, 0.8, 0.25, 1); }
.cal-slide-in-left { animation: cal-slide-from-left 0.22s cubic-bezier(0.25, 0.8, 0.25, 1); }
.cal-day.cal-today .cal-day-num { color:var(--bg); font-weight:800; background:var(--accent, var(--red)); border-radius:10px; padding:1px 6px; display:inline-block; opacity:1; line-height:1.3; }
/* Selected day — same square geometry as `.cal-today` so the two read as
peers, but rendered as an outline + softer fill instead of a solid
accent block. Today wins when both classes are on the same cell. */
.cal-day.cal-selected:not(.cal-today) {
box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--accent, var(--fg)) 65%, transparent);
background: color-mix(in srgb, var(--accent, var(--fg)) 12%, var(--bg));
border-radius: 8px;
}
.cal-day.cal-weekend:not(.cal-today) { background:color-mix(in srgb, var(--fg) 2.5%, var(--bg)); }
.cal-day.cal-empty-day::after {
content:'';
position:absolute;
left:8px;
right:8px;
bottom:8px;
height:1px;
background:color-mix(in srgb, var(--fg) 7%, transparent);
opacity:0.55;
pointer-events:none;
}
.cal-day.cal-other { opacity:0.34; }
.cal-event-row::after {
content:'';
width:3px;
height:11px;
margin-left:auto;
opacity:0;
flex:0 0 auto;
border-left:1px solid color-mix(in srgb, var(--fg) 34%, transparent);
border-right:1px solid color-mix(in srgb, var(--fg) 34%, transparent);
transition:opacity 0.15s;
}
.cal-event-row:hover::after { opacity:0.75; }
.cal-dragging { opacity:0.3; }
#cal-body.cal-events-full > :is(.cal-grid, .cal-wk-wrap) {
display: none;
}
#cal-body.cal-events-full > .cal-day-detail {
height: auto;
min-height: 0;
flex: 1 1 auto;
}
#cal-body.cal-calendar-full > .cal-day-detail {
display: none;
}
.cal-event-link-target {
animation: cal-event-link-target-flash 2.2s ease-out;
box-shadow:
inset 0 0 0 2px color-mix(in srgb, var(--accent, var(--red)) 88%, transparent),
0 0 0 3px color-mix(in srgb, var(--accent, var(--red)) 20%, transparent),
0 8px 20px color-mix(in srgb, var(--accent, var(--red)) 18%, transparent) !important;
}
@keyframes cal-event-link-target-flash {
0%, 28% {
filter: brightness(1.18) saturate(1.12);
transform: translateY(-1px);
}
100% {
filter: none;
transform: none;
}
}
.cal-event-loc a, .cal-event-time a { color:inherit; text-decoration:underline; text-decoration-color:color-mix(in srgb, currentColor 40%, transparent); }
.cal-event-loc a:hover, .cal-event-time a:hover { opacity:1; text-decoration-color:currentColor; }
#cal-settings > svg {
top: 3px !important;
}
.cal-wk-col.cal-wk-today .cal-wk-col-head { background: color-mix(in srgb, var(--accent, var(--red)) 12%, var(--bg)); }
.cal-wk-col.cal-wk-today .cal-wk-dn,
.cal-wk-col.cal-wk-today .cal-wk-dt { color: var(--accent, var(--red)); font-weight: 700; }
/* Sunday: a softer rest-day tint so the week edge reads visually, even
when today is mid-week. Falls back gracefully under the .cal-wk-today
rules above (today on a Sunday still gets the accent treatment). */
.cal-wk-col.cal-wk-sun .cal-wk-col-head { background: color-mix(in srgb, var(--fg) 7%, var(--bg)); }
.cal-wk-col.cal-wk-sun .cal-wk-grid { background: color-mix(in srgb, var(--fg) 2.5%, var(--bg)); }
.cal-wk-col.cal-wk-sun .cal-wk-allday { background: color-mix(in srgb, var(--fg) 3%, var(--bg)); }
.cal-wk-col.cal-wk-sun .cal-wk-dn,
.cal-wk-col.cal-wk-sun .cal-wk-dt { color: color-mix(in srgb, var(--fg) 60%, transparent); }
.cal-wk-dt { font-variant-numeric: tabular-nums; opacity: 0.5; }
.cal-wk-allday::-webkit-scrollbar { display: none; }
.cal-wk-now::before {
content: '';
position: absolute;
left: -3px;
top: -4px;
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--accent, var(--red, #f87171));
box-shadow:0 0 0 3px color-mix(in srgb, var(--accent, var(--red)) 18%, transparent);
}
.cal-wk-now::after {
content:'now';
position:absolute;
right:4px;
top:-10px;
padding:1px 5px;
border-radius:7px;
font-size:8px;
line-height:1.2;
font-weight:800;
text-transform:uppercase;
color:var(--bg);
background:var(--accent, var(--red));
box-shadow:0 1px 4px color-mix(in srgb, #000 22%, transparent);
}
@keyframes cal-wk-hover-label-reveal {
0%, 35% { opacity: 0; }
100% { opacity: 1; }
}
.cal-wk-block:hover .cal-wk-block-resize::after {
content: '';
position: absolute;
left: 30%;
right: 30%;
bottom: 1px;
height: 2px;
border-radius: 2px;
background: color-mix(in srgb, var(--fg) 35%, transparent);
}
.cal-wk-block::after {
content:'';
position:absolute;
top:6px;
right:5px;
width:3px;
height:14px;
opacity:0;
border-left:1px solid color-mix(in srgb, var(--fg) 35%, transparent);
border-right:1px solid color-mix(in srgb, var(--fg) 35%, transparent);
transition:opacity 0.15s;
}
.cal-wk-block:hover::after { opacity:0.75; }
.cal-wk-block-resize:hover::after {
background: color-mix(in srgb, var(--fg) 65%, transparent);
}
/* Body of a block while it's being repositioned: lifted shadow + above
siblings so the drop target reads clearly. */
.cal-wk-block-ghost {
z-index: 6 !important;
box-shadow: 0 4px 12px color-mix(in srgb, var(--fg) 24%, transparent) !important;
cursor: grabbing;
}
.cal-wk-block { cursor: grab; }
.cal-wk-block:active { cursor: grabbing; }
.cal-wk-slot-selected-label {
pointer-events: none;
}
.cal-wk-slot-resize::after {
content: '';
position: absolute;
left: 18%;
right: 18%;
top: 6px;
height: 3px;
border-radius: 2px;
background: var(--accent, var(--fg));
box-shadow: 0 0 0 2px color-mix(in srgb, var(--bg) 75%, transparent);
}
.cal-wk-col.cal-wk-slot-day-selected .cal-wk-col-head {
color: var(--accent, var(--fg));
box-shadow: inset 0 -2px 0 color-mix(in srgb, var(--accent, var(--fg)) 70%, transparent);
}
.cal-year-month:hover .cal-year-month-title { opacity:1; color:var(--accent); }
.cal-year-day { cursor:pointer; opacity:0.5; }
.cal-search-input.cal-day-search:focus { width:100%; }
.cal-agenda-day::before {
content:'';
position:absolute;
left:4px;
top:22px;
bottom:-12px;
width:1px;
background:color-mix(in srgb, var(--fg) 12%, transparent);
}
.cal-agenda-day::after {
content:'';
position:absolute;
left:1px;
top:12px;
width:7px;
height:7px;
border-radius:50%;
background:color-mix(in srgb, var(--fg) 22%, var(--bg));
box-shadow:0 0 0 3px var(--bg);
}
.cal-agenda-day.is-today::after { background:var(--accent, var(--red)); }
.cal-agenda-day.is-today .cal-agenda-date { opacity: 1; color: var(--accent, var(--red)); border-bottom-color: color-mix(in srgb, var(--accent, var(--red)) 35%, var(--border)); }
.cal-agenda-event:hover .cal-event-more,
.cal-event-item:hover .cal-event-more { opacity:0.6; }
button.cal-event-more {
background:transparent;
border:none;
color:var(--fg);
width:20px; height:20px;
cursor:pointer;
padding:0;
display:inline-flex;
align-items:center;
justify-content:center;
font-family:inherit;
flex-shrink:0;
margin-left:auto;
align-self:center;
transform:translateY(-4px);
transition:opacity 0.15s;
}
button.cal-event-more:hover { opacity:1 !important; }
/* Day-detail "+ New" button nudged down 2px. */
#cal-add-day { position: relative; top: 2px; }
/* Accent the native date/time picker glyphs. We recolor the webkit indicator
by masking an accent fill through a calendar/clock SVG (same technique as the
mono icon picker). accent-color covers the spinner parts where supported. */
.cal-form-bespoke input[type="date"],
.cal-form-bespoke input[type="time"],
.cal-form-bespoke input[type="datetime-local"] { accent-color: var(--accent, var(--red)); }
.cal-form-bespoke input[type="date"]::-webkit-datetime-edit-month-field,
.cal-form-bespoke input[type="date"]::-webkit-datetime-edit-day-field {
color: var(--accent, var(--red));
font-weight: 600;
}
.cal-form-bespoke input[type="date"]::-webkit-datetime-edit-year-field {
color: var(--fg);
font-weight: 400;
}
.cal-form-bespoke input[type="date"]::-webkit-calendar-picker-indicator,
.cal-form-bespoke input[type="time"]::-webkit-calendar-picker-indicator,
.cal-form-bespoke input[type="datetime-local"]::-webkit-calendar-picker-indicator {
background-color: var(--accent, var(--red));
background-image: none;
cursor: pointer;
width: 14px; height: 14px;
}
.cal-form-bespoke input[type="date"]::-webkit-calendar-picker-indicator,
.cal-form-bespoke input[type="datetime-local"]::-webkit-calendar-picker-indicator {
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2'/%3E%3Cline x1='16' y1='2' x2='16' y2='6'/%3E%3Cline x1='8' y1='2' x2='8' y2='6'/%3E%3Cline x1='3' y1='10' x2='21' y2='10'/%3E%3C/svg%3E") center/contain no-repeat;
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2'/%3E%3Cline x1='16' y1='2' x2='16' y2='6'/%3E%3Cline x1='8' y1='2' x2='8' y2='6'/%3E%3Cline x1='3' y1='10' x2='21' y2='10'/%3E%3C/svg%3E") center/contain no-repeat;
}
.cal-form-bespoke input[type="time"]::-webkit-calendar-picker-indicator {
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpolyline points='12 6 12 12 16 14'/%3E%3C/svg%3E") center/contain no-repeat;
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpolyline points='12 6 12 12 16 14'/%3E%3C/svg%3E") center/contain no-repeat;
}
/* Title underline + clock focus ring + primary button track --ev-color
when set; default to --accent / --fg otherwise. */
.cal-form-bespoke .cal-input.cal-hero-title:focus { border-bottom-color: var(--ev-color, var(--accent, color-mix(in srgb, var(--fg) 60%, transparent))); }
.cal-form-bespoke[style*="--ev-color"] .cal-input.cal-hero-title { border-bottom-color: color-mix(in srgb, var(--ev-color) 35%, var(--border)); }
.cal-form-bespoke .cal-hero-time:focus-visible,
.cal-form-bespoke .cal-hero-date:focus-visible { outline-color: var(--ev-color, var(--accent, color-mix(in srgb, var(--fg) 50%, transparent))); }
/* Custom-BG events paint the form card with the uploaded image. Make sure
the action row + buttons stay clearly readable on top of it. */
.cal-form-bespoke.cal-form-bg-image .cal-form-actions {
position: relative;
z-index: 2;
/* Solid backing strip so the Save/Cancel/Delete buttons don't fade into
the photo behind them. */
background: color-mix(in srgb, var(--panel) 92%, transparent);
margin: 8px -14px -14px;
padding: 10px 14px;
border-top: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
border-radius: 0 0 8px 8px;
}
.cal-form-today span { letter-spacing: 0.04em; text-transform: none; opacity: 0.85; font-weight: 500; }
.cal-hero-time .cal-hero-clock,
.cal-hero-time .cal-hero-ampm { line-height: 1; }
.cal-hero-time:focus-visible { outline: 2px solid var(--accent, color-mix(in srgb, var(--fg) 50%, transparent)); outline-offset: 2px; }
.cal-hero-date:focus-visible { outline: 2px solid var(--accent, color-mix(in srgb, var(--fg) 50%, transparent)); outline-offset: 2px; }
.cal-input.cal-hero-title:focus {
border-color: transparent;
border-bottom-color: var(--accent, color-mix(in srgb, var(--fg) 60%, transparent));
}
.cal-form-bespoke.is-expanded .cal-form-details {
max-height: 720px;
opacity: 1;
margin-top: 4px;
}
.cal-form-bespoke .cal-form-actions { margin-top: 8px; justify-content: flex-end; }
.cal-form-bespoke .cal-form-row { margin-bottom: 8px; }
.cal-loc-input-wrap > input { height: 100%; }
.cal-loc-input-wrap > input::placeholder { color: transparent; }
.cal-loc-hint svg { color: var(--accent, var(--red)); }
.cal-loc-input-wrap > input:placeholder-shown ~ .cal-loc-hint { display: inline-flex; }
.cal-loc-map.is-disabled { opacity: 0.3; pointer-events: none; cursor: default; }
select.cal-input { appearance:auto; }
textarea.cal-input { resize:vertical; font-family:inherit; }
button.cal-btn.cal-btn-danger { color:var(--accent, var(--red)); border-color:var(--accent, var(--red)); background:transparent; }
button.cal-btn.cal-btn-danger:hover { background:var(--accent, var(--red)); color:var(--bg); }
/* Brief outline flash on a note card when it's the target of a
#note-<id> link click from chat — same pattern as the cookbook
task flash, just scoped to .note-card. */
.note-card-flash {
animation: note-card-flash-anim 1.6s ease-out;
}
@keyframes note-card-flash-anim {
0% { box-shadow: 0 0 0 2px var(--accent, var(--red)); }
100% { box-shadow: 0 0 0 2px transparent; }
}