feat(notifications): add overlay notifications with position config (#110)

This commit is contained in:
2026-06-10 22:46:52 -07:00
committed by GitHub
parent c09d009a3e
commit 7be1843c41
177 changed files with 7524 additions and 440 deletions
+651 -1
View File
@@ -146,6 +146,656 @@ body:focus-visible,
transform: translateY(0);
}
/* Follow the configured notification position (default stays top-right). */
.overlay-error-toast.position-top-left {
left: 16px;
right: auto;
}
.overlay-error-toast.position-top {
left: 50%;
right: auto;
transform: translate(-50%, -6px);
}
.overlay-error-toast.position-top:not(.hidden) {
transform: translate(-50%, 0);
}
.overlay-error-toast.position-top-right {
left: auto;
right: 16px;
}
.overlay-notification-stack {
position: absolute;
top: 16px;
width: min(420px, calc(100vw - 32px));
display: flex;
flex-direction: column;
gap: 8px;
pointer-events: auto;
z-index: 2147483647 !important;
}
.overlay-notification-stack.position-top-left {
left: 16px;
right: auto;
transform: none;
}
.overlay-notification-stack.position-top {
left: 50%;
right: auto;
transform: translateX(-50%);
}
.overlay-notification-stack.position-top-right {
left: auto;
right: 16px;
transform: none;
}
.overlay-notification-card {
/* Accent color is overridden per variant and drives the icon and border tint. */
--overlay-notification-accent: var(--ctp-blue);
position: relative;
display: grid;
grid-template-columns: 22px minmax(0, 1fr) 22px;
gap: 12px;
align-items: start;
min-height: 72px;
padding: 16px;
border-radius: 12px;
border: 1px solid color-mix(in srgb, var(--overlay-notification-accent) 45%, var(--ctp-surface1));
background: var(--ctp-base);
box-shadow: 0 12px 28px -12px rgba(24, 25, 38, 0.7);
color: var(--ctp-text);
overflow: hidden;
}
/* Direction-aware enter/exit — slide in from the stack's anchored edge, slide back out. */
.overlay-notification-card.entering {
animation: overlay-notification-enter-right 240ms cubic-bezier(0.21, 1.02, 0.73, 1) both;
}
.overlay-notification-card.leaving {
pointer-events: none;
animation: overlay-notification-leave-right 190ms cubic-bezier(0.55, 0.06, 0.68, 0.19) both;
}
.overlay-notification-stack.position-top-left .overlay-notification-card.entering {
animation-name: overlay-notification-enter-left;
}
.overlay-notification-stack.position-top-left .overlay-notification-card.leaving {
animation-name: overlay-notification-leave-left;
}
.overlay-notification-stack.position-top .overlay-notification-card.entering {
animation-name: overlay-notification-enter-top;
}
.overlay-notification-stack.position-top .overlay-notification-card.leaving {
animation-name: overlay-notification-leave-top;
}
.overlay-notification-card.info {
--overlay-notification-accent: var(--ctp-blue);
}
.overlay-notification-card.progress {
--overlay-notification-accent: var(--ctp-sky);
}
.overlay-notification-card.success {
--overlay-notification-accent: var(--ctp-green);
}
.overlay-notification-card.warning {
--overlay-notification-accent: var(--ctp-yellow);
}
.overlay-notification-card.error {
--overlay-notification-accent: var(--ctp-red);
}
.overlay-notification-card.has-image {
/* Reserve a real track for the thumbnail so it never overlaps the text.
minmax(0, 100px) lets the column shrink the image on narrow notifications
instead of letting it spill into the content column. */
grid-template-columns: minmax(0, 100px) minmax(0, 1fr) 22px;
min-height: 88px;
}
.overlay-notification-image {
width: 100%;
max-width: 100px;
aspect-ratio: 100 / 56;
height: auto;
align-self: center;
display: block;
border-radius: 7px;
border: 1px solid color-mix(in srgb, var(--overlay-notification-accent) 28%, var(--ctp-surface2));
background: var(--ctp-crust);
object-fit: cover;
}
.overlay-notification-icon {
width: 22px;
height: 22px;
align-self: center;
display: grid;
place-items: center;
border-radius: 7px;
background: color-mix(in srgb, var(--overlay-notification-accent) 16%, transparent);
color: var(--overlay-notification-accent);
font-size: 12px;
font-weight: 900;
line-height: 1;
}
.overlay-notification-card.info .overlay-notification-icon::before {
content: 'i';
font-family: Georgia, 'Times New Roman', serif;
font-style: italic;
}
.overlay-notification-card.success .overlay-notification-icon::before {
content: '\2713';
}
.overlay-notification-card.warning .overlay-notification-icon::before {
content: '!';
}
.overlay-notification-card.error .overlay-notification-icon::before {
content: '\2715';
font-size: 11px;
}
.overlay-notification-card.progress .overlay-notification-icon::before {
content: '';
width: 13px;
height: 13px;
border-radius: 50%;
border: 2px solid color-mix(in srgb, var(--overlay-notification-accent) 28%, transparent);
border-top-color: var(--overlay-notification-accent);
animation: overlay-notification-spin 0.75s linear infinite;
}
.overlay-notification-content {
min-width: 0;
padding-top: 1px;
}
.overlay-notification-title {
color: var(--ctp-text);
font-size: 13px;
font-weight: 700;
line-height: 1.3;
letter-spacing: 0.1px;
}
.overlay-notification-body {
margin-top: 4px;
color: var(--ctp-subtext0);
font-size: 12px;
font-weight: 500;
line-height: 1.4;
overflow-wrap: anywhere;
}
.overlay-notification-actions {
display: flex;
flex-wrap: wrap;
gap: 7px;
margin-top: 11px;
}
.overlay-notification-action {
min-height: 27px;
padding: 4px 11px;
border-radius: 7px;
border: 1px solid color-mix(in srgb, var(--overlay-notification-accent) 35%, var(--ctp-surface2));
background: color-mix(in srgb, var(--overlay-notification-accent) 12%, var(--ctp-surface0));
color: var(--ctp-text);
font: inherit;
font-size: 12px;
font-weight: 600;
cursor: pointer;
transition:
background 120ms ease,
border-color 120ms ease;
}
.overlay-notification-action:hover {
border-color: var(--overlay-notification-accent);
background: color-mix(in srgb, var(--overlay-notification-accent) 24%, var(--ctp-surface0));
}
.overlay-notification-close {
width: 22px;
height: 22px;
align-self: start;
border: none;
border-radius: 6px;
background: transparent;
color: var(--ctp-overlay1);
font: inherit;
font-size: 16px;
line-height: 1;
cursor: pointer;
transition:
background 120ms ease,
color 120ms ease;
}
.overlay-notification-close:hover {
background: color-mix(in srgb, var(--ctp-red) 18%, transparent);
color: var(--ctp-red);
}
@keyframes overlay-notification-enter-right {
from {
opacity: 0;
transform: translateX(28px) scale(0.96);
}
to {
opacity: 1;
transform: translateX(0) scale(1);
}
}
@keyframes overlay-notification-enter-left {
from {
opacity: 0;
transform: translateX(-28px) scale(0.96);
}
to {
opacity: 1;
transform: translateX(0) scale(1);
}
}
@keyframes overlay-notification-enter-top {
from {
opacity: 0;
transform: translateY(-16px) scale(0.96);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
@keyframes overlay-notification-leave-right {
from {
opacity: 1;
transform: translateX(0) scale(1);
}
to {
opacity: 0;
transform: translateX(28px) scale(0.94);
}
}
@keyframes overlay-notification-leave-left {
from {
opacity: 1;
transform: translateX(0) scale(1);
}
to {
opacity: 0;
transform: translateX(-28px) scale(0.94);
}
}
@keyframes overlay-notification-leave-top {
from {
opacity: 1;
transform: translateY(0) scale(1);
}
to {
opacity: 0;
transform: translateY(-14px) scale(0.94);
}
}
@media (prefers-reduced-motion: reduce) {
.overlay-notification-card.entering,
.overlay-notification-card.leaving {
animation-duration: 1ms;
}
}
@keyframes overlay-notification-spin {
to {
transform: rotate(360deg);
}
}
/* Notification history panel — slides in from the same edge the notifications use. */
.notification-history {
--notification-history-width: min(380px, calc(100vw - 24px));
position: absolute;
top: 0;
bottom: 0;
width: var(--notification-history-width);
display: flex;
flex-direction: column;
background: color-mix(in srgb, var(--ctp-mantle) 94%, transparent);
border: 1px solid var(--ctp-surface0);
box-shadow: 0 18px 48px -18px rgba(24, 25, 38, 0.85);
color: var(--ctp-text);
pointer-events: auto;
z-index: 2147483646;
opacity: 0;
visibility: hidden;
transition:
transform 240ms cubic-bezier(0.21, 1.02, 0.73, 1),
opacity 200ms ease,
visibility 0s linear 240ms;
}
.notification-history.side-left {
left: 0;
right: auto;
border-left: none;
border-top-right-radius: 14px;
border-bottom-right-radius: 14px;
transform: translateX(-104%);
}
.notification-history.side-right {
left: auto;
right: 0;
border-right: none;
border-top-left-radius: 14px;
border-bottom-left-radius: 14px;
transform: translateX(104%);
}
.notification-history.open {
opacity: 1;
visibility: visible;
transform: translateX(0);
transition:
transform 260ms cubic-bezier(0.21, 1.02, 0.73, 1),
opacity 200ms ease,
visibility 0s linear 0s;
}
.notification-history-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 16px 18px;
border-bottom: 1px solid var(--ctp-surface0);
background: color-mix(in srgb, var(--ctp-crust) 60%, transparent);
}
.notification-history-title {
font-size: 14px;
font-weight: 800;
letter-spacing: 0.2px;
color: var(--ctp-lavender);
}
.notification-history-header-actions {
display: flex;
align-items: center;
gap: 8px;
}
.notification-history-clear {
padding: 5px 12px;
border-radius: 8px;
border: 1px solid color-mix(in srgb, var(--ctp-mauve) 38%, var(--ctp-surface1));
background: color-mix(in srgb, var(--ctp-mauve) 14%, var(--ctp-surface0));
color: var(--ctp-text);
font: inherit;
font-size: 12px;
font-weight: 600;
cursor: pointer;
transition:
background 120ms ease,
border-color 120ms ease,
opacity 120ms ease;
}
.notification-history-clear:hover:not(:disabled) {
border-color: var(--ctp-mauve);
background: color-mix(in srgb, var(--ctp-mauve) 26%, var(--ctp-surface0));
}
.notification-history-clear:disabled {
opacity: 0.4;
cursor: default;
}
.notification-history-close {
width: 26px;
height: 26px;
display: grid;
place-items: center;
border: none;
border-radius: 7px;
background: transparent;
color: var(--ctp-overlay1);
font: inherit;
font-size: 18px;
line-height: 1;
cursor: pointer;
transition:
background 120ms ease,
color 120ms ease;
}
.notification-history-close:hover {
background: color-mix(in srgb, var(--ctp-red) 18%, transparent);
color: var(--ctp-red);
}
.notification-history-body {
position: relative;
flex: 1;
min-height: 0;
overflow-y: auto;
padding: 12px;
scrollbar-width: thin;
scrollbar-color: var(--ctp-surface2) transparent;
}
.notification-history-body::-webkit-scrollbar {
width: 8px;
}
.notification-history-body::-webkit-scrollbar-thumb {
background: var(--ctp-surface1);
border-radius: 8px;
}
.notification-history-list {
list-style: none;
display: flex;
flex-direction: column;
gap: 8px;
margin: 0;
padding: 0;
}
.notification-history-item {
--notification-history-accent: var(--ctp-blue);
position: relative;
display: grid;
grid-template-columns: 4px auto minmax(0, 1fr) 22px;
gap: 10px;
align-items: start;
padding: 11px 12px;
border-radius: 10px;
border: 1px solid var(--ctp-surface0);
background: var(--ctp-base);
}
.notification-history-item::before {
content: '';
align-self: stretch;
border-radius: 4px;
background: var(--notification-history-accent);
}
.notification-history-item.info {
--notification-history-accent: var(--ctp-blue);
}
.notification-history-item.progress {
--notification-history-accent: var(--ctp-sky);
}
.notification-history-item.success {
--notification-history-accent: var(--ctp-green);
}
.notification-history-item.warning {
--notification-history-accent: var(--ctp-yellow);
}
.notification-history-item.error {
--notification-history-accent: var(--ctp-red);
}
.notification-history-thumb {
width: 56px;
aspect-ratio: 100 / 56;
height: auto;
align-self: center;
border-radius: 6px;
border: 1px solid color-mix(in srgb, var(--notification-history-accent) 28%, var(--ctp-surface2));
background: var(--ctp-crust);
object-fit: cover;
}
.notification-history-icon {
width: 10px;
height: 10px;
align-self: center;
border-radius: 50%;
background: var(--notification-history-accent);
}
.notification-history-content {
min-width: 0;
}
.notification-history-item-title {
font-size: 13px;
font-weight: 700;
line-height: 1.3;
color: var(--ctp-text);
}
.notification-history-item-body {
margin-top: 3px;
font-size: 12px;
font-weight: 500;
line-height: 1.4;
color: var(--ctp-subtext0);
overflow-wrap: anywhere;
}
.notification-history-time {
display: block;
margin-top: 5px;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.3px;
color: var(--ctp-overlay1);
}
.notification-history-actions {
display: flex;
flex-wrap: wrap;
gap: 7px;
margin-top: 9px;
}
.notification-history-action {
min-height: 24px;
max-width: 100%;
padding: 4px 9px;
border: 1px solid color-mix(in srgb, var(--notification-history-accent) 38%, var(--ctp-surface2));
border-radius: 6px;
background: color-mix(in srgb, var(--notification-history-accent) 18%, var(--ctp-surface0));
color: var(--ctp-text);
font: inherit;
font-size: 11px;
font-weight: 700;
line-height: 1.2;
overflow-wrap: anywhere;
cursor: pointer;
transition:
background 120ms ease,
border-color 120ms ease,
color 120ms ease;
}
.notification-history-action:hover {
border-color: color-mix(in srgb, var(--notification-history-accent) 70%, var(--ctp-surface2));
background: color-mix(in srgb, var(--notification-history-accent) 28%, var(--ctp-surface0));
}
.notification-history-remove {
width: 22px;
height: 22px;
align-self: start;
border: none;
border-radius: 6px;
background: transparent;
color: var(--ctp-overlay1);
font: inherit;
font-size: 15px;
line-height: 1;
cursor: pointer;
transition:
background 120ms ease,
color 120ms ease;
}
.notification-history-remove:hover {
background: color-mix(in srgb, var(--ctp-red) 18%, transparent);
color: var(--ctp-red);
}
.notification-history-empty {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
min-height: 96px;
padding: 24px;
text-align: center;
font-size: 13px;
font-weight: 500;
color: var(--ctp-overlay0);
}
.notification-history-empty.hidden {
display: none;
}
@media (prefers-reduced-motion: reduce) {
.notification-history {
transition-duration: 1ms;
}
}
.modal {
position: absolute;
inset: 0;
@@ -1282,7 +1932,7 @@ iframe.yomitan-popup,
iframe[id^='yomitan-popup'],
[data-subminer-yomitan-popup-host='true'] {
pointer-events: auto !important;
z-index: 2147483647 !important;
z-index: 2147483645;
}
.kiku-info-text {