/* ── Markdown prose ── */ .md p { margin: 0 0 0.85em; } .md p:last-child { margin-bottom: 0; } .md a { color: var(--color-accent); text-decoration: underline; text-underline-offset: 2px; } .md a:hover { text-decoration: none; } .md strong { font-weight: 700; } .md em { font-style: italic; } .md code { font-family: var(--font-mono, monospace); background: var(--color-bg); border: 1px solid var(--color-border); border-radius: 3px; padding: 0.1em 0.35em; font-size: 0.88em; } .md pre { background: var(--color-bg); border: 1px solid var(--color-border); border-radius: 8px; padding: 0.75rem 1rem; overflow-x: auto; margin: 0.6em 0; } .md pre code { background: none; border: none; padding: 0; } .md ul, .md ol { padding-left: 1.5em; margin: 0.4em 0; } .md li { margin: 0.15em 0; } .md blockquote { border-left: 3px solid var(--color-border); margin: 0.5em 0; padding: 0.2em 0.75em; opacity: 0.75; } .md h1, .md h2, .md h3, .md h4, .md h5, .md h6 { margin: 0.6em 0 0.2em; font-weight: 700; line-height: 1.25; } .md img { max-width: 100%; height: auto; display: block; } /* Compact / card mode: strip vertical spacing */ .md--inline p, .md--inline ul, .md--inline ol, .md--inline pre, .md--inline blockquote { margin: 0; } .md--inline li { margin: 0; } .md--inline ul, .md--inline ol { padding-left: 1.2em; } /* ── Dump detail page ── */ .dump-detail { display: flex; flex-direction: column; gap: 1rem; } .dump-post-header { display: flex; flex-direction: column; gap: 1rem; padding: 1.25rem; margin-bottom: 3px; background: var(--color-surface); border-radius: 12px 12px 0 0; } .dump-header-block { display: flex; align-items: center; gap: 1rem; } .dump-header-left { display: flex; flex-direction: column; align-items: center; gap: 0.5rem; flex-shrink: 0; } .dump-header-right { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.3rem; } .dump-title { margin: 0; font-size: 1.5rem; font-weight: 700; line-height: 1.25; word-break: break-word; } .dump-title-view { position: relative; display: inline-flex; align-items: center; gap: 0.4rem; border-radius: 6px; max-width: 100%; /* Padding gives the hover box breathing room (incl. right of the pencil); the matching negative margin keeps the title's visual position unchanged. */ padding: 0.1rem 0.45rem; margin: -0.1rem -0.45rem; } .dump-title-view--editable { cursor: pointer; } .dump-title-view--editable:hover { background: var(--color-surface); } .dump-title-edit-btn { font-size: 0.85rem; line-height: 1; flex-shrink: 0; color: var(--color-text-muted); opacity: 0; transition: opacity 0.1s; pointer-events: none; } .dump-title-view--editable:hover .dump-title-edit-btn { opacity: 1; } .dump-title-editor { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; } /* Shared chrome for inline-edit inputs (the editors that live outside `.form`). Each rule below adds only its own size / weight / width deltas. */ .dump-title-input, .profile-email-input, .playlist-edit-input, .playlist-edit-textarea { border: 2px solid var(--color-border); border-radius: 8px; background: var(--color-bg); color: var(--color-text); font-family: inherit; outline: none; transition: border-color 0.2s; } .dump-title-input:focus, .profile-email-input:focus, .playlist-edit-input:focus, .playlist-edit-textarea:focus { border-color: var(--color-accent); } .dump-title-input { flex: 1; min-width: 12rem; padding: 0.4rem 0.6rem; font-size: 1.25rem; font-weight: 700; } .dump-title-error { width: 100%; margin: 0; font-size: 0.85rem; color: var(--color-danger); } /* On the narrowest viewports the fixed-width action column squeezes the title into a sliver. Stack the block: title takes the full width on top, the vote/playlist actions sit in a row beneath it. */ @media (max-width: 512px) { .dump-header-block { flex-direction: column; align-items: stretch; } .dump-header-left { flex-direction: row; align-items: center; order: 2; } .dump-header-right { order: 1; } } .dump-comment { font-size: 1.05rem; line-height: 1.72; opacity: 0.85; border-left: 3px solid var(--color-accent); margin: 0; padding: 0.5rem 1rem; word-break: break-word; } .dump-rich-content { padding: 1.25rem; background: var(--color-surface); border-radius: 0 0 12px 12px; } .dump-url-link { display: inline-block; word-break: break-all; padding: 1rem; border: 2px solid var(--color-border); border-radius: 8px; width: 100%; box-sizing: border-box; } .dump-actions { display: flex; align-items: center; gap: 0.75rem; } .dump-actions a { color: var(--color-text); text-decoration: none; font-size: 0.9rem; opacity: 0.7; transition: opacity 0.15s, color 0.15s; } .dump-actions a:hover { opacity: 1; color: var(--color-accent); } .dump-actions a:last-child { margin-left: auto; } /* ── Forms ── */ /* Unified form system: `.form` container + `.form-field` / `.form-label` / `.form-hint` building blocks, shared by every form. */ .form { display: flex; flex-direction: column; gap: 1rem; } .form-field { display: flex; flex-direction: column; gap: 0.4rem; } .form-label { font-size: 0.85rem; font-weight: 600; opacity: 0.6; } /* Label paired with an inline action (e.g. a "reset to default" button). */ .form-label-row { display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; } .form-label-action { background: none; border: none; padding: 0; cursor: pointer; font-size: 0.8rem; font-weight: 600; color: var(--color-text); opacity: 0.6; } .form-label-action:hover:not(:disabled) { opacity: 1; } .form-label-action:disabled { opacity: 0.3; cursor: default; } .form input, .form textarea { width: 100%; box-sizing: border-box; padding: 0.65rem 1rem; border-radius: 8px; border: 2px solid var(--color-border); background-color: var(--color-bg); color: var(--color-text); font-size: 1rem; font-family: var(--font-body); font-optical-sizing: auto; font-weight: 400; font-style: normal; line-height: 1.5; transition: border-color 0.2s, box-shadow 0.2s, background-color 0.2s; outline: none; } /* A submit/secondary button placed as a direct child of `.form` (the auth forms, which have no action row) spans the full width like the inputs. Buttons inside `.form-actions` keep their intrinsic width. */ .form > .btn-primary, .form > .btn-secondary, .form > .btn-danger { width: 100%; } .form textarea { resize: vertical; } .form input:hover, .form textarea:hover { border-color: color-mix( in srgb, var(--color-accent) 45%, var(--color-border) 55% ); } .form input:focus, .form textarea:focus { border-color: var(--color-accent); background-color: color-mix( in srgb, var(--color-accent) 4%, var(--color-bg) 96% ); box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-accent) 18%, transparent); } .form input:disabled, .form textarea:disabled { opacity: 0.6; } .form-hint { font-size: 0.8rem; opacity: 0.7; margin-top: 0.1rem; } .form-hint--error { color: var(--color-error, #c0392b); opacity: 1; } .form-success { color: var(--color-success, green); margin: 0; } .dump-create-title-row { display: flex; align-items: center; gap: 0.5rem; } .dump-create-title-row input { flex: 1; } .dump-create-title-edit-btn { background: none; border: none; cursor: pointer; font-size: 1rem; line-height: 1; color: var(--color-muted); border-radius: 4px; padding: 0.4rem; transition: color 0.15s; } .dump-create-title-edit-btn:hover { color: var(--color-text); } .dump-create-title { margin: 0; font-size: 1.4rem; font-weight: 700; } /* ── Mode toggle — segmented control ── */ .visibility-toggle { display: flex; background: var(--color-bg); border: 1.5px solid var(--color-border); border-radius: 9px; padding: 3px; gap: 2px; } .visibility-toggle button { flex: 1; padding: 0.3rem 0.9rem; border-radius: 6px; border: none; background: transparent; color: var(--color-text); cursor: pointer; font-size: 0.88rem; font-weight: 600; transition: background 0.15s, color 0.15s; white-space: nowrap; } .visibility-toggle button.active { background: var(--color-accent); color: var(--color-on-accent); } .visibility-toggle button:not(.active):hover { background: color-mix(in srgb, var(--color-accent) 12%, transparent); } /* ── FileDropZone ── */ .fdz-wrapper { display: flex; flex-direction: column; gap: 0.4rem; } .fdz-label { font-size: 0.9rem; font-weight: 500; } .fdz { border: 2px dashed var(--color-border-subtle); border-radius: 10px; background: var(--color-surface); cursor: pointer; transition: border-color 0.15s, background 0.15s; outline: none; } .fdz:hover:not(.fdz--disabled):not(.fdz--filled), .fdz:focus-visible { border-color: var(--color-accent); } .fdz--drag { border-color: var(--color-accent); background: color-mix(in srgb, var(--color-accent) 8%, var(--color-surface)); } .fdz--disabled { cursor: not-allowed; opacity: 0.55; } .fdz--filled { cursor: default; border-style: solid; } .fdz__empty { display: flex; flex-direction: column; align-items: center; gap: 0.3rem; padding: 2rem 1.5rem; user-select: none; } .fdz__upload-icon { width: 2rem; height: 2rem; opacity: 0.4; margin-bottom: 0.2rem; } .fdz__hint { margin: 0; font-size: 0.9rem; font-weight: 500; } .fdz__browse { margin: 0; font-size: 0.85rem; color: var(--color-text-secondary); } .fdz__browse-link { color: var(--color-accent); text-decoration: underline; text-underline-offset: 2px; } .fdz__limit { margin: 0.25rem 0 0; font-size: 0.78rem; color: var(--color-text-muted); } .fdz__file { display: flex; align-items: center; gap: 0.75rem; padding: 0.85rem 1rem; } .fdz__file-icon { font-size: 1.5rem; flex-shrink: 0; } .fdz__file-meta { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; flex: 1; } .fdz__file-name { font-size: 0.9rem; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .fdz__file-size { font-size: 0.78rem; color: var(--color-text-muted); } .fdz__clear { flex-shrink: 0; background: none; border: none; cursor: pointer; color: var(--color-text-muted); font-size: 0.8rem; padding: 0.3rem 0.5rem; border-radius: 4px; transition: color 0.12s, background 0.12s; line-height: 1; } .fdz__clear:hover { color: var(--color-text); background: var(--color-border-subtle); } /* ── Local file / URL preview (DumpCreate) ── */ .local-preview-image { width: 100%; max-height: 320px; object-fit: contain; border-radius: 10px; background: var(--color-bg); display: block; } .preview-loading { margin: 0; font-size: 0.85rem; opacity: 0.5; font-style: italic; } /* File previews (Dump page) */ .file-preview-image { max-width: 100%; border-radius: 8px; display: block; } .file-preview-play-btn { position: relative; width: 100%; padding: 0; border: none; background: #000; cursor: pointer; display: block; border-radius: 8px; overflow: hidden; } .file-preview-video-thumb { width: 100%; max-height: 480px; object-fit: contain; display: block; } .file-preview-play-btn .rich-content-play-overlay { font-size: 3rem; } .audio-file-preview { display: flex; align-items: center; gap: 0.75rem; padding: 0.75rem 1rem; background: color-mix( in srgb, var(--color-accent) 8%, var(--color-surface) 92% ); border: 2px solid var(--color-border); border-radius: 0 0 12px 12px; } /* When active, brighten only the *un-played* bars. Excluding played bars keeps this rule from out-specifying `.waveform-bar--played` (which would paint every bar the played colour and make the waveform look stuck at 100%). */ .audio-file-preview--active .waveform-bar:not(.waveform-bar--played) { fill: color-mix(in srgb, var(--color-accent) 45%, var(--color-border) 55%); } .audio-file-preview--active .audio-player-btn { background: var(--color-accent-hover); } /* ── Video player ── */ .video-player { width: 100%; border-radius: 0 0 12px 12px; overflow: hidden; border: 2px solid var(--color-border); position: relative; } .video-player-video { width: 100%; max-height: 480px; object-fit: contain; display: block; cursor: pointer; } .video-player:not(.video-player--controls-visible) .video-player-video { cursor: none; } .video-player .video-player-controls { position: absolute; bottom: 0; left: 0; right: 0; border-radius: 0; border: none; background: linear-gradient(transparent, rgba(0, 0, 0, 0.38)); color: var(--color-on-accent); opacity: 0; pointer-events: none; transition: opacity 0.25s ease; } .video-player--controls-visible .video-player-controls { opacity: 1; pointer-events: auto; } .video-player-controls .audio-player-time { color: var(--color-on-accent); opacity: 0.85; } .video-player-controls .audio-player-vol-btn { color: var(--color-on-accent); } .video-player-controls .audio-player-track { background: rgba(255, 255, 255, 0.25); } .video-player-controls .audio-player-btn { background: rgba(255, 255, 255, 0.2); backdrop-filter: blur(4px); } .video-player-controls .audio-player-btn:hover { background: rgba(255, 255, 255, 0.35); } /* ── Custom audio player ── */ .audio-player { display: flex; align-items: center; flex-wrap: nowrap; gap: 0.6rem; padding: 0.75rem 1rem; background: color-mix( in srgb, var(--color-accent) 8%, var(--color-surface) 92% ); border-radius: 0 0 12px 12px; border: 2px solid var(--color-border); width: 100%; box-sizing: border-box; min-width: 0; overflow: hidden; } .audio-player-btn { flex-shrink: 0; width: 2.2rem; height: 2.2rem; border-radius: 50%; border: none; background: var(--color-accent); color: var(--color-on-accent); cursor: pointer; display: grid; place-items: center; padding: 0.45rem; transition: background 0.15s, transform 0.1s; } .audio-player-btn:hover { background: var(--color-accent-hover); transform: scale(1.08); } .audio-player-btn svg { width: 100%; height: 100%; display: block; } .audio-player-time { font-size: 0.78rem; font-variant-numeric: tabular-nums; opacity: 0.6; flex-shrink: 0; } .audio-player-track { position: relative; flex: 1; min-width: 48px; height: 4px; border-radius: 2px; background: color-mix( in srgb, var(--color-accent) 20%, var(--color-border) 80% ); } /* ── Waveform ── */ .waveform-svg { flex: 1; min-width: 0; height: 48px; cursor: pointer; display: block; } .waveform-bar { fill: color-mix(in srgb, var(--color-accent) 25%, var(--color-border) 75%); } .waveform-bar--played { fill: var(--color-accent); } .waveform-skeleton { flex: 1; min-width: 0; height: 48px; border-radius: 3px; background: color-mix( in srgb, var(--color-accent) 12%, var(--color-border) 88% ); position: relative; overflow: hidden; cursor: pointer; } .waveform-skeleton-fill { position: absolute; inset: 0 auto 0 0; background: var(--color-accent); opacity: 0.45; transition: width 0.1s linear; } .audio-player-track--volume { flex: 1 1 100px; max-width: 120px; } .audio-player-fill { position: absolute; inset: 0 auto 0 0; background: var(--color-accent); border-radius: 2px; pointer-events: none; } .audio-player-range { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; } .audio-player-volume { display: flex; align-items: center; gap: 0.4rem; flex-shrink: 1; min-width: 0; } .audio-player-vol-btn { flex-shrink: 0; width: 1.4rem; height: 1.4rem; border: none; background: transparent; color: var(--color-text); cursor: pointer; display: grid; place-items: center; padding: 0; opacity: 0.55; transition: opacity 0.15s; } .audio-player-vol-btn:hover { opacity: 1; } .audio-player-vol-btn svg { width: 100%; height: 100%; display: block; } .file-preview-pdf { width: 100%; height: 80vh; min-height: 600px; border-radius: 8px; border: 2px solid var(--color-border); display: block; } .file-download-link { display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.75rem 1.5rem; border: 2px solid var(--color-accent); border-radius: 8px; color: var(--color-accent); text-decoration: none; font-weight: 500; transition: background 0.15s, color 0.15s; } .file-download-link:hover { background: var(--color-accent); color: var(--color-on-accent); } .dump-file-notice { margin: 0 0 0.25rem; font-size: 0.9rem; opacity: 0.8; } .rich-content-card { display: flex; border: 2px solid var(--color-border); border-radius: 10px; overflow: hidden; transition: border-color 0.2s; } .rich-content-card:has(.rich-content-body:hover) { border-color: var(--color-accent); } .rich-content-card--youtube { border-color: var(--color-youtube); } .rich-content-card--youtube:has(.rich-content-body:hover) { border-color: var(--color-youtube-hover); } .rich-content-card--youtube .rich-content-badge { background: var(--color-youtube); } .rich-content-card--bandcamp { border-color: var(--color-bandcamp); } .rich-content-card--bandcamp:has(.rich-content-body:hover) { border-color: var(--color-bandcamp-hover); } .rich-content-card--bandcamp .rich-content-badge { background: var(--color-bandcamp); } .rich-content-card--soundcloud { border-color: var(--color-soundcloud); } .rich-content-card--soundcloud:has(.rich-content-body:hover) { border-color: var(--color-soundcloud-hover); } .rich-content-card--soundcloud .rich-content-badge { background: var(--color-soundcloud); } /* Brand badges sit on a saturated brand colour — use the fixed white foreground rather than the accent-tuned `on-accent` of the generic badge. */ .rich-content-card--youtube .rich-content-badge, .rich-content-card--bandcamp .rich-content-badge, .rich-content-card--soundcloud .rich-content-badge { color: var(--color-on-brand); } /* ── Global persistent player ── */ .global-player { position: fixed; bottom: 1rem; left: 50%; transform: translateX(-50%); width: calc(100% - 2.5rem); max-width: 860px; z-index: 1000; background: var(--color-surface); border: 2px solid var(--color-border); border-radius: 10px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.45), 0 2px 8px rgba(0, 0, 0, 0.3); animation: player-enter 0.25s cubic-bezier(0.34, 1.56, 0.64, 1) both; transition: opacity 0.2s ease, box-shadow 0.2s ease; } .global-player--reduced { opacity: 0.6; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25); } .global-player--reduced:hover { opacity: 1; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.45), 0 2px 8px rgba(0, 0, 0, 0.3); } @keyframes player-enter { from { transform: translateX(-50%) translateY(1.5rem); } to { transform: translateX(-50%) translateY(0); } } .global-player-header { display: flex; align-items: center; gap: 0.75rem; padding: 0.5rem 1rem; } .global-player-title { flex: 1; font-weight: 600; font-size: 0.9rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } a.global-player-title { color: inherit; text-decoration: none; } a.global-player-title:hover { text-decoration: underline; } .global-player-body { display: grid; grid-template-rows: 1fr; transition: grid-template-rows 0.3s ease; } .global-player--reduced .global-player-body { grid-template-rows: 0fr; } .global-player-iframe-wrap, .global-player-media-wrap { overflow: hidden; min-height: 0; border-radius: 0 0 8px 8px; } /* Inside the global player the shell already draws the border — drop the nested media player's own border so it isn't doubled up. */ .global-player .audio-player, .global-player .video-player { border: none; } .global-player iframe { width: 100%; border: none; display: block; } .global-player-iframe--youtube { aspect-ratio: 16/9; max-height: 40vh; } .global-player-iframe--soundcloud { height: 166px; } .global-player-iframe--bandcamp { height: 120px; } .global-player.global-player--bandcamp { max-width: 600px; } .feed-loading-more { text-align: center; padding: 1rem; color: var(--color-text-muted); font-size: 0.85rem; } body.has-player { padding-bottom: var(--player-height, 0px); } /* ── Floating "new dump" button ─────────────────────────────────────── Revealed once the header scrolls out of view. Bottom-right by default, but moves to the top-right while the global player is mounted (it owns the bottom of the screen, full-width on mobile). Sits below the player and modal backdrop (both z-index 1000). */ .dump-fab { position: fixed; --fab-size: 3.5rem; /* Sit just outside the right edge of the centered content lane — in the margin beside it — rather than over the content or pinned to the viewport edge. The lane width is `--fab-lane` (860px for most feeds; the journal grid is wider, see below). Falls back to a 1.25rem viewport gutter once that margin is too narrow to hold the button. */ right: max(1.25rem, calc(50% - var(--fab-lane, 860px) / 2 - var(--fab-size) - 1rem)); /* Anchored to the bottom-right by default, but expressed via `top` (rather than `bottom`) so the jump to the top-right when a player mounts can animate as a slide. `dvh` tracks the visible viewport on mobile; `vh` is the fallback for browsers without it. */ top: calc(100vh - var(--fab-size) - 1.25rem - env(safe-area-inset-bottom, 0px)); top: calc(100dvh - var(--fab-size) - 1.25rem - env(safe-area-inset-bottom, 0px)); z-index: 900; width: var(--fab-size); height: var(--fab-size); display: flex; align-items: center; justify-content: center; padding: 0; border: none; /* Circle by default; a theme can square it off by setting --fab-radius: 0. */ border-radius: var(--fab-radius, 50%); background: var(--color-accent); color: var(--color-on-accent); cursor: pointer; box-shadow: 0 4px 16px color-mix(in srgb, var(--color-accent) 45%, transparent), 0 2px 6px rgba(0, 0, 0, 0.3); /* Hidden state: faded out, nudged down, non-interactive. */ opacity: 0; transform: translateY(1rem) scale(0.9); pointer-events: none; transition: opacity 0.2s ease, transform 0.2s ease, background 0.15s ease, top 0.4s cubic-bezier(0.4, 0, 0.2, 1); } .dump-fab-icon { width: 1.6rem; height: 1.6rem; } .dump-fab--visible { opacity: 1; transform: translateY(0) scale(1); pointer-events: auto; } .dump-fab:hover { background: var(--color-accent-hover); transform: translateY(-2px) scale(1); box-shadow: 0 6px 20px color-mix(in srgb, var(--color-accent) 55%, transparent), 0 2px 6px rgba(0, 0, 0, 0.3); } .dump-fab:active { transform: translateY(0) scale(0.96); } /* The journal tab lays out its masonry grid in a wider lane than the other feeds, so the button must track that wider edge to stay aligned with it. */ body:has(.journal-grid) { --fab-lane: 1180px; } /* The player occupies the bottom of the screen (full-width on mobile), so when one is mounted slide the button up to the top-right corner to stay clear. */ body.has-player .dump-fab { top: calc(1.25rem + env(safe-area-inset-top, 0px)); } /* Reserve room at the end of the scroll so the button never hides the last content when scrolled all the way down. */ body.has-fab .page-content { padding-bottom: 5rem; } /* Chat FAB — mirrors .dump-fab and stacks directly above it (the "+" dump button keeps the bottom slot; chat sits one button-height up) so the two never overlap. */ .chat-fab { position: fixed; --fab-size: 3.5rem; /* Share the dump-fab's column. */ right: max(1.25rem, calc(50% - var(--fab-lane, 860px) / 2 - var(--fab-size) - 1rem)); /* One button-height + gap above the dump-fab's bottom-anchored resting spot. */ top: calc(100vh - var(--fab-size) * 2 - 2rem - env(safe-area-inset-bottom, 0px)); top: calc(100dvh - var(--fab-size) * 2 - 2rem - env(safe-area-inset-bottom, 0px)); z-index: 900; width: var(--fab-size); height: var(--fab-size); display: flex; align-items: center; justify-content: center; padding: 0; border: none; border-radius: var(--fab-radius, 50%); background: var(--color-accent); color: var(--color-on-accent); cursor: pointer; box-shadow: 0 4px 16px color-mix(in srgb, var(--color-accent) 45%, transparent), 0 2px 6px rgba(0, 0, 0, 0.3); opacity: 0; transform: translateY(1rem) scale(0.9); pointer-events: none; transition: opacity 0.2s ease, transform 0.2s ease, background 0.15s ease, top 0.4s cubic-bezier(0.4, 0, 0.2, 1); } .chat-fab-icon { width: 1.6rem; height: 1.6rem; } .chat-fab--visible { opacity: 1; transform: translateY(0) scale(1); pointer-events: auto; } .chat-fab:hover { background: var(--color-accent-hover); transform: translateY(-2px) scale(1); box-shadow: 0 6px 20px color-mix(in srgb, var(--color-accent) 55%, transparent), 0 2px 6px rgba(0, 0, 0, 0.3); } .chat-fab:active { transform: translateY(0) scale(0.96); } /* Match the dump-fab's clearance shift when a player is mounted. */ body.has-player .chat-fab { top: calc(1.25rem + env(safe-area-inset-top, 0px)); /* Stack below the dump-fab in the top-right corner to avoid overlap. */ right: max(1.25rem, calc(50% - var(--fab-lane, 860px) / 2 - var(--fab-size) - 1rem)); top: calc(1.25rem + var(--fab-size) + 0.75rem + env(safe-area-inset-top, 0px)); } /* The badge sits on the circular FAB; nudge it onto the rim. */ .chat-fab .notification-badge { top: 2px; right: 2px; box-shadow: 0 0 0 2px var(--color-accent); } .rich-content-thumbnail-btn { position: relative; padding: 0; border: none; background: none; cursor: pointer; display: flex; align-self: stretch; overflow: hidden; flex-shrink: 0; } .rich-content-play-overlay { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; background: rgba(0, 0, 0, 0.25); color: #fff; font-size: 1.75rem; transition: background 0.18s ease; } .rich-content-thumbnail-btn:hover .rich-content-play-overlay { background: rgba(0, 0, 0, 0.5); } .rich-content-thumbnail { width: 180px; min-width: 180px; object-fit: cover; display: block; } @media (max-width: 512px) { .rich-content-card { flex-direction: column; } .rich-content-thumbnail { width: 100%; min-width: 0; max-height: 180px; } } .rich-content-body { display: flex; flex-direction: column; text-decoration: none; color: var(--color-text); gap: 0.4rem; padding: 0.9rem 1.1rem; flex: 1; min-width: 0; } /* Generic badge (unknown providers) — themed accent. Brand providers override the background below and switch to the fixed white `on-brand` foreground. */ .rich-content-badge { display: inline-block; background: var(--color-accent); color: var(--color-on-accent); font-size: 0.7rem; font-weight: 700; letter-spacing: 0.04em; padding: 0.15rem 0.5rem; border-radius: 4px; align-self: flex-start; } .rich-content-title { margin: 0; font-weight: 600; font-size: 1rem; line-height: 1.35; overflow-wrap: break-word; word-break: break-word; } /* Multi-line truncation boilerplate. Each owner sets its own `-webkit-line-clamp` line count; this group carries the rest. */ .rich-content-description, .dump-card-comment, .playlist-card-description, .journal-card-title, .journal-card--image .journal-card-comment, .journal-card-quote-body { display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden; } .rich-content-description { margin: 0; font-size: 0.85rem; opacity: 0.75; -webkit-line-clamp: 2; line-clamp: 2; } .rich-content-url { font-size: 0.75rem; opacity: 0.5; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: block; } .dump-card--fading { filter: brightness(0.65); } .dump-card--dismissing { opacity: 0; filter: brightness(0.25); grid-template-rows: 0fr; pointer-events: none; } .rich-content-compact { display: flex; align-items: center; text-decoration: none; flex-shrink: 0; } .rich-content-compact-thumbnail { width: 36px; height: 36px; object-fit: cover; border-radius: 4px; border: 1px solid var(--color-border); display: block; } .rich-content-compact-icon { font-size: 1rem; opacity: 0.6; } .avatar-img { object-fit: cover; border: 2px solid var(--color-surface); border-radius: 50%; display: block; } .avatar-initials { display: flex; align-items: center; justify-content: center; border-radius: 50%; background: var(--color-accent); color: var(--color-on-accent); font-weight: 700; user-select: none; flex-shrink: 0; } /* ── Vote button ── */ .vote-btn { display: inline-flex; align-items: center; gap: 0.3rem; padding: 0.25rem 0.6rem; border: 2px solid var(--color-border); border-radius: 6px; background: transparent; color: var(--color-text); font-size: 0.85rem; font-weight: 600; cursor: pointer; transition: border-color 0.15s, background 0.15s, color 0.15s; white-space: nowrap; flex-shrink: 0; font-variant-numeric: tabular-nums; -webkit-font-smoothing: antialiased; } .vote-btn:hover:not(:disabled) { border-color: var(--color-accent); color: var(--color-accent); } .vote-btn--active { border-color: var(--color-accent); background: var(--color-accent); color: var(--color-on-accent); } .vote-btn--active:hover:not(:disabled) { background: var(--color-accent-hover); border-color: var(--color-accent-hover); color: var(--color-on-accent); } .vote-btn:focus { outline: none; } .vote-btn:focus-visible { box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-accent) 35%, transparent); } .vote-btn:disabled { opacity: 0.5; cursor: default; } /* Clickable counts. Padding + matching negative margin enlarge the click target without shifting layout — the box grows, its visual position holds. */ .vote-count-clickable, .like-count-clickable { display: inline-block; cursor: pointer; } .vote-count-clickable:hover, .like-count-clickable:hover { text-decoration: underline; } .vote-count-clickable { padding: 0.4rem; margin: -0.4rem; } /* ── Dump OP line ── */ .dump-op { display: flex; align-items: center; flex-wrap: wrap; gap: 0.25rem 0.5rem; font-size: 0.85rem; opacity: 0.7; } .dump-op-link { font-weight: 600; color: var(--color-text); text-decoration: none; min-width: 0; overflow-wrap: anywhere; } .dump-op-link:hover { color: var(--color-accent); } /* ── Avatar edit overlay ── */ /* ── ImagePicker (reusable clickable cover image) ── */ .img-picker { position: relative; flex-shrink: 0; cursor: pointer; outline: none; } .img-picker-img { width: 100%; height: 100%; object-fit: cover; display: block; border: 1px solid var(--color-border); } .img-picker-placeholder { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; border: 2px dashed var(--color-border); background: var(--color-bg); color: var(--color-text); font-size: 1.5rem; opacity: 0.4; box-sizing: border-box; } .img-picker-overlay { position: absolute; inset: 0; background: var(--color-overlay); color: var(--color-on-accent); font-size: 1.1rem; display: flex; align-items: center; justify-content: center; opacity: 0; transition: opacity 0.15s; } .img-picker:hover .img-picker-overlay, .img-picker:focus-visible .img-picker-overlay { opacity: 1; } .profile-avatar-wrapper { position: relative; flex-shrink: 0; } .avatar-change-overlay { position: absolute; inset: 0; border-radius: 50%; background: var(--color-overlay); color: var(--color-on-accent); font-size: 1.1rem; display: flex; align-items: center; justify-content: center; opacity: 0; cursor: pointer; transition: opacity 0.15s; } .profile-avatar-wrapper:hover .avatar-change-overlay { opacity: 1; } /* ── Public profile page ── */ .profile-header { display: flex; align-items: flex-start; gap: 1.5rem; } .profile-info { position: relative; min-width: 0; } .profile-info-scroll { overflow-x: auto; scrollbar-width: none; padding-bottom: 0.25rem; padding-right: 0.25rem; } .profile-info-scroll::-webkit-scrollbar { display: none; } /* Fade hint only at narrow viewports where the info column can actually overflow */ @media (max-width: 600px) { .profile-info::after { content: ""; position: absolute; top: 0; right: 0; bottom: 0; width: 2rem; background: linear-gradient(to right, transparent, var(--color-bg)); pointer-events: none; } } .profile-tabs-scroller { margin-top: 0.5rem; padding-bottom: 0.5rem; border-bottom: 2px solid var(--color-border); } .profile-columns { display: grid; grid-template-columns: 1fr; gap: 1.5rem; } @media (min-width: 900px) { .profile-columns { grid-template-columns: 1fr 1fr; gap: 2rem; align-items: start; } } .profile-section ul { list-style: none; margin: 0; padding: 0; } .empty-state { margin: 0; opacity: 0.5; font-size: 0.9rem; } /* ── Profile section header (shared between bare h2 and wrapper div) ── */ .profile-section > h2, .profile-section-header { margin-bottom: 0.75rem; border-bottom: 2px solid var(--color-border); padding-bottom: 0.4rem; } .profile-section-title { margin: 0; } .profile-section-header { display: flex; align-items: center; justify-content: space-between; gap: 1rem; } /* ── Profile (own) page ── */ .profile-username-row { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; } .profile-username { margin: 0; font-size: 1.25rem; font-weight: 600; } /* Role badge — elevated roles only (see RoleChip) */ .profile-role-chip { padding: 0.1rem 0.55rem; border-radius: 999px; background: var(--color-accent); color: var(--color-on-accent); font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; white-space: nowrap; } .profile-role-chip--moderator { background: var(--color-success); } .profile-header .btn-border { margin-top: 0.5rem; } .profile-header .follow-btn { margin-top: 0.5rem; } /* ── Profile description ── */ .profile-description { width: 100%; margin-bottom: 1.5rem; } .profile-description-view { position: relative; padding: 0.5rem 0.6rem; border-radius: 6px; font-size: 0.95rem; } .profile-description-view--editable { cursor: pointer; } .profile-description-view--editable:hover { background: var(--color-surface); } .profile-description-text { white-space: pre-wrap; overflow-wrap: break-word; word-break: break-word; line-height: 1.6; } .profile-description-empty { color: var(--color-muted); font-style: italic; } .profile-description-edit-btn { position: absolute; top: 0.55rem; right: 0.5rem; font-size: 0.85rem; color: var(--color-muted); opacity: 0; transition: opacity 0.1s; pointer-events: none; } .profile-description-view--editable:hover .profile-description-edit-btn { opacity: 1; } .profile-description-editor { display: flex; flex-direction: column; gap: 0.5rem; } .profile-email-display { display: inline-flex; align-items: center; gap: 0.35rem; font-size: 0.78rem; color: var(--color-text-muted); margin: 0.1rem 0 0.4rem; cursor: pointer; border-radius: 4px; padding: 0.1rem 0.2rem; margin-left: -0.2rem; } .profile-email-display:hover { background: var(--color-surface); } .profile-email-display:hover .profile-description-edit-btn { opacity: 1; } .profile-email-editor { margin: 0.2rem 0 0.4rem; display: flex; flex-direction: column; gap: 0.4rem; } .profile-email-input { width: min(260px, 100%); padding: 0.35rem 0.6rem; border-radius: 6px; font-size: 0.85rem; line-height: 1.5; } .profile-email-actions { display: flex; gap: 0.4rem; } .profile-email-btn { display: inline-flex; align-items: center; justify-content: center; height: 1.75rem; padding: 0 0.65rem; border-radius: 5px; border: 1.5px solid; font-size: 0.8rem; font-family: inherit; font-weight: 500; cursor: pointer; box-sizing: border-box; transition: background 0.15s, border-color 0.15s, color 0.15s; } .profile-email-btn--save { background: var(--color-accent); color: var(--color-on-accent); border-color: var(--color-accent); } .profile-email-btn--save:hover:not(:disabled) { background: var(--color-accent-hover); border-color: var(--color-accent-hover); } .profile-email-btn--save:disabled { opacity: 0.45; cursor: not-allowed; } .profile-email-btn--cancel { background: transparent; color: var(--color-text-muted); border-color: var(--color-border); } .profile-email-btn--cancel:hover { color: var(--color-text); border-color: var(--color-text-muted); } .profile-description-actions { display: flex; align-items: center; gap: 0.5rem; } .profile-invited-by { font-size: 0.78rem; color: var(--color-text-muted); margin: 0.15rem 0 0.4rem; } .profile-invited-by--founding { font-style: italic; } .profile-invited-by-link { color: var(--color-text-muted); text-decoration: none; font-weight: 600; } .profile-invited-by-link:hover { color: var(--color-accent); } .profile-own-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.5rem; align-items: center; } .profile-own-actions .btn-border { margin-top: 0; } .invite-generate { display: contents; } .invite-btn { padding: 0.3rem 0.9rem; border: 1.5px solid var(--color-accent); border-radius: 6px; background: transparent; color: var(--color-accent); font-size: 0.85rem; cursor: pointer; transition: background 0.15s, color 0.15s; } .invite-btn:hover { background: var(--color-accent); color: var(--color-on-accent); } .invite-result { display: flex; align-items: center; gap: 0.5rem; max-width: 480px; } .invite-url { font-size: 0.75rem; font-family: var(--font-mono, monospace); color: var(--color-text-muted); flex: 1; min-width: 0; overflow-x: auto; white-space: nowrap; background: var(--color-surface); border: 1px solid var(--color-border-subtle); border-radius: 6px; padding: 0.35rem 0.6rem; } /* Always-visible copy target. Sits first so it's the obvious tap. */ .invite-copy-btn { order: -1; flex-shrink: 0; padding: 0.2rem 0.65rem; border: 1px solid var(--color-border-subtle); border-radius: 4px; background: transparent; color: var(--color-text); font-size: 0.75rem; cursor: pointer; white-space: nowrap; transition: background 0.12s; } .invite-copy-btn:hover { background: var(--color-accent); color: var(--color-on-accent); border-color: var(--color-accent); } /* ── Profile sub-pages (dumps / upvoted / playlists) ── */ .profile-subpage-back { display: inline-block; font-size: 0.85rem; color: var(--color-text-muted); text-decoration: none; margin-bottom: 0.75rem; } .profile-subpage-back:hover { color: var(--color-text); } .profile-subpage-title-row { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; } .profile-subpage-title { margin: 0; font-size: 1.4rem; font-weight: 700; flex: 1; } /* ── Profile "View all" link ── */ .profile-view-all { display: inline-block; margin-top: 0.75rem; font-size: 0.85rem; color: var(--color-text-muted); text-decoration: none; } .profile-view-all:hover { color: var(--color-accent); } /* ── Border-tinted button ── */ .btn-border { display: inline-flex; align-items: center; justify-content: center; padding: 0.3rem 0.9rem; border: 1.5px solid var(--color-border); border-radius: 6px; background: transparent; color: var(--color-text-muted); font-size: 0.85rem; font-family: inherit; cursor: pointer; transition: border-color 0.15s, color 0.15s, background 0.15s; } .btn-border:hover { border-color: var(--color-danger); color: var(--color-danger); } /* ── ErrorCard ── */ .error-card-wrap { display: flex; flex-direction: column; gap: 0.75rem; } .error-card { display: flex; align-items: flex-start; gap: 0.6rem; background: var(--color-danger-bg); color: var(--color-text); padding: 0.7rem 1rem; border-radius: 8px; font-size: 0.9rem; } .error-card-icon { flex-shrink: 0; line-height: 1.45; } .error-card-body { display: flex; flex-direction: column; gap: 0.2rem; } .error-card-title { margin: 0; font-size: 0.95rem; font-weight: 700; } .error-card-message { margin: 0; line-height: 1.45; opacity: 0.85; } .error-card-actions { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; } .app-header-status { margin: 1rem auto 0; max-width: 860px; width: calc(100% - 2rem); padding: 0.75rem 1rem; border: none; border-radius: 10px; background: var(--color-danger-bg); color: var(--color-text); line-height: 1.5; overflow-wrap: anywhere; word-break: break-word; } .app-header-status strong { font-size: 0.95rem; } .page-error-wrap { margin: 2rem auto; max-width: 480px; width: 100%; } /* ── Shared layout ── */ .page-shell { display: flex; flex-direction: column; } .page-content { width: 100%; max-width: 860px; margin: 0 auto; padding: 2rem 1.25rem 0; box-sizing: border-box; animation: page-enter 0.2s ease both; display: flex; flex-direction: column; gap: 2.5rem; } .page-content--centered { align-items: center; padding-top: 2.5rem; } .page-loading { text-align: center; opacity: 0.6; padding: 4rem 1rem; } /* ── Shared header ── */ .app-header { position: relative; display: flex; align-items: center; gap: 1rem; padding: 0 1.25rem; min-height: 3.75rem; background: var(--color-surface); border-bottom: 2px solid var(--color-border); } @media (min-width: 860px) { .app-header--has-center { display: grid; grid-template-columns: auto 1fr auto; } .app-header--has-center .app-header-nav { grid-column: 3; } } .app-header-brand { font-size: 1.15rem; font-weight: 700; letter-spacing: -0.01em; flex-shrink: 0; color: var(--color-text); text-decoration: none; } .app-header-center { display: none; align-items: center; justify-content: center; min-width: 0; overflow-x: clip; overflow-y: visible; container-type: inline-size; } @media (min-width: 860px) { .app-header-center { display: flex; } .app-header-center .search-bar-btn { display: none; } } /* Below-header search row — small viewports only */ .header-search-below { display: flex; align-items: center; padding: 0.5rem 1rem; background: var(--color-surface); border-bottom: 2px solid var(--color-border); } .header-search-below .search-bar { flex: 1; } .header-search-below .search-bar-input { max-width: 100%; } @media (min-width: 860px) { .header-search-below { display: none; } } /* When the search bar is expanded, immediately clear the rest of the center — the expanded input needs the full column width */ .app-header-center:has(.search-bar--expanded) .index-presence, .app-header-center:has(.search-bar--expanded) .feed-sort { display: none; } /* As the center column shrinks (viewport narrow, search collapsed), shed content in order: presence first, then tabs (still in .index-below-header) */ @container (max-width: 460px) { .index-presence { display: none; } } @container (max-width: 280px) { .feed-sort { display: none; } } .header-center-slot { display: flex; align-items: center; gap: 0.75rem; } .app-header-brand:hover { color: var(--color-accent); } .app-header-nav { display: flex; align-items: center; justify-content: flex-end; gap: 0.6rem; margin-left: auto; flex-shrink: 0; flex-wrap: nowrap; } /* Normalize every header-nav control to one height (search / notifications / avatar / + new were all different sizes) and centre their contents — flex + line-height:1 keeps text and emoji glyphs vertically centred in the box. */ .app-header-nav button, .app-header-user { height: 2.25rem; box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; font-size: 0.95rem; font-family: inherit; line-height: 1; } .app-header-nav button { padding: 0.35rem 0.85rem; } .app-header-nav .btn-primary { padding: 0.35rem 1rem; } /* Icon-only controls are square, so the row reads as a tidy set. */ .app-header-nav .nav-search-btn, .app-header-nav .notification-bell, .app-header-nav .chat-button, .app-header-nav .user-menu-trigger { width: 2.25rem; padding: 0; justify-content: center; } /* Ghost search button — always visible except when the center search bar is shown */ .nav-search-btn { display: flex; align-items: center; padding: 0.35rem; background: transparent; border: none; color: inherit; text-decoration: none; font-size: 1rem; cursor: pointer; border-radius: 6px; } .nav-search-btn:hover { background: var(--color-surface); } /* Text links — visible only at wide viewports */ .nav-links { display: none; align-items: center; gap: 0.6rem; } @media (min-width: 1150px) { .nav-links { display: flex; } } /* Avatar menu — visible only below the text-links breakpoint */ .nav-compact { display: flex; align-items: center; } @media (min-width: 1150px) { .nav-compact { display: none; } } .app-header-user { display: inline-flex; align-items: center; text-decoration: none; color: var(--color-text); font-weight: 600; font-size: 0.95rem; padding: 0.35rem 0.85rem; border-radius: 8px; background: var(--color-header-user-bg); transition: background 0.15s; } .app-header-user:hover { background: var(--color-header-user-bg-hover); } /* ── User menu (compact nav) ── */ .user-menu { position: relative; } .user-menu-trigger { display: flex; align-items: center; background: transparent; border: none; border-radius: 8px; padding: 0.25rem; cursor: pointer; transition: background 0.15s; } .user-menu-trigger:hover { background: var(--color-header-user-bg-hover); } .user-menu-dropdown { position: absolute; top: calc(100% + 0.4rem); right: 0; min-width: 150px; background: var(--color-surface); border: 2px solid var(--color-border-subtle); border-radius: 10px; padding: 0.35rem; display: flex; flex-direction: column; gap: 0.15rem; z-index: 100; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2); } .user-menu-item { display: block; padding: 0.45rem 0.75rem; border-radius: 7px; text-decoration: none; color: var(--color-text); font-size: 0.9rem; font-weight: 600; transition: background 0.12s; } .user-menu-item:hover { background: var(--color-header-user-bg-hover); } .user-list-popover { /* position/top/bottom/left/max-height set inline — anchored to the trigger's viewport rect, flipping above it when there isn't room below */ min-width: 180px; overflow-y: auto; background: var(--color-surface); border: 2px solid var(--color-border-subtle); border-radius: 10px; padding: 0.35rem; display: flex; flex-direction: column; gap: 0.15rem; z-index: 100; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2); } .user-list-popover-item { display: flex; align-items: center; gap: 0.5rem; padding: 0.35rem 0.5rem; border-radius: 7px; text-decoration: none; color: var(--color-text); font-size: 0.85rem; font-weight: 600; transition: background 0.12s; } .user-list-popover-item:hover { background: var(--color-header-user-bg-hover); } .user-list-popover-status { margin: 0; padding: 0.5rem 0.6rem; font-size: 0.8rem; color: var(--color-text-muted); } .user-list-popover-load-more { background: none; border: none; cursor: pointer; padding: 0.4rem 0.6rem; font-size: 0.8rem; font-weight: 600; color: var(--color-accent); text-align: left; font-family: inherit; } .user-list-popover-load-more:disabled { opacity: 0.5; cursor: default; } /* ── Auth card ── */ .auth-card { width: 100%; max-width: 360px; display: flex; flex-direction: column; gap: 1.25rem; background: var(--color-surface); border: 2px solid var(--color-border); border-radius: 12px; padding: 2rem; } .auth-card-title { margin: 0; font-size: 1.5rem; font-weight: 700; text-align: center; } .auth-card-footer { text-align: center; font-size: 0.9rem; opacity: 0.7; margin: 0; } .auth-link-btn { background: none; border: none; padding: 0; cursor: pointer; color: var(--color-accent); font-size: inherit; font-family: inherit; text-decoration: underline; text-underline-offset: 2px; } .auth-link-btn:hover { opacity: 0.8; } .auth-reset-panel { border-top: 1px solid var(--color-border-subtle); padding-top: 1rem; display: flex; flex-direction: column; gap: 0.75rem; } .auth-reset-form { margin: 0; } .auth-reset-sent { font-size: 0.9rem; text-align: center; margin: 0; opacity: 0.85; } /* ── Form pages (DumpCreate / DumpEdit) ── */ @keyframes page-enter { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } } .form-page { display: grid; grid-template-columns: 1fr; grid-template-rows: auto; gap: 3px; animation: page-enter 0.2s ease both; } @media (min-width: 860px) { .form-page--two-col { grid-template-columns: 1fr 1fr; } .form-page--two-col .form-page-header { grid-column: 1 / -1; } .form-page--two-col .dump-edit-preview { border-radius: 0 0 0 12px; } /* Higher specificity than the generic `.form-page .form` below so the form's bottom-left stays square where it meets the preview. */ .form-page.form-page--two-col .form { border-radius: 0 0 12px 0; } } .form-page-header { padding: 1.25rem; background: var(--color-surface); border-radius: 12px 12px 0 0; display: flex; flex-direction: column; gap: 0.2rem; } .form-page-eyebrow { margin: 0; font-size: 0.78rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.07em; opacity: 0.45; } .form-page-title { margin: 0; font-size: 1.4rem; font-weight: 700; word-break: break-word; } .dump-edit-preview { padding: 1.25rem; background: var(--color-surface); overflow: hidden; border-radius: 0 0 12px 12px; } .dump-edit-refresh { margin-top: 0.75rem; } .dump-edit-thumbnail-row { display: flex; align-items: center; gap: 0.75rem; } .form-page .form { background: var(--color-surface); border-radius: 0 0 12px 12px; padding: 1.25rem; } .form-actions { display: flex; align-items: center; justify-content: space-between; padding-top: 0.75rem; border-top: 1px solid var(--color-border-subtle); margin-top: 0.5rem; gap: 0.75rem; } .form-actions-right { display: flex; align-items: center; gap: 0.75rem; margin-left: auto; } /* Subtle text action. Resets the global