PLWL/renderer/style.css

806 lines
23 KiB
CSS

@font-face {
font-family: 'Nunito';
src: url('Nunito-Regular.ttf') format('truetype');
}
@font-face {
font-family: 'Nunito Black';
src: url('Nunito-Black.ttf') format('truetype');
}
:root {
--main-bg-color: rgb(20, 20, 20);
--panel-color: rgb(26, 26, 26);
--text-color: rgb(240, 240, 245);
--darker-text-color: rgb(160, 160, 170);
--light-border-color: rgba(255, 255, 255, 0.08);
--button-margin: 0.4rem;
--button-height: 2.4rem;
--icon-button-size: 3.4rem;
--border-width: 0.09rem;
--big-default: rgb(207, 207, 207);
--big-red: hsl(0, 68%, 52%);
--big-green: hsl(120, 60%, 55%);
--press-scale: scale(0.92);
--spring: cubic-bezier(0.175, 0.885, 0.32, 1.275);
--smooth: cubic-bezier(0.25, 1, 0.5, 1);
}
* {
font-size: 1rem;
box-sizing: border-box;
margin: 0;
padding: 0;
transition: all 0.2s ease;
color: var(--text-color);
font-family: "Nunito", sans-serif;
-webkit-tap-highlight-color: transparent;
scrollbar-width: none;
}
svg, svg * {
color: inherit;
}
.button span {
font-size: inherit;
color: inherit;
}
*::-webkit-scrollbar { display: none; width: 0; height: 0; }
*:focus { outline: none; box-shadow: none; }
html { font-size: max(15px, calc(100vw / 90)); }
body {
background-color: var(--main-bg-color);
overflow: hidden;
display: flex;
height: 100vh;
width: 100%;
}
main {
display: flex;
height: 100%;
width: 100%;
overflow: hidden;
}
sidebar {
display: flex;
flex-direction: column;
justify-content: space-between;
width: var(--icon-button-size);
min-width: var(--icon-button-size);
border-right: var(--border-width) solid var(--light-border-color);
padding: 0.6rem 0;
background: var(--panel-color);
}
sidebarelement { display: block; position: relative; }
sidebarelement indicator {
content: "";
position: absolute;
width: 0;
height: 2rem;
left: -0.08rem;
top: 50%;
transform: translateY(-50%);
border: 0.15rem solid transparent;
border-radius: 0.8rem;
}
sidebarelement.active indicator { border-color: var(--text-color); }
sidebarelement.active .icon-button { background: rgba(255, 255, 255, 0.05); }
.icon-button {
width: var(--icon-button-size);
height: var(--icon-button-size);
margin: var(--button-margin) auto;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
background: transparent;
border: var(--border-width) solid transparent;
border-radius: 0.8rem;
cursor: pointer;
backface-visibility: hidden;
transform: translateZ(0);
will-change: transform;
transform-origin: center center;
}
.icon-button:hover { background: rgba(255, 255, 255, 0.05); }
.icon-button:active { background: rgba(255, 255, 255, 0.1); transform: var(--press-scale); }
.icon-button svg { width: 55%; height: 55%; }
content {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
padding: 0 2rem 2rem;
}
.view { display: none; }
.view.active {
display: block;
animation: view-in 0.25s var(--spring);
}
@keyframes view-in {
0% { opacity: 0; transform: translateY(0.8rem); }
100% { opacity: 1; transform: translateY(0); }
}
.view-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
padding: 1.6rem 0 1rem;
gap: 1rem;
}
.view-header h1 {
font-family: 'Nunito Black', 'Nunito', sans-serif;
font-size: 1.8rem;
}
.header-actions { display: flex; gap: 0.6rem; align-items: center; }
.gc-head {
display: flex;
gap: 1rem;
align-items: flex-start;
}
.gc-head-main { flex: 1; }
.gc-head-main .field-label { margin-top: 0; }
.gc-icon-box {
width: 6rem;
height: 6rem;
border-radius: 0.9rem;
border: var(--border-width) solid var(--light-border-color);
background: rgba(255, 255, 255, 0.03);
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
flex-shrink: 0;
}
.gc-icon-box img {
width: 100%;
height: 100%;
object-fit: contain;
}
.gc-icon-placeholder {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: var(--darker-text-color);
opacity: 0.5;
}
.gc-icon-placeholder::before {
content: '';
width: 2.4rem;
height: 2.4rem;
background: currentColor;
-webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2'><rect x='3' y='3' width='18' height='18' rx='2'/><circle cx='8.5' cy='8.5' r='1.5'/><path d='M21 15l-5-5L5 21'/></svg>") center / contain no-repeat;
mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2'><rect x='3' y='3' width='18' height='18' rx='2'/><circle cx='8.5' cy='8.5' r='1.5'/><path d='M21 15l-5-5L5 21'/></svg>") center / contain no-repeat;
}
.icon-actions {
display: flex;
gap: 0.5rem;
margin-top: 0.4rem;
flex-wrap: wrap;
}
.subtitle { color: var(--darker-text-color); font-size: 0.9rem; margin-top: 0.2rem; }
/* "Do not close the app while this runs" - the one line on a sync popup the
user has to notice, so it stands out from the subtitles around it. */
.sync-warning { color: var(--big-red); margin-top: 0.6rem; }
.button {
height: var(--button-height);
padding: 0 1rem;
margin: var(--button-margin) 0;
border-radius: 0.8rem;
background-color: rgba(255, 255, 255, 0.05);
border: var(--border-width) solid var(--light-border-color);
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.4rem;
cursor: pointer;
white-space: nowrap;
/* only Regular and Black faces are bundled, so >=600 resolves to the same
bold rendering on every button; primary just adds the filled style */
font-weight: 700;
backface-visibility: hidden;
transform: translateZ(0);
will-change: transform;
transform-origin: center center;
}
.button:hover { background-color: rgba(255, 255, 255, 0.09); }
.button:active { background-color: rgba(255, 255, 255, 0.1); transform: var(--press-scale); }
.button svg {
width: 1.1rem;
height: 1.1rem;
flex-shrink: 0;
pointer-events: none;
color: inherit;
}
.button.primary {
background-color: var(--text-color);
color: var(--main-bg-color);
font-weight: 800;
border: none;
}
.button.primary:hover { filter: brightness(0.92); }
.button.primary:active { filter: brightness(0.85); transform: var(--press-scale); }
.button.primary svg { color: var(--main-bg-color); }
.button.danger {
color: var(--big-red);
border-color: rgba(255, 0, 0, 0.32);
}
.button.danger:hover { background-color: rgba(255, 0, 0, 0.09); border-color: var(--big-red); }
.button.danger:active { background-color: rgba(255, 0, 0, 0.14); transform: var(--press-scale); }
.button.danger svg { color: var(--big-red); }
.button.small { height: 2rem; padding: 0 0.7rem; font-size: 0.85rem; }
.button.small svg { width: 1.05rem; height: 1.05rem; }
.button span {
font-size: inherit;
color: inherit;
}
input, textarea {
width: 100%;
border-radius: 0.8rem;
background-color: rgba(255, 255, 255, 0.05);
border: var(--border-width) solid var(--light-border-color);
margin: var(--button-margin) 0;
padding: 0.4rem 0.7rem;
min-height: var(--button-height);
user-select: text !important;
cursor: text;
backface-visibility: hidden;
transform: translateZ(0);
will-change: transform;
transform-origin: center center;
}
input:hover, textarea:hover { background-color: rgba(255, 255, 255, 0.09); }
input:active, textarea:active { background-color: rgba(255, 255, 255, 0.1); }
input:focus, textarea:focus { border-color: rgba(255, 255, 255, 0.25); background-color: rgba(255, 255, 255, 0.1); }
textarea { resize: vertical; font-family: inherit; }
/* --- checkbox: styled to match the app (radius, borders, press feedback) --- */
.check-label {
display: flex;
align-items: center;
gap: 0.7rem;
margin-top: 1.1rem; /* extra breathing room below the custom-select */
margin-bottom: var(--button-margin);
cursor: pointer;
color: var(--darker-text-color);
user-select: none;
}
.check-label:hover { color: var(--text-color); }
input[type='checkbox'] {
-webkit-appearance: none;
appearance: none;
width: 1.7rem;
height: 1.7rem;
min-height: 0;
flex: 0 0 auto;
margin: 0;
padding: 0;
border-radius: 0.65rem;
background-color: rgba(255, 255, 255, 0.05);
border: var(--border-width) solid var(--light-border-color);
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
backface-visibility: hidden;
transform: translateZ(0);
transition: all 0.15s ease;
}
input[type='checkbox']:hover { background-color: rgba(255, 255, 255, 0.09); }
input[type='checkbox']:active { background-color: rgba(255, 255, 255, 0.1); transform: var(--press-scale); }
input[type='checkbox']:focus-visible { border-color: rgba(255, 255, 255, 0.25); }
input[type='checkbox']:checked {
background-color: var(--text-color);
border-color: var(--text-color);
}
input[type='checkbox']:checked::before {
content: '';
width: 0.95rem;
height: 0.95rem;
background: var(--main-bg-color);
-webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'><path d='M4 12.5l5.5 5.5L20 6.5'/></svg>") center / contain no-repeat;
mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'><path d='M4 12.5l5.5 5.5L20 6.5'/></svg>") center / contain no-repeat;
}
.check-label input[type='checkbox']:checked + span { color: var(--text-color); }
select {
display: none;
}
.custom-select {
position: relative;
border-radius: 0.8rem;
background-color: rgba(255, 255, 255, 0.05);
margin: var(--button-margin) 0;
height: var(--button-height);
display: flex;
cursor: pointer;
align-items: center;
border: var(--border-width) solid var(--light-border-color);
width: 100%;
user-select: none;
z-index: 2;
backface-visibility: hidden;
transform: translateZ(0);
will-change: transform;
transform-origin: center center;
}
.custom-select:hover { background-color: rgba(255, 255, 255, 0.09); }
.custom-select:active { background-color: rgba(255, 255, 255, 0.1); transform: var(--press-scale); }
.custom-select.wide { width: 100%; }
.custom-select-value {
padding: var(--button-margin) calc(var(--button-margin) * 2);
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
gap: 0.6rem;
}
.custom-select-value span {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.custom-select .chevron {
width: 1.1rem;
height: 1.1rem;
flex-shrink: 0;
margin-left: auto;
margin-right: 0.2rem;
opacity: 0.7;
transition: transform 0.2s ease;
}
.custom-select.open .chevron { transform: rotate(180deg); }
.custom-select.open {
border-color: rgba(255, 255, 255, 0.25);
z-index: 50;
transform: none;
}
.custom-select-options {
position: absolute;
top: calc(100% + 0.4rem);
left: 0;
width: 100%;
background-color: var(--main-bg-color);
border: var(--border-width) solid var(--light-border-color);
border-radius: 0.8rem;
max-height: 15rem;
overflow-y: auto;
z-index: 100;
opacity: 0;
visibility: hidden;
transform: translateY(-0.625rem);
transition: all 0.2s ease;
box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.5);
padding: 0.4rem;
pointer-events: none;
}
.custom-select.open .custom-select-options {
opacity: 1;
visibility: visible;
transform: translateY(0);
pointer-events: auto;
}
.custom-select-option {
padding: var(--button-margin) calc(var(--button-margin) * 2);
height: var(--button-height);
display: flex;
align-items: center;
border-radius: 0.8rem;
background-color: transparent;
cursor: pointer;
transition: background-color 0.2s;
text-align: left;
margin-bottom: 0.2rem;
width: 100%;
border: none;
font-weight: 500;
}
.custom-select-option:last-child { margin-bottom: 0; }
.custom-select-option:hover { background-color: rgba(255, 255, 255, 0.1); }
.custom-select-option.selected {
background-color: rgba(255, 255, 255, 0.15);
font-weight: bold;
}
.toolbar {
display: flex;
align-items: center;
gap: 0.6rem;
margin: 0.5rem 0 1rem;
}
.toolbar input { max-width: 22rem; }
.form-card {
background: var(--panel-color);
border: var(--border-width) solid var(--light-border-color);
border-radius: 1rem;
padding: 1.2rem 1.4rem;
margin-bottom: 1.2rem;
max-width: 46rem;
}
.form-card h2 {
font-size: 1.2rem;
font-family: 'Nunito Black', 'Nunito', sans-serif;
margin-bottom: 0.3rem;
}
.form-actions {
display: flex;
gap: 0.6rem;
justify-content: flex-end;
margin-top: 1rem;
}
.field-label {
display: block;
font-size: 0.85rem;
color: var(--darker-text-color);
margin-top: 0.9rem;
}
/* LAN syncing: generated id/code, shown as read-only machine values */
.code-row { display: flex; gap: 0.6rem; align-items: center; margin: var(--button-margin) 0; }
.code-row code {
flex: 1;
min-width: 0;
height: var(--button-height);
display: flex;
align-items: center;
padding: 0 0.8rem;
border-radius: 0.8rem;
background-color: rgba(255, 255, 255, 0.05);
border: var(--border-width) solid var(--light-border-color);
font-family: ui-monospace, 'DejaVu Sans Mono', monospace;
letter-spacing: 0.08em;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
user-select: text;
}
/* the reroll button shares the row with the code boxes, so it has to be just
as tall as them and carry the same font size (the shared .small button is
shorter and its 0.85rem text looks lost next to the code value) */
.code-row .button { height: var(--button-height); font-size: 1rem; }
.code-row #ls-sync-code { font-size: 1.25rem; font-weight: 700; letter-spacing: 0.3em; }
.check-group { display: flex; flex-direction: column; }
.check-group .check-label { margin-top: 0.2rem; margin-bottom: 0.2rem; }
.path-row { display: flex; gap: 0.6rem; align-items: center; }
.path-row input { flex: 1; min-width: 0; }
.path-row .custom-select { flex: 1; margin: var(--button-margin) 0; }
.game-list {
display: flex;
flex-wrap: wrap;
gap: 0.9rem;
margin: 1rem 0;
}
.game-card {
position: relative;
cursor: grab;
flex: 1 1 17rem;
min-width: 17rem;
max-width: 24rem;
background: var(--panel-color);
border: var(--border-width) solid var(--light-border-color);
border-radius: 1rem;
padding: 1rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
animation: view-in 0.3s var(--spring);
}
.game-icon {
width: 4rem;
height: 4rem;
object-fit: contain;
border-radius: 0.6rem;
background: rgba(255, 255, 255, 0.03);
align-self: flex-start;
}
.game-icon.placeholder {
display: flex;
align-items: center;
justify-content: center;
color: var(--darker-text-color);
opacity: 0.45;
}
.game-icon.placeholder::before {
content: '';
width: 2rem;
height: 2rem;
background: currentColor;
-webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2'><rect x='3' y='3' width='18' height='18' rx='2'/><circle cx='8.5' cy='8.5' r='1.5'/><path d='M21 15l-5-5L5 21'/></svg>") center / contain no-repeat;
mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2'><rect x='3' y='3' width='18' height='18' rx='2'/><circle cx='8.5' cy='8.5' r='1.5'/><path d='M21 15l-5-5L5 21'/></svg>") center / contain no-repeat;
}
.game-card:hover { border-color: rgba(255, 255, 255, 0.2); }
/* drag & drop reordering: the dragged card fades, the hovered one shows on
which side the dragged card will land */
.game-card.dragging { opacity: 0.4; }
.game-card.drop-before::after,
.game-card.drop-after::after {
content: '';
position: absolute;
top: 0.5rem;
bottom: 0.5rem;
width: 0.22rem;
border-radius: 0.11rem;
background: var(--text-color);
}
.game-card.drop-before::after { left: -0.5rem; }
.game-card.drop-after::after { right: -0.5rem; }
.game-card h3 {
font-size: 1.05rem;
font-family: 'Nunito Black', 'Nunito', sans-serif;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.game-card .meta {
color: var(--darker-text-color);
font-size: 0.8rem;
display: flex;
gap: 0.4rem;
flex-wrap: wrap;
}
.badge {
font-size: 0.72rem;
padding: 0.1rem 0.5rem;
border-radius: 0.6rem;
border: var(--border-width) solid var(--light-border-color);
color: var(--darker-text-color);
}
.badge.primary { color: var(--text-color); border-color: rgba(255, 255, 255, 0.35); }
.game-card .actions {
display: flex;
gap: 0.5rem;
margin-top: 0.4rem;
}
.game-card .actions .button { flex: 1; margin: 0; padding: 0; }
.game-card .actions .button[data-act=kill] { font-size: 0.78rem; padding: 0 0.3rem; }
.list { display: flex; flex-direction: column; gap: 0.6rem; margin: 1rem 0; }
.list-row {
display: flex;
align-items: center;
gap: 1rem;
background: var(--panel-color);
border: var(--border-width) solid var(--light-border-color);
border-radius: 0.9rem;
padding: 0.7rem 1.1rem;
animation: view-in 0.25s var(--spring);
}
.list-row .grow { flex: 1; min-width: 0; }
.list-row .title { font-weight: 700; }
.list-row .sub {
color: var(--darker-text-color);
font-size: 0.8rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.list-row .actions { display: flex; gap: 0.5rem; }
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
gap: 0.6rem;
padding: 4rem 1rem;
max-width: 34rem;
margin: 0 auto;
}
.empty-title { font-family: 'Nunito Black', 'Nunito', sans-serif; font-size: 1.4rem; }
.empty-hint { color: var(--darker-text-color); }
.overrides { display: flex; flex-direction: column; gap: 0.4rem; }
.override-row { display: flex; gap: 0.5rem; align-items: center; }
.override-row input { margin: 0.1rem 0; }
.override-row input.dll { max-width: 12rem; }
.override-row .custom-select { width: 10rem; margin: 0.1rem 0; flex-shrink: 0; }
.override-row .custom-select-options { min-width: 10rem; }
.override-row .button svg { width: 1rem; height: 1rem; }
.tools-grid {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin: 0.6rem 0 1rem;
}
.modal-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0);
backdrop-filter: blur(0);
-webkit-backdrop-filter: blur(0);
display: flex;
align-items: center;
justify-content: center;
z-index: 100;
transition: background 0.3s ease, backdrop-filter 0.3s ease, -webkit-backdrop-filter 0.3s ease;
}
.modal-overlay.show {
background: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(0.25rem);
-webkit-backdrop-filter: blur(0.25rem);
}
.modal {
background: var(--panel-color);
border: var(--border-width) solid var(--light-border-color);
border-radius: 0.9rem;
padding: 1.4rem 1.6rem;
max-width: 30rem;
width: 90%;
display: flex;
flex-direction: column;
gap: 0.8rem;
box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.5);
opacity: 0;
transform: scale(0.6);
transition: all 0.2s var(--spring);
position: relative;
}
.modal-overlay.show .modal {
opacity: 1;
transform: scale(1);
}
.modal.closing {
opacity: 0;
transform: scale(0.8);
transition: all 0.15s ease;
}
/* --- animated content swap (import mode -> prefix picker etc.) -------------- */
/* During the morph the box keeps a fixed pixel height and glides to the new
content's height; overflow is clipped ONLY then (a permanent clip would
cut off open custom-select dropdowns). */
.modal.resizing {
overflow: hidden;
transition: height 0.22s var(--spring);
will-change: height;
}
/* Opaque snapshot of the OLD content laid over the new one; fades out while
the box glides -> content dissolves from old to new (like switching tabs). */
.modal .modal-ghost {
position: absolute;
inset: 1.4rem 1.6rem; /* overlay the box's own padding area */
z-index: 5;
pointer-events: none;
background: var(--panel-color);
display: flex;
flex-direction: column;
gap: 0.8rem; /* mirror the modal's layout so the snapshot aligns 1:1 */
opacity: 1;
}
.modal.swapping .modal-ghost {
transition: opacity 0.18s ease;
opacity: 0;
}
/* new content fades in under the fading-out ghost -> real crossfade */
.modal.swapping > :not(.modal-ghost) {
animation: modal-content-in 0.2s ease;
}
@keyframes modal-content-in {
from { opacity: 0; }
to { opacity: 1; }
}
.modal h2 {
font-family: 'Nunito Black', 'Nunito', sans-serif;
font-size: 1.2rem;
}
.modal p { color: var(--darker-text-color); white-space: pre-line; }
.modal .modal-actions {
display: flex;
gap: 0.6rem;
justify-content: flex-end;
margin-top: 0.6rem;
}
.modal .mode-buttons { display: flex; flex-direction: column; gap: 0.6rem; }
.modal .mode-buttons .button { justify-content: flex-start; }
/* --- "Open with PLWL" picker ---------------------------------------------- */
.modal .openwith-file-row {
word-break: break-all;
margin-top: -0.3rem;
}
.modal .openwith-file-row code {
font-family: inherit;
color: var(--text-color);
background-color: rgba(255, 255, 255, 0.06);
border-radius: 0.4rem;
padding: 0.1rem 0.4rem;
}
.button:disabled {
opacity: 0.45;
pointer-events: none;
}
/* --- form fields inside modals (shared by every modal with inputs) --------- */
/* .modal is a flex column with gap: 0.8rem. That gap ADDS to the field
margins reused from the config forms (.field-label margin-top + control
margin), so label->control spacing in modals ended up ~3x bigger than in
.form-card (where block-layout margins collapse). Inside modals the gap
owns the group spacing, so the repeated field styles collapse to the same
rhythm as the config forms: label hugs its control (~0.4rem), groups stay
0.8rem apart. */
.modal .field-label { margin-top: 0; margin-bottom: -0.4rem; }
.modal .custom-select,
.modal input,
.modal textarea { margin: 0; }
.toasts {
position: fixed;
bottom: 1rem;
right: 1rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
z-index: 200;
}
.toast {
background: var(--panel-color);
border: var(--border-width) solid var(--light-border-color);
border-radius: 0.8rem;
padding: 0.6rem 1rem;
max-width: min(24rem, calc(100vw - 2rem));
box-shadow: 0 0.5rem 2rem rgba(0, 0, 0, 0.4);
opacity: 0;
transform: translateX(1.5rem);
white-space: normal;
overflow-wrap: anywhere;
word-break: break-word;
min-width: 0;
transition:
opacity 0.2s ease,
transform 0.3s ease-out,
background-color 0.15s ease;
}
.toast.error { border-color: var(--big-red); }
.toast.success { border-color: var(--big-green); }
.toast.show {
opacity: 1;
transform: translateX(0);
}
.toast.hide {
opacity: 0;
transform: translateX(1rem);
}
.progress-row {
display: flex;
align-items: center;
gap: 0.6rem;
font-size: 0.85rem;
color: var(--darker-text-color);
}
progress {
width: 100%;
height: 0.5rem;
border: none;
border-radius: 0.4rem;
overflow: hidden;
background: rgba(255, 255, 255, 0.08);
-webkit-appearance: none;
}
.progress-pct {
min-width: 2.6rem;
text-align: right;
font-variant-numeric: tabular-nums;
}
progress::-webkit-progress-bar { background: rgba(255, 255, 255, 0.08); border-radius: 0.4rem; }
progress::-webkit-progress-value { background: var(--text-color); border-radius: 0.4rem; }