diff --git a/assets/beep.css b/assets/beep.css index 108db19..d596fca 100755 --- a/assets/beep.css +++ b/assets/beep.css @@ -1,7 +1,6 @@ /* ============================================================ - Beep — Twitter-style microblog for WordPress - All rules are scoped under .beep-feed so they don't leak - into the host theme. + Beep — Twitter/X Dark Mode + Post and feed styling to match Twitter/X perfectly ============================================================ */ .beep-feed, @@ -10,106 +9,99 @@ } .beep-feed { - --beep-bg: #ffffff; - --beep-text: #0F1419; - --beep-text-secondary: #536471; - --beep-border: #EFF3F4; - --beep-hover: #F7F9F9; + /* Twitter Dark Mode Palette */ + --beep-bg: #15202B; + --beep-card-bg: #192734; + --beep-card-hover: #1C2732; + --beep-text: #E7E9EA; + --beep-muted: #71767B; + --beep-border: #2F3336; --beep-accent: #1D9BF0; --beep-accent-hover: #1A8CD8; --beep-like: #F91880; --beep-repost: #00BA7C; - --beep-page-bg: #E6ECF0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; max-width: 600px; - margin: 24px auto; + margin: 0 auto; background: var(--beep-bg); - border: 1px solid var(--beep-border); - border-radius: 16px; - overflow: hidden; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, - "Helvetica Neue", Arial, sans-serif; + border-left: 1px solid var(--beep-border); + border-right: 1px solid var(--beep-border); color: var(--beep-text); font-size: 15px; line-height: 1.4; -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + min-height: 100vh; } -/* ---------- Header ---------- */ +/* Header */ .beep-feed-header { - padding: 14px 20px; + padding: 16px 20px; border-bottom: 1px solid var(--beep-border); - font-size: 20px; - font-weight: 800; - background: rgba(255, 255, 255, 0.85); - backdrop-filter: blur(12px); display: flex; align-items: center; - gap: 10px; + justify-content: space-between; position: sticky; top: 0; z-index: 10; + background: rgba(21, 32, 43, 0.85); + backdrop-filter: blur(12px); } -.beep-feed-header-text { +.beep-feed-header-title { font-size: 20px; font-weight: 800; color: var(--beep-text); - white-space: nowrap; + letter-spacing: -0.5px; } -.beep-feed-header .beep-logo { - height: 36px; - width: auto; - max-width: 100%; - flex-shrink: 0; - object-fit: contain; - display: block; -} - -/* ---------- Notices ---------- */ -.beep-notice { - padding: 24px 20px; +/* Nav tabs */ +.beep-top-nav { + display: flex; border-bottom: 1px solid var(--beep-border); +} + +.beep-nav-tab { + flex: 1; + background: transparent; + border: 0; + padding: 16px 0; + font-family: inherit; + font-size: 15px; + font-weight: 700; + color: var(--beep-muted); + cursor: pointer; + position: relative; text-align: center; + transition: background-color 0.15s ease, color 0.15s ease; } -.beep-notice p { - margin: 0 0 12px; - font-size: 16px; +.beep-nav-tab:hover { + background: var(--beep-card-hover); + color: var(--beep-text); } -.beep-notice-banned { - color: var(--beep-text-secondary); - font-style: italic; +.beep-nav-tab.is-active { + color: var(--beep-text); } -/* ---------- Avatars ---------- */ -.beep-feed .beep-avatar { - width: 48px !important; - height: 48px !important; - min-width: 48px !important; - max-width: 48px !important; - border-radius: 50% !important; - flex-shrink: 0; - background: #ccd6dd; - object-fit: cover !important; - aspect-ratio: 1 / 1 !important; - display: block !important; +.beep-nav-tab.is-active::after { + content: ''; + position: absolute; + left: 0; + right: 0; + bottom: 0; + height: 4px; + background: var(--beep-accent); + border-radius: 9999px; } -.beep-feed .beep-avatar-sm { - width: 36px !important; - height: 36px !important; - min-width: 36px !important; - max-width: 36px !important; -} - -/* ---------- Compose ---------- */ +/* Compose area */ .beep-compose { display: flex; gap: 12px; - padding: 16px; + padding: 16px 20px; border-bottom: 1px solid var(--beep-border); } @@ -123,102 +115,167 @@ border: 0 !important; outline: 0 !important; resize: none !important; - background: #ffffff !important; + background: transparent !important; font-family: inherit !important; font-size: 18px !important; - line-height: 1.4 !important; + line-height: 1.5 !important; color: var(--beep-text) !important; padding: 8px 0 !important; - min-height: 32px; + min-height: 52px; overflow: hidden; box-shadow: none !important; + caret-color: var(--beep-accent); } .beep-feed .beep-input::placeholder { - color: var(--beep-text-secondary) !important; - opacity: 1 !important; + color: var(--beep-muted) !important; } -.beep-compose-footer, -.beep-reply-footer { +.beep-compose-footer { display: flex; - justify-content: flex-end; + justify-content: space-between; align-items: center; - gap: 12px; + margin-top: 12px; + padding-top: 12px; border-top: 1px solid var(--beep-border); - padding-top: 10px; - margin-top: 4px; } -/* ---------- Char counter ---------- */ -.beep-char-counter { - font-size: 13px; - color: var(--beep-text-secondary); - font-variant-numeric: tabular-nums; +.beep-compose-tools { + display: flex; + gap: 4px; } -.beep-counter-warn { color: #FFD400; } -.beep-counter-bad { color: #F4212E; font-weight: 700; } +.beep-compose-tool { + background: transparent; + border: 0; + color: var(--beep-accent); + cursor: pointer; + width: 36px; + height: 36px; + border-radius: 9999px; + display: flex; + align-items: center; + justify-content: center; + transition: background-color 0.15s ease; +} -/* ---------- Buttons ---------- */ +.beep-compose-tool:hover { + background: rgba(29, 155, 240, 0.15); +} + +/* Buttons */ .beep-button { - display: inline-block; + display: inline-flex; + align-items: center; + justify-content: center; background: var(--beep-accent); color: #fff; border: 0; border-radius: 9999px; - padding: 8px 18px; + padding: 8px 20px; font-weight: 700; font-size: 15px; cursor: pointer; - text-decoration: none; font-family: inherit; - transition: background-color 0.15s ease, opacity 0.15s ease; + transition: background-color 0.15s ease, transform 0.1s ease; } -.beep-button:hover { background: var(--beep-accent-hover); color: #fff; } +.beep-button:hover { background: var(--beep-accent-hover); color: #fff; } +.beep-button:active { transform: scale(0.96); } .beep-button:disabled { opacity: 0.5; cursor: default; } -.beep-button:disabled:hover { background: var(--beep-accent); } -.beep-button-secondary { +/* Follow button */ +.beep-follow-btn { + background: var(--beep-text); + color: var(--beep-bg); + border: 0; + border-radius: 9999px; + padding: 6px 16px; + font-weight: 700; + font-size: 14px; + cursor: pointer; + font-family: inherit; + transition: opacity 0.15s ease; + white-space: nowrap; +} + +.beep-follow-btn:hover { opacity: 0.85; } + +.beep-following-btn { background: transparent; - color: var(--beep-accent); - border: 1px solid var(--beep-accent); - margin-left: 8px; -} -.beep-button-secondary:hover { - background: rgba(29, 155, 240, 0.1); - color: var(--beep-accent); + color: var(--beep-text); + border: 1px solid var(--beep-border); + border-radius: 9999px; + padding: 6px 16px; + font-weight: 700; + font-size: 14px; + cursor: pointer; + font-family: inherit; + transition: border-color 0.15s ease, color 0.15s ease, background-color 0.15s ease; } -/* ---------- Post ---------- */ -.beep-list { - background: var(--beep-bg); +.beep-following-btn:hover { + border-color: #F4212E; + color: #F4212E; + background: rgba(244, 33, 46, 0.1); } +/* Char counter */ +.beep-char-counter { font-size: 13px; color: var(--beep-muted); font-variant-numeric: tabular-nums; } +.beep-counter-warn { color: #FFD400; } +.beep-counter-bad { color: #F4212E; font-weight: 700; } + +/* Avatars */ +.beep-feed .beep-avatar { + width: 48px !important; + height: 48px !important; + min-width: 48px !important; + max-width: 48px !important; + border-radius: 50% !important; + flex-shrink: 0; + background: var(--beep-border); + object-fit: cover !important; + aspect-ratio: 1 / 1 !important; + display: block !important; + border: 0 !important; + transition: filter 0.15s ease; +} + +.beep-feed .beep-avatar:hover { filter: brightness(0.9); } + +.beep-feed .beep-avatar-sm { + width: 40px !important; + height: 40px !important; + min-width: 40px !important; + max-width: 40px !important; +} + +/* Post list */ +.beep-list { background: var(--beep-bg); } + .beep-empty { - padding: 40px 20px; + padding: 60px 20px; text-align: center; - color: var(--beep-text-secondary); + color: var(--beep-muted); } +/* Individual post */ .beep-post { display: flex; gap: 12px; - padding: 12px 16px; + padding: 14px 20px; border-bottom: 1px solid var(--beep-border); + background: var(--beep-bg); transition: background-color 0.15s ease; + cursor: pointer; } -.beep-post:hover { background: var(--beep-hover); } +.beep-post:hover { background: var(--beep-card-hover); } .beep-post:last-child { border-bottom: 0; } -.beep-post-body { - flex: 1; - min-width: 0; -} +.beep-post-body { flex: 1; min-width: 0; } -/* ---------- Meta line ---------- */ +/* Meta line */ .beep-meta { display: flex; align-items: center; @@ -232,84 +289,64 @@ .beep-name { font-weight: 700; color: var(--beep-text); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } +.beep-name:hover { text-decoration: underline; cursor: pointer; } + .beep-handle, .beep-dot, -.beep-time { - color: var(--beep-text-secondary); - font-weight: 400; -} +.beep-time { color: var(--beep-muted); font-weight: 400; white-space: nowrap; } .beep-time { text-decoration: none; } +.beep-time:hover { text-decoration: underline; cursor: pointer; } .beep-spacer { flex: 1; } -.beep-menu-trigger { - background: transparent; - border: 0; - color: var(--beep-text-secondary); - cursor: pointer; - font-size: 18px; - line-height: 1; - width: 24px; - height: 24px; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - padding: 0; - transition: background-color 0.15s ease, color 0.15s ease; +/* Verified badge */ +.beep-verified { + width: 18px; + height: 18px; + fill: var(--beep-accent); + flex-shrink: 0; + margin-left: 2px; + vertical-align: text-bottom; } -.beep-menu-trigger:hover { - background: rgba(244, 33, 46, 0.1); - color: #F4212E; -} - -/* ---------- Text ---------- */ +/* Post text */ .beep-text { font-size: 15px; - line-height: 1.4; + line-height: 1.5; color: var(--beep-text); word-wrap: break-word; overflow-wrap: anywhere; - margin: 2px 0 12px; + margin: 0; + white-space: pre-wrap; } -.beep-tag, -.beep-mention, -.beep-link { - color: var(--beep-accent); - text-decoration: none; -} +.beep-tag, .beep-mention { color: var(--beep-text); text-decoration: none; cursor: pointer; } +.beep-tag:hover, .beep-mention:hover { text-decoration: underline; } -.beep-tag:hover, -.beep-mention:hover, -.beep-link:hover { - text-decoration: underline; -} +.beep-link { color: var(--beep-accent); text-decoration: none; } +.beep-link:hover { text-decoration: underline; } -.beep-mention-stale { - color: var(--beep-accent); - opacity: 0.85; -} - -/* ---------- Action row ---------- */ +/* Action bar */ .beep-actions { display: flex; justify-content: space-between; max-width: 425px; - margin-top: 4px; + margin-top: 12px; } .beep-action { display: flex; align-items: center; - gap: 6px; + gap: 4px; background: transparent; border: 0; - color: var(--beep-text-secondary); + color: var(--beep-muted); font-size: 13px; font-family: inherit; cursor: pointer; @@ -319,144 +356,522 @@ transition: color 0.15s ease, background-color 0.15s ease; } -.beep-action svg { +.beep-action svg, +.beep-action .beep-icon-svg { width: 18px; height: 18px; fill: currentColor; display: block; + flex-shrink: 0; } -.beep-action .beep-count { +.beep-count { font-variant-numeric: tabular-nums; + min-width: 20px; } -.beep-action-reply:hover { color: var(--beep-accent); background: rgba(29,155,240,0.10); } -.beep-action-like:hover { color: var(--beep-like); background: rgba(249, 24,128,0.10); } -.beep-action-share:hover { color: var(--beep-accent); background: rgba(29,155,240,0.10); } +/* Reply - blue */ +.beep-action-reply:hover { + color: var(--beep-accent); + background: rgba(29, 155, 240, 0.1); +} -/* Like state */ -.beep-action-like .beep-icon-filled { display: none; } -.beep-action-like .beep-icon-outline { display: inline-flex; } +/* Retweet - green */ +.beep-action-retweet:hover { + color: var(--beep-repost); + background: rgba(0, 186, 124, 0.1); +} -.beep-action-like.is-liked { +.beep-action.is-reposted { color: var(--beep-repost); } + +/* Like - pink */ +.beep-action-like:hover { color: var(--beep-like); + background: rgba(249, 24, 128, 0.1); } -.beep-action-like.is-liked .beep-icon-outline { display: none; } -.beep-action-like.is-liked .beep-icon-filled { display: inline-flex; } -/* Heart "pop" animation when newly liked */ +.beep-action.is-liked { color: var(--beep-like); } + +/* Bookmark - blue */ +.beep-action-bookmark:hover { + color: var(--beep-accent); + background: rgba(29, 155, 240, 0.1); +} + +.beep-action.is-bookmarked { color: var(--beep-accent); } + +/* Share - blue */ +.beep-action-share:hover { + color: var(--beep-accent); + background: rgba(29, 155, 240, 0.1); +} + +/* Heart pop animation */ .beep-action-like.beep-just-liked .beep-icon-filled { animation: beep-heart-pop 0.55s ease; } @keyframes beep-heart-pop { - 0% { transform: scale(0.5); } - 35% { transform: scale(1.35); } - 60% { transform: scale(0.85); } + 0% { transform: scale(0.85); } + 40% { transform: scale(1.35); } + 70% { transform: scale(0.9); } 100% { transform: scale(1); } } -/* ---------- Reply form ---------- */ -.beep-reply-form { - margin-top: 8px; - padding: 10px 0 4px; - border-top: 1px solid var(--beep-border); +/* Retweet spin animation */ +.beep-action-retweet.beep-just-retweeted svg { + animation: beep-retweet-spin 0.4s ease; } -.beep-reply-form .beep-input { - font-size: 16px; +@keyframes beep-retweet-spin { + 0% { transform: rotate(0deg) scale(1); } + 50% { transform: rotate(180deg) scale(1.2); } + 100% { transform: rotate(360deg) scale(1); } } -.beep-reply-form[hidden] { display: none; } +/* Media */ +.beep-media-attachments { + margin-top: 12px; + border-radius: 16px; + overflow: hidden; + border: 1px solid var(--beep-border); +} -.beep-pending { - padding: 12px; - background: var(--beep-hover); - color: var(--beep-text-secondary); - border-radius: 8px; +.beep-media-attachments img { + width: 100%; + height: auto; + display: block; + max-height: 400px; + object-fit: cover; +} + +/* Image grid */ +.beep-media-grid { + display: grid; + gap: 2px; + border-radius: 16px; + overflow: hidden; + border: 1px solid var(--beep-border); +} + +.beep-media-grid-2 { grid-template-columns: 1fr 1fr; } +.beep-media-grid-3 .beep-media-grid-item:first-child { grid-column: span 2; } +.beep-media-grid-4 { grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; } + +.beep-media-grid-item { + overflow: hidden; + aspect-ratio: 16 / 9; +} + +.beep-media-grid-item img { + width: 100%; + height: 100%; + object-fit: cover; + max-height: none; +} + +/* Polls */ +.beep-poll-container { + margin-top: 12px; + border: 1px solid var(--beep-border); + border-radius: 16px; + padding: 16px; + background: transparent; +} + +.beep-poll-question { + font-size: 15px; + font-weight: 700; + color: var(--beep-text); + margin-bottom: 12px; +} + +.beep-poll-options { + display: flex; + flex-direction: column; + gap: 8px; +} + +.beep-poll-option { + border: 1px solid var(--beep-border); + border-radius: 9999px; + padding: 10px 16px; + cursor: pointer; font-size: 14px; - text-align: center; + font-weight: 700; + color: var(--beep-text); + background: transparent; + transition: background-color 0.15s ease, border-color 0.15s ease; + position: relative; + overflow: hidden; } -/* ---------- Replies ---------- */ +.beep-poll-option:hover { + background: var(--beep-card-hover); + border-color: var(--beep-muted); +} + +.beep-poll-option.is-voted { border-color: var(--beep-accent); } + +.beep-poll-result { + border: 1px solid var(--beep-border); + border-radius: 9999px; + padding: 10px 16px; + font-size: 14px; + font-weight: 700; + color: var(--beep-text); + position: relative; + overflow: hidden; +} + +.beep-poll-result-bar { + position: absolute; + left: 0; + top: 0; + bottom: 0; + background: var(--beep-accent); + opacity: 0.3; + max-width: 100%; +} + +.beep-poll-result.is-voted { border-color: var(--beep-accent); } +.beep-poll-result-text { position: relative; z-index: 1; } +.beep-poll-end { font-size: 13px; color: var(--beep-muted); margin-top: 8px; font-weight: 400; } + +/* Quotes */ +.beep-quoted-container { + margin-top: 12px; + border: 1px solid var(--beep-border); + border-radius: 16px; + padding: 12px 16px; + background: transparent; +} + +.beep-quoted-post { text-decoration: none; color: inherit; } +.beep-quoted-post:hover { text-decoration: none; } + +.beep-quoted-meta { font-size: 13px; color: var(--beep-muted); margin-bottom: 4px; } +.beep-quoted-meta .beep-name { font-weight: 700; } + +.beep-quoted-text { + font-size: 14px; + color: var(--beep-text); + line-height: 1.4; + margin: 0; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; +} + +/* Voice note */ +.beep-voice-note { + margin-top: 12px; + display: flex; + align-items: center; + gap: 12px; + background: var(--beep-card-hover); + border-radius: 9999px; + padding: 10px 16px; + max-width: 400px; +} + +.beep-voice-play { + background: var(--beep-accent); + border: 0; + border-radius: 50%; + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + flex-shrink: 0; + padding: 0; +} + +.beep-voice-play svg { width: 14px; height: 14px; fill: #fff; } + +.beep-voice-waveform { + flex: 1; + height: 24px; + display: flex; + align-items: center; + gap: 2px; +} + +.beep-voice-bar { + width: 3px; + height: 100%; + background: var(--beep-accent); + border-radius: 9999px; + opacity: 0.6; +} + +.beep-voice-duration { font-size: 13px; color: var(--beep-muted); font-variant-numeric: tabular-nums; } + +/* Replies */ .beep-replies { margin-top: 8px; - padding-left: 24px; + padding-left: 0; border-left: 2px solid var(--beep-border); + margin-left: 24px; } .beep-reply { display: flex; gap: 10px; - padding: 10px 0; + padding: 10px 16px; border-bottom: 1px solid var(--beep-border); + background: var(--beep-bg); + transition: background-color 0.15s ease; } .beep-reply:last-child { border-bottom: 0; } +.beep-reply-body { flex: 1; min-width: 0; } +.beep-reply .beep-text { font-size: 14px; margin-bottom: 0; } +.beep-reply .beep-meta { font-size: 13px; margin-bottom: 0; } +.beep-reply .beep-avatar { width: 36px !important; height: 36px !important; min-width: 36px !important; max-width: 36px !important; } -.beep-reply-body { - flex: 1; - min-width: 0; +/* Reply form */ +.beep-reply-form { + margin-top: 8px; + padding: 12px 16px; + border-top: 1px solid var(--beep-border); + border-bottom: 1px solid var(--beep-border); + display: flex; + gap: 10px; + align-items: flex-start; + background: var(--beep-bg); } -.beep-reply .beep-text { - font-size: 14px; - margin-bottom: 0; +.beep-reply-form[hidden] { display: none; } +.beep-reply-form-body { flex: 1; min-width: 0; } + +.beep-feed .beep-reply-input { + width: 100% !important; + border: 0 !important; + outline: 0 !important; + resize: none !important; + background: transparent !important; + font-family: inherit !important; + font-size: 15px !important; + color: var(--beep-text) !important; + padding: 6px 0 !important; + min-height: 36px; + box-shadow: none !important; + caret-color: var(--beep-accent); } -/* ---------- New-post entry animation ---------- */ -.beep-new { - animation: beep-slide-in 0.4s ease; +.beep-feed .beep-reply-input::placeholder { color: var(--beep-muted) !important; } + +.beep-reply-footer { + display: flex; + justify-content: flex-end; + align-items: center; + margin-top: 8px; } +/* Animations */ +.beep-new { animation: beep-slide-in 0.3s ease; } + @keyframes beep-slide-in { - from { opacity: 0; transform: translateY(-6px); } + from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: translateY(0); } } -/* ---------- Mobile ---------- */ -@media (max-width: 640px) { - .beep-feed { - margin: 0 auto; - border-radius: 0; - border-left: 0; - border-right: 0; - } - .beep-actions { max-width: none; } +/* Post badge */ +.beep-post-badge { + width: 18px; + height: 18px; + flex-shrink: 0; + opacity: 0.5; + margin-left: 2px; } -/* ---------- Tip popover (for "Copied" etc) ---------- */ +.beep-post-badge:hover { opacity: 0.8; cursor: pointer; } + +/* Loading */ +.beep-loading { opacity: 0.5; pointer-events: none; } + +/* Mobile */ +@media (max-width: 640px) { + .beep-feed { margin: 0; border-left: 0; border-right: 0; } + .beep-post { padding: 12px 16px; } + .beep-actions { max-width: none; } + .beep-compose { padding: 12px 16px; } + .beep-feed-header { padding: 12px 16px; } +} + +/* Delete */ +.beep-delete-row { display: flex; justify-content: flex-end; margin-top: 6px; } + +.beep-delete-link { + background: transparent; + border: 0; + color: var(--beep-muted); + font-family: inherit; + font-size: 13px; + cursor: pointer; + padding: 4px 8px; + border-radius: 4px; + transition: color 0.15s ease, background-color 0.15s ease; +} + +.beep-delete-link:hover { color: #F4212E; background: rgba(244, 33, 46, 0.1); } + +/* Notice */ +.beep-notice { padding: 14px 20px; border-bottom: 1px solid var(--beep-border); font-size: 14px; } +.beep-notice p { margin: 0; color: var(--beep-muted); } +.beep-notice-banned { color: #F4212E; font-style: italic; } + +/* Header post button */ +.beep-post-btn-header { + background: var(--beep-accent); + color: #fff; + border: 0; + border-radius: 9999px; + padding: 8px 20px; + font-weight: 700; + font-size: 15px; + cursor: pointer; + font-family: inherit; + transition: background-color 0.15s ease, transform 0.1s ease; +} + +.beep-post-btn-header:hover { background: var(--beep-accent-hover); } +.beep-post-btn-header:active { transform: scale(0.96); } + +/* Back link */ +.beep-back-link { + display: flex; + align-items: center; + gap: 8px; + padding: 12px 20px; + border-bottom: 1px solid var(--beep-border); + background: var(--beep-bg); + color: var(--beep-text); + text-decoration: none; + font-weight: 700; + font-size: 19px; + transition: background-color 0.15s ease; +} + +.beep-back-link:hover { background: var(--beep-card-hover); color: var(--beep-text); text-decoration: none; } +.beep-back-link svg { width: 20px; height: 20px; fill: currentColor; } + +/* Thread modal */ +.beep-thread-modal { + position: fixed; + inset: 0; + z-index: 1000; + display: flex; + align-items: flex-start; + justify-content: center; + background: rgba(0, 0, 0, 0.4); + backdrop-filter: blur(4px); +} + +.beep-thread-modal[hidden] { display: none; } + +.beep-thread-content { + background: var(--beep-card-bg); + width: 100%; + max-width: 600px; + max-height: 90vh; + overflow-y: auto; + border-left: 1px solid var(--beep-border); + border-right: 1px solid var(--beep-border); +} + +.beep-thread-header { + display: flex; + align-items: center; + gap: 16px; + padding: 12px 20px; + border-bottom: 1px solid var(--beep-border); + position: sticky; + top: 0; + background: var(--beep-card-bg); + z-index: 1; +} + +.beep-thread-close { + background: transparent; + border: 0; + color: var(--beep-text); + cursor: pointer; + width: 36px; + height: 36px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + padding: 0; + transition: background-color 0.15s ease; +} + +.beep-thread-close:hover { background: var(--beep-card-hover); } +.beep-thread-close svg { width: 20px; height: 20px; fill: currentColor; } +.beep-thread-title { font-size: 18px; font-weight: 700; color: var(--beep-text); } + +/* Tooltip */ .beep-tip { position: absolute; margin-left: 8px; - padding: 2px 8px; + padding: 4px 10px; background: var(--beep-text); - color: #fff; + color: var(--beep-bg); border-radius: 4px; font-size: 12px; white-space: nowrap; + pointer-events: none; + z-index: 100; animation: beep-tip-fade 1.5s ease forwards; } @keyframes beep-tip-fade { - 0% { opacity: 0; } - 20% { opacity: 1; } + 0% { opacity: 0; transform: translateY(-4px); } + 20% { opacity: 1; transform: translateY(0); } 80% { opacity: 1; } 100% { opacity: 0; } } -/* ============================================================ - v1.4 — sort tabs, nested replies, Twitter polish - ============================================================ */ - -/* ---------- Sort tabs (Latest / Top) ---------- */ -.beep-feed .beep-tabs { - display: flex; - border-bottom: 1px solid var(--beep-border); - background: var(--beep-bg); +/* GIF */ +.beep-gif-embed { + margin-top: 12px; + border-radius: 12px; + overflow: hidden; + border: 1px solid var(--beep-border); } -.beep-feed .beep-tab { +.beep-gif-embed img { + width: 100%; + height: auto; + max-height: 400px; + object-fit: cover; + display: block; + margin: 0; + border-radius: 12px; +} + +/* Profile header */ +.beep-profile-header { padding: 16px 20px; border-bottom: 1px solid var(--beep-border); } +.beep-profile-name { font-size: 20px; font-weight: 800; color: var(--beep-text); margin-bottom: 2px; } +.beep-profile-meta { font-size: 14px; color: var(--beep-muted); } +.beep-profile-stats { display: flex; gap: 20px; margin-top: 12px; } +.beep-profile-stat { color: var(--beep-muted); font-size: 14px; cursor: pointer; } +.beep-profile-stat:hover { text-decoration: underline; } +.beep-profile-stat strong { color: var(--beep-text); font-weight: 700; } + +/* Tabs */ +.beep-tabs { + display: flex; + border-bottom: 1px solid var(--beep-border); + background: var(--beep-card-bg); +} + +.beep-tab { flex: 1; background: transparent; border: 0; @@ -464,22 +879,15 @@ font-family: inherit; font-size: 15px; font-weight: 700; - color: var(--beep-text-secondary); + color: var(--beep-muted); cursor: pointer; position: relative; transition: background-color 0.15s ease, color 0.15s ease; } -.beep-feed .beep-tab:hover { - background: var(--beep-hover); - color: var(--beep-text); -} - -.beep-feed .beep-tab.is-active { - color: var(--beep-text); -} - -.beep-feed .beep-tab.is-active::after { +.beep-tab:hover { background: var(--beep-card-hover); color: var(--beep-text); } +.beep-tab.is-active { color: var(--beep-text); } +.beep-tab.is-active::after { content: ''; position: absolute; left: 50%; @@ -491,1077 +899,145 @@ background: var(--beep-accent); } -/* ---------- Loading state on feed list ---------- */ -.beep-feed .beep-list.beep-loading { - opacity: 0.5; - pointer-events: none; - transition: opacity 0.15s ease; -} - -/* ---------- Action row variant for replies (smaller, no share) ---------- */ -.beep-feed .beep-actions-reply { - max-width: 240px; - gap: 24px; - margin-top: 2px; -} - -/* ---------- Nested replies: visual indent, capped at depth 2 ---------- */ -.beep-feed .beep-replies-nested { - margin-top: 8px; - margin-left: 12px; - padding-left: 14px; - border-left: 2px solid var(--beep-border); -} - -/* Anything deeper than depth 2 stops indenting more */ -.beep-feed .beep-depth-2 .beep-replies-nested, -.beep-feed .beep-depth-3 .beep-replies-nested, -.beep-feed .beep-depth-4 .beep-replies-nested { - margin-left: 0; - padding-left: 8px; -} - -/* ---------- Focus rings on text inputs ---------- */ +/* Focus */ .beep-feed .beep-input:focus, -.beep-feed .beep-input:focus-visible { +.beep-feed .beep-input:focus-visible, +.beep-feed .beep-reply-input:focus, +.beep-feed .beep-reply-input:focus-visible { outline: 0 !important; box-shadow: none !important; } -.beep-feed .beep-compose:focus-within, -.beep-feed .beep-reply-form:focus-within { - background: linear-gradient(to bottom, transparent, rgba(29, 155, 240, 0.04)); - transition: background-color 0.15s ease; +.beep-feed .beep-compose:focus-within { background: rgba(29, 155, 240, 0.03); } + +/* Hidden */ +[hidden] { display: none !important; } + + +/* Magic Login Form */ +.beep-magic-login { + padding: 24px; + display: flex; + justify-content: center; } -/* ---------- Buttons: refine to match Twitter's "Post" button ---------- */ -.beep-feed .beep-post-button, -.beep-feed .beep-reply-submit { - font-size: 14px; - padding: 7px 18px; - font-weight: 700; - letter-spacing: -0.1px; +.beep-magic-login-card { + background: #192734; + border-radius: 16px; + padding: 32px; + width: 100%; + max-width: 400px; + text-align: center; } -/* ---------- Tighter post hover ---------- */ -.beep-feed .beep-post { - transition: background-color 0.15s ease; +.beep-magic-login-header { + margin-bottom: 24px; } -/* ---------- Refine action icons size ---------- */ -.beep-feed .beep-action { - padding: 4px 6px; - font-size: 13px; -} -.beep-feed .beep-action svg { - width: 17px; - height: 17px; +.beep-magic-login-header svg { + margin-bottom: 16px; } -/* ---------- Reply rows: tighten spacing ---------- */ -.beep-feed .beep-reply { - padding: 10px 0 8px; +.beep-magic-login-header h3 { + color: #E7E9EA; + font-size: 20px; + font-weight: 700; + margin: 0 0 8px 0; } -/* Make sure reply meta + actions feel cohesive */ -.beep-feed .beep-reply .beep-text { - margin-bottom: 4px; +.beep-magic-login-header p { + color: #71767B; + font-size: 14px; + margin: 0; } -/* ---------- Per-post Beep badge (top-right of each post/reply) ---------- */ -.beep-feed .beep-post-badge { - width: 20px; - height: 20px; - flex-shrink: 0; - display: block; - opacity: 0.75; - margin-left: 4px; - transition: opacity 0.15s ease; -} -.beep-feed .beep-post-badge:hover { - opacity: 1; -} -.beep-feed .beep-post-badge-sm { - width: 16px; - height: 16px; -} - -/* ---------- Delete row (bottom of post/reply, admin only) ---------- */ -.beep-feed .beep-delete-row { - display: flex; - justify-content: flex-end; - margin-top: 6px; -} - -.beep-feed .beep-delete-link { - background: transparent; - border: 0; - color: var(--beep-text-secondary); - font-family: inherit; - font-size: 22px; - line-height: 1; - font-weight: 400; - cursor: pointer; - width: 28px; - height: 28px; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - padding: 0; - transition: color 0.15s ease, background-color 0.15s ease, transform 0.1s ease; -} - -.beep-feed .beep-delete-link:hover { - color: #F4212E; - background: rgba(244, 33, 46, 0.10); -} - -.beep-feed .beep-delete-link:active { - color: #F4212E; - background: rgba(244, 33, 46, 0.25); - transform: scale(0.92); -} - -/* Override earlier 20/16 badge sizes with bigger 32/24 */ -.beep-feed .beep-post-badge { - width: 32px; - height: 32px; -} -.beep-feed .beep-post-badge-sm { - width: 24px; - height: 24px; -} - -/* ============================================================ - v1.4.1 — theme-busting overrides - Many WordPress themes style ALL + + + + + + 'POST', 'callback' => [__CLASS__, 'submit_beep'], - 'permission_callback' => ['Beep_Likes', 'can_interact'], + 'permission_callback' => [Beep_Likes::class, 'can_interact'], ]); + register_rest_route('beep/v1', '/reply/(?P\d+)', [ 'methods' => 'POST', 'callback' => [__CLASS__, 'submit_reply'], - 'permission_callback' => ['Beep_Likes', 'can_interact'], + 'permission_callback' => [Beep_Likes::class, 'can_interact'], ]); - register_rest_route('beep/v1', '/thread/(?P\d+)', [ - 'methods' => 'GET', - 'callback' => [__CLASS__, 'get_thread_api'], - 'permission_callback' => '__return_true', - ]); - register_rest_route('beep/v1', '/delete/(?P\d+)', [ + + register_rest_route('beep/v1', '/beep/(?P\d+)', [ 'methods' => 'DELETE', 'callback' => [__CLASS__, 'delete_beep'], - 'permission_callback' => ['Beep_Likes', 'can_interact'], + 'permission_callback' => function ($req) { + return current_user_can('delete_post', (int) $req['id']); + }, ]); + register_rest_route('beep/v1', '/reply/(?P\d+)', [ 'methods' => 'DELETE', 'callback' => [__CLASS__, 'delete_reply'], - 'permission_callback' => ['Beep_Likes', 'can_interact'], + 'permission_callback' => function () { + return current_user_can('moderate_comments'); + }, ]); } public static function submit_beep($request) { - $content = sanitize_textarea_field($request->get_param('content')); - if (empty($content)) { - return new WP_Error('empty_content', 'Beep cannot be empty.', ['status' => 400]); + $content = trim((string) $request->get_param('content')); + $length = function_exists('mb_strlen') ? mb_strlen($content) : strlen($content); + + if ($content === '' || $length > BEEP_CHAR_LIMIT) { + return new WP_Error('beep_invalid_content', + 'Content must be 1-' . BEEP_CHAR_LIMIT . ' characters.', + ['status' => 400]); } + $post_id = wp_insert_post([ - 'post_type' => 'beep', - 'post_content' => $content, + 'post_type' => Beep_CPT::POST_TYPE, 'post_status' => 'publish', 'post_author' => get_current_user_id(), + 'post_content' => wp_kses($content, []), + 'post_title' => wp_trim_words($content, 8, '...'), + ], true); + + if (is_wp_error($post_id)) { + return $post_id; + } + + return rest_ensure_response([ + 'id' => $post_id, + 'html' => beep_render_post(get_post($post_id)), ]); - if (is_wp_error($post_id)) { return $post_id; } - return rest_ensure_response(['id' => $post_id, 'url' => get_permalink($post_id)]); } public static function submit_reply($request) { - $post_id = (int) $request['id']; - $parent = get_post($post_id); - if (!$parent || $parent->post_type !== 'beep') { - return new WP_Error('invalid_post', 'Invalid beep post.', ['status' => 400]); + $post_id = (int) $request['id']; + $content = trim((string) $request->get_param('content')); + $parent_id = (int) $request->get_param('parent'); + $length = function_exists('mb_strlen') ? mb_strlen($content) : strlen($content); + + if (get_post_type($post_id) !== Beep_CPT::POST_TYPE) { + return new WP_Error('beep_not_a_beep', 'Not a beep.', ['status' => 400]); } - $content = sanitize_textarea_field($request->get_param('content')); - if (empty($content)) { - return new WP_Error('empty_content', 'Reply cannot be empty.', ['status' => 400]); + if ($content === '' || $length > BEEP_CHAR_LIMIT) { + return new WP_Error('beep_invalid_content', + 'Reply must be 1-' . BEEP_CHAR_LIMIT . ' characters.', + ['status' => 400]); } + // Validate the parent comment belongs to this post if provided. + if ($parent_id > 0) { + $parent = get_comment($parent_id); + if (!$parent || (int) $parent->comment_post_ID !== $post_id) { + return new WP_Error('beep_bad_parent', 'Invalid parent.', ['status' => 400]); + } + } + + $user = wp_get_current_user(); + + $approved = get_option('comment_moderation') ? 0 : 1; + if (get_option('comment_previously_approved')) { + $prev = get_comments([ + 'user_id' => $user->ID, + 'status' => 'approve', + 'count' => true, + ]); + if (!$prev) { + $approved = 0; + } + } + $comment_id = wp_insert_comment([ - 'comment_post_ID' => $post_id, - 'comment_content' => $content, - 'comment_author' => get_current_user_id() ? null : 'Anonymous', - 'comment_type' => 'comment', - 'user_id' => get_current_user_id(), + 'comment_post_ID' => $post_id, + 'comment_author' => $user->display_name, + 'comment_author_email' => $user->user_email, + 'comment_author_url' => '', + 'comment_content' => wp_kses($content, []), + 'comment_type' => 'comment', + 'comment_parent' => $parent_id, + 'user_id' => $user->ID, + 'comment_approved' => $approved, ]); + if (!$comment_id) { - return new WP_Error('insert_failed', 'Could not save reply.', ['status' => 500]); + return new WP_Error('beep_insert_failed', 'Reply could not be saved.', ['status' => 500]); } - return rest_ensure_response(['id' => $comment_id]); + + $comment = get_comment($comment_id); + $pending = ($comment->comment_approved == 0); + + // Depth of this reply for client-side indenting. + $depth = 0; + $cur = $comment; + while ($cur && (int) $cur->comment_parent > 0 && $depth < 10) { + $cur = get_comment((int) $cur->comment_parent); + $depth++; + } + $can_interact = Beep_Likes::can_interact(); + + return rest_ensure_response([ + 'id' => $comment_id, + 'parent' => (int) $comment->comment_parent, + 'pending' => $pending, + 'html' => $pending ? '' : beep_render_reply($comment, [], $depth, $post_id, $can_interact), + ]); } public static function delete_beep($request) { $post_id = (int) $request['id']; - $post = get_post($post_id); - if (!$post || $post->post_type !== 'beep') { - return new WP_Error('invalid_post', 'Invalid beep.', ['status' => 400]); + if (get_post_type($post_id) !== Beep_CPT::POST_TYPE) { + return new WP_Error('beep_not_a_beep', 'Not a beep.', ['status' => 400]); } $deleted = wp_delete_post($post_id, true); if (!$deleted) { - return new WP_Error('delete_failed', 'Could not delete.', ['status' => 500]); + return new WP_Error('beep_delete_failed', 'Could not delete.', ['status' => 500]); } return rest_ensure_response(['deleted' => true]); } @@ -90,7 +155,7 @@ class Beep_Replies { $comment_id = (int) $request['id']; $deleted = wp_delete_comment($comment_id, true); if (!$deleted) { - return new WP_Error('delete_failed', 'Could not delete.', ['status' => 500]); + return new WP_Error('beep_delete_failed', 'Could not delete.', ['status' => 500]); } return rest_ensure_response(['deleted' => true]); } @@ -107,110 +172,4 @@ class Beep_Replies { public static function reply_count($post_id) { return (int) get_comments_number($post_id); } - - public static function get_thread($post_id, $depth = 0, $max_depth = 10) { - if ($depth > $max_depth) return []; - $post = get_post($post_id); - if (!$post || $post->post_type !== 'beep') return []; - $thread = [self::format_beep_reply($post, $depth)]; - $replies = get_comments([ - 'post_id' => $post_id, 'status' => 'approve', 'order' => 'ASC', - 'type' => 'comment', 'number' => 100, - ]); - foreach ($replies as $reply) { - if ($depth < $max_depth) { - $thread = array_merge($thread, self::get_comment_thread($reply->comment_ID, $depth + 1, $max_depth)); - } - } - return $thread; - } - - private static function get_comment_thread($comment_id, $depth, $max_depth) { - if ($depth > $max_depth) return []; - $comment = get_comment($comment_id); - if (!$comment) return []; - $result = [self::format_comment_reply($comment, $depth)]; - $children = get_comments([ - 'parent' => $comment_id, 'status' => 'approve', 'order' => 'ASC', - 'type' => 'comment', 'number' => 100, - ]); - foreach ($children as $child) { - $result = array_merge($result, self::get_comment_thread($child->comment_ID, $depth + 1, $max_depth)); - } - return $result; - } - - public static function get_thread_api($request) { - $post_id = (int) $request['post_id']; - $thread = self::get_thread($post_id); - return rest_ensure_response(['thread' => $thread]); - } - - public static function format_beep_reply($post, $depth = 0) { - $author_id = $post->post_author; - $author = get_userdata($author_id); - $avatar = get_user_meta($author_id, 'beep_avatar', true); - if (!$avatar) { - $avatar = 'https://www.gravatar.com/avatar/' . md5(strtolower($author->user_email ?? '')) . '?s=48&d=mp'; - } - return [ - 'id' => $post->ID, - 'parent_id' => $post->post_parent ?: null, - 'is_post' => true, - 'depth' => $depth, - 'content' => $post->post_content, - 'time_ago' => self::time_ago(strtotime($post->post_date)), - 'like_count' => Beep_Likes::get_count($post->ID, 'post'), - 'reply_count' => self::reply_count($post->ID), - 'author' => [ - 'id' => $author_id, - 'name' => $author->display_name ?? 'Unknown', - 'username' => $author->user_login ?? '', - 'avatar' => $avatar, - ], - ]; - } - - public static function format_comment_reply($comment, $depth = 0) { - $author_id = (int) $comment->user_id; - $author = $author_id ? get_userdata($author_id) : null; - if ($author_id) { - $avatar = get_user_meta($author_id, 'beep_avatar', true); - if (!$avatar) { - $avatar = 'https://www.gravatar.com/avatar/' . md5(strtolower($author->user_email ?? '')) . '?s=48&d=mp'; - } - $name = $author->display_name ?? 'Unknown'; - $username = $author->user_login ?? ''; - } else { - $avatar = 'https://www.gravatar.com/avatar/?s=48&d=mp'; - $name = esc_html($comment->comment_author); - $username = ''; - } - return [ - 'id' => $comment->comment_ID, - 'parent_id' => (int) $comment->comment_parent ?: null, - 'is_post' => false, - 'depth' => $depth, - 'content' => $comment->comment_content, - 'time_ago' => self::time_ago(strtotime($comment->comment_date)), - 'like_count' => Beep_Likes::get_count($comment->comment_ID, 'reply'), - 'reply_count' => 0, - 'author' => [ - 'id' => $author_id, - 'name' => $name, - 'username' => $username, - 'avatar' => $avatar, - ], - ]; - } - - public static function time_ago($timestamp) { - $diff = time() - $timestamp; - if ($diff < 0) $diff = 0; - if ($diff < 60) return $diff . 's'; - if ($diff < 3600) return floor($diff / 60) . 'm'; - if ($diff < 86400) return floor($diff / 3600) . 'h'; - if ($diff < 604800) return floor($diff / 86400) . 'd'; - return gmdate('M j', $timestamp); - } } diff --git a/includes/class-shortcode.php b/includes/class-shortcode.php index a0653c8..466ca04 100755 --- a/includes/class-shortcode.php +++ b/includes/class-shortcode.php @@ -86,6 +86,11 @@ class Beep_Shortcode { $posts = ($sort === 'top') ? self::get_top_beeps($limit) : self::get_latest_beeps($limit); $has_posts = !empty($posts); + // Show magic login form if not logged in + if (!is_user_logged_in()) { + return Beep_Magic_Links::render_login_form() . beep_render_public_feed($posts, $atts); + } + ob_start(); ?>
@@ -135,7 +140,19 @@ class Beep_Shortcode {
var BeepConfig=%s;', + json_encode([ + 'restUrl' => esc_url_raw(rest_url('beep/v1/')), + 'nonce' => wp_create_nonce('wp_rest'), + 'charLimit' => BEEP_CHAR_LIMIT, + 'loginUrl' => esc_url_raw(wp_login_url(is_singular() ? get_permalink() : home_url('/'))), + 'isLoggedIn' => is_user_logged_in(), + ]) + ); + $beep_script = ''; + return ob_get_clean() . $beep_config . $beep_script . beep_render_thread_modal() . beep_render_gif_modal(); } /** diff --git a/includes/create_poll_tables.php b/includes/create_poll_tables.php new file mode 100644 index 0000000..3ed8028 --- /dev/null +++ b/includes/create_poll_tables.php @@ -0,0 +1,56 @@ +prefix . "beep_polls"; +$votes_table = $wpdb->prefix . "beep_poll_votes"; +$charset = $wpdb->get_charset_collate(); + +$polls_sql = "CREATE TABLE IF NOT EXISTS $polls_table ( + id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + beep_id BIGINT(20) UNSIGNED NOT NULL, + question TEXT NOT NULL, + options TEXT NOT NULL, + votes TEXT NOT NULL DEFAULT '[]', + total_votes BIGINT(20) UNSIGNED NOT NULL DEFAULT 0, + ends_at DATETIME NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id), + KEY beep_id (beep_id) +) $charset;"; + +$votes_sql = "CREATE TABLE IF NOT EXISTS $votes_table ( + id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + poll_id BIGINT(20) UNSIGNED NOT NULL, + user_id BIGINT(20) UNSIGNED NOT NULL, + option_index INT NOT NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id), + UNIQUE KEY poll_user (poll_id, user_id), + KEY poll_id (poll_id), + KEY user_id (user_id) +) $charset;"; + +require_once(ABSPATH . "wp-admin/includes/upgrade.php"); +$result1 = dbDelta($polls_sql); +$result2 = dbDelta($votes_sql); +echo "Tables created: $polls_table, $votes_table"; +echo " +Result1: " . print_r($result1, true); +echo " +Result2: " . print_r($result2, true); +?> \ No newline at end of file diff --git a/includes/helpers.php b/includes/helpers.php index 8a911bb..9a620aa 100755 --- a/includes/helpers.php +++ b/includes/helpers.php @@ -7,8 +7,8 @@ if (!defined('ABSPATH')) { * Wordmark (bird + "Beep!") shown at the top of the feed. */ function beep_logo_svg() { - $src = esc_url(BEEP_URL . 'assets/beep-wordmark.png'); - return ''; + $src = esc_url(BEEP_URL . 'assets/beep-wordmark.jpg'); + return 'Messages from'; } /** @@ -241,6 +241,12 @@ function beep_render_post($post) { 0 ? esc_html($reply_count) : ''; ?> + - - - + - - -
' . ''; } + + +function beep_render_public_feed($posts, $atts) { + ob_start(); + ?> +
+
+ + + + + +
+ +
+ + + + Sign in to post +
+ + +
+ + + +
+ +
No beeps yet.
+ +
+