diff --git a/deploy/flarum/apply-community-theme-update.sh b/deploy/flarum/apply-community-theme-update.sh new file mode 100644 index 0000000..1ff648a --- /dev/null +++ b/deploy/flarum/apply-community-theme-update.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ "${EUID}" -ne 0 ]]; then + echo "run as root" >&2 + exit 1 +fi + +source_root="${1:-}" +change_id="${2:-}" +app_root="/var/www/html" +extension_root="$app_root/extensions/ablecloud-community-theme" +backup_root="/var/backups/techflow-flarum/$change_id" + +if [[ -z "$source_root" || -z "$change_id" ]]; then + echo "usage: $0 /tmp/ablecloud-community-theme- theme-" >&2 + exit 1 +fi + +source_root="$(realpath "$source_root")" +extension_root="$(realpath "$extension_root")" + +[[ "$source_root" == /tmp/ablecloud-community-theme-* ]] || { + echo "unexpected source path: $source_root" >&2 + exit 1 +} +[[ "$extension_root" == "/var/www/html/extensions/ablecloud-community-theme" ]] || { + echo "unexpected extension path: $extension_root" >&2 + exit 1 +} +[[ "$backup_root" == /var/backups/techflow-flarum/theme-* ]] || { + echo "unexpected backup path: $backup_root" >&2 + exit 1 +} +[[ -f "$source_root/resources/less/forum.less" ]] || { + echo "theme source is incomplete" >&2 + exit 1 +} + +install -d -m 0750 "$backup_root" +cp -a "$extension_root" "$backup_root/extension-before" +cp -a "$app_root/vendor/ablecloud/community-theme" "$backup_root/vendor-before" +cp -a "$app_root/composer.json" "$app_root/composer.lock" "$backup_root/" +cp -a "$app_root/public/assets" "$backup_root/assets-before" +if [[ -d "$app_root/storage/locale" ]]; then + cp -a "$app_root/storage/locale" "$backup_root/locale-before" +fi + +rsync -a --delete "$source_root/" "$extension_root/" +# SCP staging directories can be intentionally private (0700). Normalize the +# deployed path so Composer and PHP-FPM can traverse the path repository. +chown -R www-data:www-data "$extension_root" +find "$extension_root" -type d -exec chmod 0755 {} + +find "$extension_root" -type f -exec chmod 0644 {} + +chmod 0755 "$extension_root/tools/warm-korean-locale.php" +cd "$app_root" +sudo -u www-data env COMPOSER_HOME=/tmp/techflow-composer \ + composer require ablecloud/community-theme:@dev --with-dependencies --no-interaction +sudo -u www-data php flarum extension:enable ablecloud-community-theme +sudo -u www-data php flarum cache:clear +sudo -u www-data env TECHFLOW_ALLOWED_FLARUM_ROOT="$app_root" \ + php vendor/ablecloud/community-theme/tools/warm-korean-locale.php "$app_root" +systemctl restart php8.3-fpm nginx + +systemctl is-active --quiet nginx +systemctl is-active --quiet php8.3-fpm +systemctl is-active --quiet mariadb +curl --fail --silent --show-error --output /dev/null \ + -H 'Host: community.ablecloud.io' \ + -H 'X-Forwarded-Proto: https' \ + http://127.0.0.1/ +grep -q 'UserPage-nav > ul.affix' \ + "$app_root/vendor/ablecloud/community-theme/resources/less/forum.less" +grep -q 'ul.affix' "$app_root/public/assets/forum.css" + +printf 'BACKUP_ROOT=%s\n' "$backup_root" +printf 'SOURCE_SHA256=' +sha256sum "$source_root/resources/less/forum.less" | awk '{print $1}' +printf 'VENDOR_SHA256=' +sha256sum "$app_root/vendor/ablecloud/community-theme/resources/less/forum.less" | awk '{print $1}' +printf 'LOCAL_HTTP=200\n' +printf 'RESULT=PASS\n' diff --git a/deploy/flarum/extensions/ablecloud-community-theme/js/dist/forum.js b/deploy/flarum/extensions/ablecloud-community-theme/js/dist/forum.js index 3be1ac9..bd52c32 100644 --- a/deploy/flarum/extensions/ablecloud-community-theme/js/dist/forum.js +++ b/deploy/flarum/extensions/ablecloud-community-theme/js/dist/forum.js @@ -5,6 +5,580 @@ var forumApp = forumAppModule.default || forumAppModule; var relativeDatePattern = /(?:๋ฐฉ๊ธˆ|์ „$|ํ›„$)/; var absoluteDatePattern = /^(\d{4})-(\d{2})-(\d{2})/; + var discussionItemEnhancedAttribute = 'data-ablecloud-post-structure'; + var discussionDetailEnhancedAttribute = 'data-ablecloud-reading-flow'; + var discussionListSnapshotKey = 'ablecloud-community-discussion-list'; + var fallbackPaneAttribute = 'data-ablecloud-fallback-pane'; + var fallbackPaneActive = false; + var infiniteLoadObserver = null; + var infiniteLoadTarget = null; + var emojiPalette = null; + var emojiPaletteTrigger = null; + var emojiSelectionRange = null; + var commonEmojis = [ + ['๐Ÿ˜€', '์›ƒ๋Š” ์–ผ๊ตด'], + ['๐Ÿ˜ƒ', 'ํ™œ์ง ์›ƒ๋Š” ์–ผ๊ตด'], + ['๐Ÿ˜Š', '๋ฏธ์†Œ'], + ['๐Ÿ˜‚', '๊ธฐ์จ์˜ ๋ˆˆ๋ฌผ'], + ['๐Ÿ™‚', '์‚ด์ง ๋ฏธ์†Œ'], + ['๐Ÿ˜‰', '์œ™ํฌ'], + ['๐Ÿ˜', '๋ฐ˜ํ•จ'], + ['๐Ÿค”', '์ƒ๊ฐ ์ค‘'], + ['๐Ÿ˜ฎ', '๋†€๋žŒ'], + ['๐Ÿ˜ข', '์Šฌํ””'], + ['๐Ÿ˜ญ', '์šธ์Œ'], + ['๐Ÿ˜…', '์•ˆ๋„'], + ['๐Ÿ˜Ž', '๋ฉ‹์ง'], + ['๐Ÿ‘', '์ข‹์•„์š”'], + ['๐Ÿ‘Ž', '์‹ซ์–ด์š”'], + ['๐Ÿ‘', '๋ฐ•์ˆ˜'], + ['๐Ÿ™', '๋ถ€ํƒ๊ณผ ๊ฐ์‚ฌ'], + ['โœ…', 'ํ™•์ธ'], + ['โ—', '์ค‘์š”'], + ['โš ๏ธ', '์ฃผ์˜'], + ['๐Ÿ’ก', '์•„์ด๋””์–ด'], + ['๐ŸŽ‰', '์ถ•ํ•˜'], + ['โค๏ธ', 'ํ•˜ํŠธ'], + ['๐Ÿ”ฅ', '๋ถˆ๊ฝƒ'], + ]; + + function disconnectInfiniteDiscussionLoading() { + if (infiniteLoadObserver) { + infiniteLoadObserver.disconnect(); + } + + infiniteLoadObserver = null; + infiniteLoadTarget = null; + } + + function enhanceInfiniteDiscussionLoading(root) { + var scrollRoot = root.querySelector('.App--index .IndexPage-results'); + var loadMore = root.querySelector('.App--index .DiscussionList-loadMore'); + var button = loadMore && loadMore.querySelector('button'); + + if (!scrollRoot || !loadMore || !button || typeof window.IntersectionObserver !== 'function') { + disconnectInfiniteDiscussionLoading(); + return; + } + + if (loadMore.getAttribute('data-ablecloud-auto-load') !== 'true') { + loadMore.setAttribute('data-ablecloud-auto-load', 'true'); + } + + if (infiniteLoadTarget === loadMore) { + return; + } + + disconnectInfiniteDiscussionLoading(); + infiniteLoadTarget = loadMore; + infiniteLoadObserver = new window.IntersectionObserver( + function (entries) { + entries.forEach(function (entry) { + var currentButton = entry.target.querySelector('button'); + + if ( + !entry.isIntersecting || + !currentButton || + currentButton.disabled || + entry.target.getAttribute('data-ablecloud-loading') === 'true' + ) { + return; + } + + entry.target.setAttribute('data-ablecloud-loading', 'true'); + currentButton.click(); + + window.setTimeout(function () { + if (entry.target.isConnected) { + entry.target.removeAttribute('data-ablecloud-loading'); + } + }, 750); + }); + }, + { + root: scrollRoot, + rootMargin: '0px 0px 120px 0px', + threshold: 0.01, + } + ); + infiniteLoadObserver.observe(loadMore); + } + + function closeEmojiPalette() { + if (emojiPalette && emojiPalette.parentNode) { + emojiPalette.parentNode.removeChild(emojiPalette); + } + + if (emojiPaletteTrigger) { + emojiPaletteTrigger.setAttribute('aria-expanded', 'false'); + } + + emojiPalette = null; + emojiPaletteTrigger = null; + emojiSelectionRange = null; + } + + function rememberEmojiCaret(editor) { + var selection = window.getSelection(); + + if (selection && selection.rangeCount) { + var range = selection.getRangeAt(0); + + if (editor.contains(range.commonAncestorContainer)) { + return range.cloneRange(); + } + } + + var fallbackRange = document.createRange(); + fallbackRange.selectNodeContents(editor); + fallbackRange.collapse(false); + return fallbackRange; + } + + function insertEmojiAtCaret(editor, emoji) { + if (!editor || !emoji) { + return; + } + + var selection = window.getSelection(); + var range = emojiSelectionRange && emojiSelectionRange.startContainer.isConnected + ? emojiSelectionRange + : rememberEmojiCaret(editor); + + editor.focus(); + + if (selection) { + selection.removeAllRanges(); + selection.addRange(range); + } + + var value = emoji + ' '; + var inserted = false; + + try { + inserted = document.execCommand('insertText', false, value); + } catch (error) { + inserted = false; + } + + if (!inserted) { + range.deleteContents(); + var textNode = document.createTextNode(value); + range.insertNode(textNode); + range.setStartAfter(textNode); + range.collapse(true); + if (selection) { + selection.removeAllRanges(); + selection.addRange(range); + } + editor.dispatchEvent(new Event('input', { bubbles: true })); + } + } + + function openEmojiPalette(trigger) { + var composer = trigger.closest('.Composer'); + var editor = composer && composer.querySelector('.TextEditor-editor[contenteditable="true"]'); + + if (!composer || !editor) { + return; + } + + closeEmojiPalette(); + document.querySelectorAll('.EmojiDropdown').forEach(function (dropdown) { + dropdown.remove(); + }); + + emojiSelectionRange = rememberEmojiCaret(editor); + emojiPaletteTrigger = trigger; + trigger.setAttribute('aria-expanded', 'true'); + + var palette = document.createElement('div'); + palette.className = 'ablecloud-EmojiPicker'; + palette.setAttribute('role', 'dialog'); + palette.setAttribute('aria-label', '์ด๋ชจ์ง€ ์„ ํƒ'); + + var header = document.createElement('div'); + header.className = 'ablecloud-EmojiPicker-header'; + + var title = document.createElement('strong'); + title.textContent = '์ด๋ชจ์ง€ ์„ ํƒ'; + header.appendChild(title); + + var closeButton = document.createElement('button'); + closeButton.type = 'button'; + closeButton.className = 'ablecloud-EmojiPicker-close'; + closeButton.setAttribute('data-ablecloud-emoji-close', 'true'); + closeButton.setAttribute('aria-label', '์ด๋ชจ์ง€ ์„ ํƒ์ฐฝ ๋‹ซ๊ธฐ'); + closeButton.textContent = 'ร—'; + header.appendChild(closeButton); + palette.appendChild(header); + + var help = document.createElement('p'); + help.className = 'ablecloud-EmojiPicker-help'; + help.textContent = '์‚ฝ์ž…ํ•  ์ด๋ชจ์ง€๋ฅผ ์„ ํƒํ•˜์„ธ์š”.'; + palette.appendChild(help); + + var grid = document.createElement('div'); + grid.className = 'ablecloud-EmojiPicker-grid'; + + commonEmojis.forEach(function (item) { + var button = document.createElement('button'); + button.type = 'button'; + button.className = 'ablecloud-EmojiPicker-option'; + button.setAttribute('data-ablecloud-emoji', item[0]); + button.setAttribute('aria-label', item[1]); + button.title = item[1]; + button.textContent = item[0]; + grid.appendChild(button); + }); + + palette.appendChild(grid); + composer.appendChild(palette); + emojiPalette = palette; + } + + function handleEmojiPaletteClick(event) { + var trigger = event.target.closest && event.target.closest('.Composer button[aria-label="์ด๋ชจ์ง€ ์‚ฝ์ž…"]'); + + if (trigger) { + event.preventDefault(); + event.stopImmediatePropagation(); + + if (emojiPalette) { + closeEmojiPalette(); + } else { + openEmojiPalette(trigger); + } + + return; + } + + var choice = event.target.closest && event.target.closest('[data-ablecloud-emoji]'); + + if (choice && emojiPalette && emojiPalette.contains(choice)) { + event.preventDefault(); + event.stopImmediatePropagation(); + var editor = emojiPalette.closest('.Composer').querySelector('.TextEditor-editor[contenteditable="true"]'); + insertEmojiAtCaret(editor, choice.getAttribute('data-ablecloud-emoji')); + closeEmojiPalette(); + return; + } + + if (event.target.closest && event.target.closest('[data-ablecloud-emoji-close="true"]')) { + event.preventDefault(); + event.stopImmediatePropagation(); + closeEmojiPalette(); + return; + } + + if (emojiPalette && !emojiPalette.contains(event.target)) { + closeEmojiPalette(); + } + } + + function handleEmojiPaletteKeydown(event) { + if (event.key === 'Escape' && emojiPalette) { + event.preventDefault(); + closeEmojiPalette(); + } + } + + function formatDiscussionCreatedAt(createdAt) { + if (!(createdAt instanceof Date) || Number.isNaN(createdAt.getTime())) { + return ''; + } + + var elapsed = Math.max(0, Date.now() - createdAt.getTime()); + var minute = 60 * 1000; + var hour = 60 * minute; + var day = 24 * hour; + + if (elapsed < minute) { + return '๋ฐฉ๊ธˆ'; + } + + if (elapsed < hour) { + return Math.floor(elapsed / minute) + '๋ถ„ ์ „'; + } + + if (elapsed < day) { + return Math.floor(elapsed / hour) + '์‹œ๊ฐ„ ์ „'; + } + + if (elapsed < 7 * day) { + return Math.floor(elapsed / day) + '์ผ ์ „'; + } + + return String(createdAt.getFullYear()).slice(-2) + '๋…„ ' + (createdAt.getMonth() + 1) + '์›” ' + createdAt.getDate() + '์ผ'; + } + + function appendMetaPart(meta, className, text) { + if (!text) { + return; + } + + if (meta.childElementCount) { + var separator = document.createElement('span'); + separator.className = 'ablecloud-DiscussionMeta-separator'; + separator.setAttribute('aria-hidden', 'true'); + separator.textContent = 'ยท'; + meta.appendChild(separator); + } + + var part = document.createElement('span'); + part.className = className; + part.textContent = text; + meta.appendChild(part); + } + + function buildDiscussionSummary(discussion) { + var firstPost = discussion && typeof discussion.firstPost === 'function' ? discussion.firstPost() : null; + + if (!firstPost || typeof firstPost.contentPlain !== 'function') { + return ''; + } + + return String(firstPost.contentPlain() || '') + .replace(/\s+/g, ' ') + .trim(); + } + + function findDiscussionThumbnail(discussion) { + var firstPost = discussion && typeof discussion.firstPost === 'function' ? discussion.firstPost() : null; + var contentHtml = firstPost && typeof firstPost.contentHtml === 'function' ? firstPost.contentHtml() : ''; + + if (!contentHtml) { + return null; + } + + var template = document.createElement('template'); + template.innerHTML = String(contentHtml); + var sourceImage = template.content.querySelector('img[src]'); + + if (!sourceImage) { + return null; + } + + var resolvedUrl; + + try { + resolvedUrl = new URL(sourceImage.getAttribute('src'), window.location.origin); + } catch (error) { + return null; + } + + if (!['http:', 'https:'].includes(resolvedUrl.protocol)) { + return null; + } + + return { + src: resolvedUrl.href, + alt: sourceImage.getAttribute('alt') || sourceImage.getAttribute('title') || '๊ฒŒ์‹œ๋ฌผ ์ฒจ๋ถ€ ์ด๋ฏธ์ง€ ๋ฏธ๋ฆฌ๋ณด๊ธฐ', + }; + } + + function enhanceDiscussionListItems(root) { + root.querySelectorAll('.DiscussionList-discussions > li[data-id]').forEach(function (listItem) { + if (listItem.getAttribute(discussionItemEnhancedAttribute) === 'true') { + return; + } + + var discussion = forumApp.store.getById('discussions', listItem.getAttribute('data-id')); + var content = listItem.querySelector('.DiscussionListItem-content'); + var main = content && content.querySelector('.DiscussionListItem-main'); + var title = main && main.querySelector('.DiscussionListItem-title'); + + if (!discussion || !content || !main || !title) { + return; + } + + var user = typeof discussion.user === 'function' ? discussion.user() : null; + var author = user && typeof user.displayName === 'function' ? user.displayName() : ''; + var category = main.querySelector('.TagLabel-name'); + var meta = document.createElement('div'); + meta.className = 'ablecloud-DiscussionMeta'; + meta.setAttribute('aria-label', 'ํ† ๋ก  ์ž‘์„ฑ ์ •๋ณด'); + appendMetaPart(meta, 'ablecloud-DiscussionMeta-category', category && category.textContent.trim()); + appendMetaPart(meta, 'ablecloud-DiscussionMeta-author', author); + appendMetaPart( + meta, + 'ablecloud-DiscussionMeta-time', + formatDiscussionCreatedAt(typeof discussion.createdAt === 'function' ? discussion.createdAt() : null) + ); + content.insertBefore(meta, main); + + var summaryText = buildDiscussionSummary(discussion); + + if (summaryText) { + var summary = document.createElement('p'); + summary.className = 'ablecloud-DiscussionSummary'; + summary.textContent = summaryText; + title.insertAdjacentElement('afterend', summary); + } + + var thumbnail = findDiscussionThumbnail(discussion); + + if (thumbnail) { + var thumbnailFrame = document.createElement('span'); + var thumbnailImage = document.createElement('img'); + thumbnailFrame.className = 'ablecloud-DiscussionThumbnail'; + thumbnailFrame.setAttribute('aria-hidden', 'true'); + thumbnailImage.src = thumbnail.src; + thumbnailImage.alt = thumbnail.alt; + thumbnailImage.loading = 'lazy'; + thumbnailImage.decoding = 'async'; + thumbnailFrame.appendChild(thumbnailImage); + main.appendChild(thumbnailFrame); + main.classList.add('ablecloud-DiscussionMain--withThumbnail'); + } + + var badges = content.querySelector('.DiscussionListItem-badges'); + + if (badges && badges.querySelector('.item-bestAnswer')) { + badges.setAttribute('aria-label', 'ํ•ด๊ฒฐ๋œ ํ† ๋ก '); + } + + listItem.setAttribute(discussionItemEnhancedAttribute, 'true'); + }); + } + + function createDetailMetaPart(className, text, href) { + var part = href ? document.createElement('a') : document.createElement('span'); + part.className = className; + part.textContent = text; + + if (href) { + part.href = href; + } + + return part; + } + + function scrollToSolution(event) { + var solution = document.getElementById('ablecloud-solution-post'); + + if (!solution) { + return; + } + + event.preventDefault(); + solution.scrollIntoView({ + behavior: window.matchMedia('(prefers-reduced-motion: reduce)').matches ? 'auto' : 'smooth', + block: 'start', + }); + } + + function enhanceLongTechnicalBlocks(root) { + root.querySelectorAll('.App--discussion .Post-body pre').forEach(function (block) { + if (block.getAttribute('data-ablecloud-log-block') === 'true' || block.scrollHeight <= 480) { + return; + } + + block.setAttribute('data-ablecloud-log-block', 'true'); + block.classList.add('ablecloud-TechnicalBlock--collapsed'); + + var toggle = document.createElement('button'); + toggle.type = 'button'; + toggle.className = 'Button ablecloud-TechnicalBlock-toggle'; + toggle.setAttribute('aria-expanded', 'false'); + toggle.textContent = '์ „์ฒด ๋กœ๊ทธ ๋ณด๊ธฐ'; + toggle.addEventListener('click', function () { + var expanded = toggle.getAttribute('aria-expanded') === 'true'; + toggle.setAttribute('aria-expanded', String(!expanded)); + toggle.textContent = expanded ? '์ „์ฒด ๋กœ๊ทธ ๋ณด๊ธฐ' : '๋กœ๊ทธ ์ ‘๊ธฐ'; + block.classList.toggle('ablecloud-TechnicalBlock--collapsed', expanded); + }); + block.insertAdjacentElement('afterend', toggle); + }); + } + + function enhanceDiscussionDetail(root) { + var page = root.querySelector('.App--discussion .DiscussionPage'); + var hero = root.querySelector('.App--discussion .DiscussionHero'); + var stream = page && page.querySelector('.PostStream'); + var firstItem = stream && stream.querySelector('.PostStream-item[data-number="1"]'); + var firstPost = firstItem && firstItem.querySelector('article.Post'); + + if (!page || !hero || !stream || !firstPost) { + return; + } + + var solutionPost = stream.querySelector('article.Post.Post--bestAnswer'); + var solutionItem = solutionPost && solutionPost.closest('.PostStream-item'); + + if (solutionItem) { + solutionItem.id = 'ablecloud-solution-post'; + } + + if (hero.getAttribute(discussionDetailEnhancedAttribute) !== 'true') { + var heroItems = hero.querySelector('.DiscussionHero-items'); + var authorLink = firstPost.querySelector('.PostUser-name a'); + var authorName = firstPost.querySelector('.PostUser-name .username'); + var publishedAt = firstPost.querySelector('.PostMeta time'); + var meta = document.createElement('li'); + meta.className = 'item-ablecloudMeta ablecloud-DiscussionDetailMeta'; + meta.setAttribute('aria-label', 'ํ† ๋ก  ์ž‘์„ฑ ์ •๋ณด'); + + if (authorName) { + meta.appendChild( + createDetailMetaPart( + 'ablecloud-DiscussionDetailMeta-author', + authorName.textContent.trim(), + authorLink && authorLink.getAttribute('href') + ) + ); + } + + if (publishedAt) { + meta.appendChild(createDetailMetaPart('ablecloud-DiscussionDetailMeta-separator', 'ยท')); + meta.appendChild(createDetailMetaPart('ablecloud-DiscussionDetailMeta-time', publishedAt.textContent.trim())); + } + + if (solutionItem) { + var solutionLink = createDetailMetaPart('ablecloud-SolutionJump', 'ํ•ด๊ฒฐ ๋‹ต๋ณ€ ๋ณด๊ธฐ', '#ablecloud-solution-post'); + solutionLink.addEventListener('click', scrollToSolution); + meta.appendChild(solutionLink); + } + + if (heroItems) { + heroItems.appendChild(meta); + } + + var solvedBadge = hero.querySelector('.Badge--bestAnswer'); + + if (solvedBadge) { + solvedBadge.setAttribute('aria-label', 'ํ•ด๊ฒฐ๋จ'); + } + + hero.setAttribute(discussionDetailEnhancedAttribute, 'true'); + } + + var duplicateSolution = firstPost.querySelector('.item-bestAnswerPost'); + + if (duplicateSolution) { + duplicateSolution.setAttribute('aria-hidden', 'true'); + } + + if (!firstItem.querySelector('.ablecloud-InlineReplyPrompt')) { + var nativeReply = page.querySelector('.DiscussionPage-nav .item-controls .SplitDropdown-button'); + + if (nativeReply) { + var replyPrompt = document.createElement('div'); + var replyButton = document.createElement('button'); + replyPrompt.className = 'ablecloud-InlineReplyPrompt'; + replyButton.type = 'button'; + replyButton.className = 'Button ablecloud-InlineReplyPrompt-button'; + replyButton.textContent = '๋‹ต๋ณ€์„ ์ž‘์„ฑํ•ด ์ฃผ์„ธ์š”'; + replyButton.addEventListener('click', function () { + nativeReply.click(); + }); + replyPrompt.appendChild(replyButton); + firstPost.insertAdjacentElement('afterend', replyPrompt); + } + } + + enhanceLongTechnicalBlocks(root); + } function formatTagDiscussionDates(root) { root.querySelectorAll('.TagTile-lastPostedDiscussion time[datetime]').forEach(function (time) { @@ -33,11 +607,10 @@ var url = new URL(link.getAttribute('href'), window.location.origin); var canonicalPath = url.pathname.replace(/(\/d\/[^/]+)\/\d+\/?$/, '$1'); - if (canonicalPath === url.pathname) { - return; + if (canonicalPath !== url.pathname) { + link.setAttribute('href', canonicalPath + url.search); } - link.setAttribute('href', canonicalPath + url.search); link.setAttribute('data-ablecloud-start-from-top', 'true'); }); } @@ -49,12 +622,240 @@ return; } - var href = link.getAttribute('href'); + window.requestAnimationFrame(function () { + window.scrollTo(0, 0); + }); + + window.setTimeout(function () { + window.scrollTo(0, 0); + }, 120); + } + + function captureDiscussionListSnapshot(root) { + var list = root.querySelector('.App--index .DiscussionList'); + + if (!list) { + return; + } + + try { + window.sessionStorage.setItem(discussionListSnapshotKey, list.outerHTML); + } catch (error) { + // A browser with disabled session storage can still use the normal back link. + } + } + + function markCurrentDiscussionInPane(pane) { + var currentPath = window.location.pathname.replace(/\/$/, '').replace(/(\/d\/[^/]+)\/\d+$/, '$1'); + + pane.querySelectorAll('.DiscussionListItem').forEach(function (item) { + var link = item.querySelector('.DiscussionListItem-main[href]'); + var active = false; + + if (link) { + try { + active = new URL(link.getAttribute('href'), window.location.origin).pathname.replace(/\/$/, '') === currentPath; + } catch (error) { + active = false; + } + } + + item.classList.toggle('active', active); + }); + } + + function ensureFallbackDiscussionPane(root) { + var page = root.querySelector('.App--discussion .DiscussionPage'); + + if (!page) { + if (fallbackPaneActive && forumApp.pane) { + forumApp.pane.disable(); + } + + fallbackPaneActive = false; + return; + } + + var nativePane = page.querySelector('.DiscussionPage-list:not([' + fallbackPaneAttribute + '="true"])'); + + if (nativePane) { + return; + } + + if (page.querySelector('.DiscussionPage-list[' + fallbackPaneAttribute + '="true"]')) { + return; + } + + var snapshot = ''; + + try { + snapshot = window.sessionStorage.getItem(discussionListSnapshotKey) || ''; + } catch (error) { + snapshot = ''; + } + + if (!snapshot) { + return; + } + + var template = document.createElement('template'); + template.innerHTML = snapshot; + var list = template.content.querySelector('.DiscussionList'); + + if (!list) { + return; + } + + var pane = document.createElement('aside'); + pane.className = 'DiscussionPage-list ablecloud-DiscussionPage-list--fallback'; + pane.setAttribute(fallbackPaneAttribute, 'true'); + pane.setAttribute('aria-label', '์ตœ๊ทผ ํ† ๋ก  ๋ชฉ๋ก'); + pane.appendChild(list); + markCurrentDiscussionInPane(pane); + page.insertBefore(pane, page.firstChild); + + if (forumApp.pane) { + forumApp.pane.enable(); + forumApp.pane.hide(); + pane.addEventListener('mouseenter', forumApp.pane.show.bind(forumApp.pane)); + pane.addEventListener('mouseleave', forumApp.pane.onmouseleave.bind(forumApp.pane)); + } + + fallbackPaneActive = true; + } + + function syncComposerPaneState(root) { + var app = root.querySelector('.App') || document.querySelector('.App'); + var composerOpen = Boolean(document.querySelector('.Composer.visible:not(.minimized)')); + var newDiscussionOpen = Boolean( + document.querySelector('.Composer.visible:not(.minimized) .ComposerBody--discussion') + ); + + document.documentElement.classList.toggle('ablecloud-composer-open', composerOpen); + document.documentElement.classList.toggle('ablecloud-new-discussion-open', newDiscussionOpen); + document.body.classList.toggle('ablecloud-composer-open', composerOpen); + document.body.classList.toggle('ablecloud-new-discussion-open', newDiscussionOpen); + + if (!app) { + return composerOpen; + } + + app.classList.toggle('ablecloud-composer-open', composerOpen); + app.classList.toggle('ablecloud-new-discussion-open', newDiscussionOpen); + + if (!composerOpen) { + closeEmojiPalette(); + } + + if (composerOpen && forumApp.pane) { + forumApp.pane.hide(); + } + + return composerOpen; + } + + function syncMobileNavigationState(root) { + var app = root.querySelector('.App') || document.querySelector('.App'); + var toggle = root.querySelector('.Navigation-drawer') || document.querySelector('.Navigation-drawer'); + + if (!app || !toggle) { + return; + } + + var open = app.classList.contains('drawerOpen'); + toggle.setAttribute('aria-label', open ? 'ํƒ์ƒ‰ ์„œ๋ž ๋‹ซ๊ธฐ' : 'ํƒ์ƒ‰ ์„œ๋ž ์—ด๊ธฐ'); + toggle.setAttribute('title', open ? '๋ฉ”๋‰ด ๋‹ซ๊ธฐ' : '๋ฉ”๋‰ด ์—ด๊ธฐ'); + + if (!open) { + root.querySelectorAll('.drawer-backdrop').forEach(function (backdrop) { + backdrop.remove(); + }); + } + } + + function handleMobileOverlayKeydown(event) { + if (event.key !== 'Escape') { + return; + } + + var app = document.querySelector('.App.drawerOpen'); + var drawerToggle = app && document.querySelector('.Navigation-drawer'); + + if (drawerToggle) { + event.preventDefault(); + drawerToggle.click(); + } + } + + function closeMobileNavigation(event) { + var app = document.querySelector('.App.drawerOpen'); + + if (!app) { + return false; + } + + if (event) { + event.preventDefault(); + event.stopImmediatePropagation(); + } + + app.classList.remove('drawerOpen'); + document.querySelectorAll('.drawer-backdrop').forEach(function (backdrop) { + backdrop.remove(); + }); + syncMobileNavigationState(document); + return true; + } + + function handleMobileNavigationClick(event) { + var toggle = event.target.closest && event.target.closest('.Navigation-drawer'); + var backdrop = event.target.closest && event.target.closest('.drawer-backdrop'); + + if ((toggle || backdrop) && document.querySelector('.App.drawerOpen')) { + closeMobileNavigation(event); + } + } + + function handleMobileComposerCloseClick(event) { + var closeButton = event.target.closest && event.target.closest('.Composer-controls .item-close button'); + + if (!closeButton || !document.body.classList.contains('ablecloud-composer-open')) { + return; + } event.preventDefault(); event.stopImmediatePropagation(); + var title = document.querySelector('.Composer input[placeholder="ํ† ์˜ ์ œ๋ชฉ"]'); + var editor = document.querySelector('.Composer .TextEditor-editor'); + var hasContent = Boolean( + (title && title.value.trim()) || + (editor && editor.textContent.trim()) + ); + + if (hasContent && !window.confirm('์ž‘์„ฑ ์ค‘์ธ ๋‚ด์šฉ์„ ์ทจ์†Œํ•˜๊ณ  ๋Œ€ํ™”์ƒ์ž๋ฅผ ๋‹ซ์œผ์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?')) { + return; + } + + forumApp.composer.hide(); + window.setTimeout(function () { + syncComposerPaneState(document); + }, 0); + } + + function showFallbackPaneFromEdge(event) { + var composerOpen = syncComposerPaneState(document); - window.location.assign(href); + if (composerOpen) { + return; + } + + if ( + fallbackPaneActive && + forumApp.pane && + event.clientX < 10 + ) { + forumApp.pane.show(); + } } forumApp.initializers.add('ablecloud-community-theme-tag-date', function () { @@ -64,18 +865,38 @@ pendingFrame = window.requestAnimationFrame(function () { formatTagDiscussionDates(document); normalizeDiscussionListTargets(document); + enhanceDiscussionListItems(document); + enhanceInfiniteDiscussionLoading(document); + captureDiscussionListSnapshot(document); + ensureFallbackDiscussionPane(document); + syncComposerPaneState(document); + syncMobileNavigationState(document); + enhanceDiscussionDetail(document); }); }; schedule(); new MutationObserver(schedule).observe(document.body, { + attributes: true, + attributeFilter: ['class'], childList: true, characterData: true, subtree: true, }); + document.addEventListener('click', function () { + window.setTimeout(schedule, 0); + window.setTimeout(schedule, 150); + window.setTimeout(schedule, 600); + }, true); document.addEventListener('click', openDiscussionFromTop, true); + document.addEventListener('click', handleMobileNavigationClick, true); + document.addEventListener('click', handleMobileComposerCloseClick, true); + document.addEventListener('click', handleEmojiPaletteClick, true); + document.addEventListener('keydown', handleEmojiPaletteKeydown, true); + document.addEventListener('keydown', handleMobileOverlayKeydown, true); + document.addEventListener('mousemove', showFallbackPaneFromEdge, { passive: true }); }); module.exports = {}; diff --git a/deploy/flarum/extensions/ablecloud-community-theme/resources/less/forum.less b/deploy/flarum/extensions/ablecloud-community-theme/resources/less/forum.less index 1fb7ed8..87282bb 100644 --- a/deploy/flarum/extensions/ablecloud-community-theme/resources/less/forum.less +++ b/deploy/flarum/extensions/ablecloud-community-theme/resources/less/forum.less @@ -37,7 +37,8 @@ .fa-sort { --ablecloud-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m7 10 5 5 5-5' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); } .fa-search { --ablecloud-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Ccircle cx='11' cy='11' r='6' fill='none' stroke='black' stroke-width='2'/%3E%3Cpath d='m16 16 4 4' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E"); } .fa-check { --ablecloud-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m5 12 4 4L19 6' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); } -.fa-times { --ablecloud-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6 6l12 12M18 6 6 18' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E"); } +.fa-times, +.fa-times-circle { --ablecloud-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='9' fill='none' stroke='black' stroke-width='2'/%3E%3Cpath d='M8 8l8 8M16 8l-8 8' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E"); } .fa-flag { --ablecloud-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M5 3v18M6 5h12l-2.5 4L18 13H6' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); } .fa-bell { --ablecloud-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M18 8a6 6 0 0 0-12 0c0 7-3 7-3 9h18c0-2-3-2-3-9M10 21h4' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); } .fa-envelope { --ablecloud-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Crect x='3' y='5' width='18' height='14' rx='2' fill='none' stroke='black' stroke-width='2'/%3E%3Cpath d='m4 7 8 6 8-6' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); } @@ -91,10 +92,42 @@ .fa-shield-alt { --ablecloud-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m12 3 8 3v6c0 5-3.5 8-8 10-4.5-2-8-5-8-10V6l8-3Z' fill='none' stroke='black' stroke-width='2' stroke-linejoin='round'/%3E%3C/svg%3E"); } .fa-plus-circle { --ablecloud-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='9' fill='none' stroke='black' stroke-width='2'/%3E%3Cpath d='M12 8v8M8 12h8' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E"); } .fa-circle { --ablecloud-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='7' fill='black'/%3E%3C/svg%3E"); } - -.Search-input::before { - font-family: "Segoe UI Symbol", "Apple Symbols", "Noto Sans Symbols 2", sans-serif !important; - content: "โŒ•" !important; +.fa-eye { --ablecloud-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M2 12s4-7 10-7 10 7 10 7-4 7-10 7S2 12 2 12Z' fill='none' stroke='black' stroke-width='2' stroke-linejoin='round'/%3E%3Ccircle cx='12' cy='12' r='3' fill='none' stroke='black' stroke-width='2'/%3E%3C/svg%3E"); } +.fa-eye-slash { --ablecloud-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M3 3l18 18M10.7 5.2A10 10 0 0 1 12 5c6 0 10 7 10 7a18 18 0 0 1-3 3.7M6.2 6.2A18 18 0 0 0 2 12s4 7 10 7a9 9 0 0 0 4-.9M9.9 9.9a3 3 0 0 0 4.2 4.2' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); } +.fa-bold { --ablecloud-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M7 4h6a4 4 0 0 1 0 8H7V4Zm0 8h7a4 4 0 0 1 0 8H7v-8Z' fill='none' stroke='black' stroke-width='2.4' stroke-linejoin='round'/%3E%3C/svg%3E"); } +.fa-italic { --ablecloud-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M10 4h8M6 20h8M14 4 10 20' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round'/%3E%3C/svg%3E"); } +.fa-code { --ablecloud-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m8 5-6 7 6 7M16 5l6 7-6 7M14 3 10 21' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); } +.fa-quote-left { --ablecloud-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M10 5H5a3 3 0 0 0-3 3v5h6v6M22 5h-5a3 3 0 0 0-3 3v5h6v6' fill='none' stroke='black' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); } +.fa-link { --ablecloud-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m10 14 4-4M8.5 17.5 6 20a4 4 0 0 1-6-6l4-4a4 4 0 0 1 6 0M15.5 6.5 18 4a4 4 0 0 1 6 6l-4 4a4 4 0 0 1-6 0' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E"); } +.fa-image { --ablecloud-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Crect x='3' y='4' width='18' height='16' rx='2' fill='none' stroke='black' stroke-width='2'/%3E%3Ccircle cx='9' cy='9' r='2' fill='none' stroke='black' stroke-width='2'/%3E%3Cpath d='m4 18 5-5 3 3 2-2 6 5' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); } +.fa-list-ul { --ablecloud-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Ccircle cx='4' cy='6' r='1.5' fill='black'/%3E%3Ccircle cx='4' cy='12' r='1.5' fill='black'/%3E%3Ccircle cx='4' cy='18' r='1.5' fill='black'/%3E%3Cpath d='M9 6h12M9 12h12M9 18h12' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E"); } +.fa-list-ol { --ablecloud-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M9 6h12M9 12h12M9 18h12M3 4h2v4M3 11h2l-2 3h2M3 17h2l-2 3h2' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); } +.fa-plus { --ablecloud-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 4v16M4 12h16' fill='none' stroke='black' stroke-width='2.2' stroke-linecap='round'/%3E%3C/svg%3E"); } +.fa-strikethrough { --ablecloud-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M17 6.5C16 5 14.3 4 12 4 8.7 4 7 5.5 7 7.5c0 1.8 1.5 2.8 4 3.5M5 12h14M9 15c.6 3.3 6.9 3.4 7.8.4' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E"); } +.fa-subscript { --ablecloud-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m5 5 8 12M13 5 5 17M15 17h4l-4 4h4' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); } +.fa-superscript { --ablecloud-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m5 7 8 12M13 7 5 19M15 3h4l-4 4h4' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); } +.fa-terminal { --ablecloud-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Crect x='3' y='4' width='18' height='16' rx='2' fill='none' stroke='black' stroke-width='2'/%3E%3Cpath d='m7 9 3 3-3 3M12 16h5' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); } +.fa-caret-square-right { --ablecloud-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Crect x='3' y='3' width='18' height='18' rx='2' fill='none' stroke='black' stroke-width='2'/%3E%3Cpath d='m10 8 6 4-6 4V8Z' fill='black'/%3E%3C/svg%3E"); } + +.Search .Search-input::before { + position: absolute; + top: 50%; + left: 14px; + z-index: 1; + display: block; + width: 18px; + height: 18px; + background: #5275a7 !important; + content: "" !important; + transform: translateY(-50%); + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Ccircle cx='11' cy='11' r='6' fill='none' stroke='black' stroke-width='2'/%3E%3Cpath d='m16 16 4 4' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E") !important; + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Ccircle cx='11' cy='11' r='6' fill='none' stroke='black' stroke-width='2'/%3E%3Cpath d='m16 16 4 4' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E") !important; + -webkit-mask-position: center; + mask-position: center; + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-size: contain; + mask-size: contain; } /* Chromium์˜ ๊ธฐ๋ณธ ๊ฒ€์ƒ‰ ์ดˆ๊ธฐํ™” ๊ธ€๋ฆฌํ”„๊ฐ€ ์›นํฐํŠธ ๋ถ€์žฌ ์‹œ ์‚ฌ๊ฐํ˜•์ด ๋˜์ง€ ์•Š๊ฒŒ ํ•œ๋‹ค. */ @@ -117,7 +150,165 @@ mask-size: contain; } +/* + * ๊ฒ€์ƒ‰ ํ™•์žฅ ๊ธฐ๋Šฅ์€ ํ•ด๊ฒฐ๋œ ํ† ๋ก  ํ•œ ๊ฑด์„ ํƒœ๊ทธยท์ œ๋ชฉยท์งˆ๋ฌธยท์ตœ์ข… ๋‹ต๋ณ€์˜ + * ๊ฐ€๋กœ 4์—ด๋กœ ์ถœ๋ ฅํ•œ๋‹ค. ํ—ค๋” ๊ฒ€์ƒ‰ ํญ์—์„œ๋Š” ๊ฐ ์—ด์ด ํ•œ๋‘ ๊ธ€์ž๋กœ ์ชผ๊ฐœ์ง€๋ฏ€๋กœ + * ๊ฒฐ๊ณผ ํ•œ ๊ฑด์„ ์œ„์—์„œ ์•„๋ž˜๋กœ ์ฝ๋Š” ๋‹จ์ผ ์—ด ์นด๋“œ๋กœ ์žฌ๊ตฌ์„ฑํ•œ๋‹ค. + */ +.Search-clear { + display: inline-flex; + align-items: center; + justify-content: center; + color: #5275a7; +} + +.Search-clear .fa-times-circle { + width: 18px; + height: 18px; +} + +.Search .Search-clear .fa-times-circle::before { + display: block !important; + width: 18px !important; + height: 18px !important; + background: currentColor !important; + content: "" !important; + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='9' fill='none' stroke='black' stroke-width='2'/%3E%3Cpath d='M8 8l8 8M16 8l-8 8' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E") !important; + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='9' fill='none' stroke='black' stroke-width='2'/%3E%3Cpath d='M8 8l8 8M16 8l-8 8' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E") !important; + -webkit-mask-position: center; + mask-position: center; + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-size: contain; + mask-size: contain; +} + +.Header-secondary .Search .Search-results { + right: 0; + left: auto; + width: ~"min(560px, calc(100vw - 24px))" !important; + max-height: ~"min(72vh, 680px)" !important; + padding: 8px 0; + overflow-x: hidden; + overflow-y: auto; + border: 1px solid var(--ablecloud-brand-border); + border-radius: 14px; + background: #fff; + box-shadow: 0 18px 44px rgba(20, 47, 84, 0.18); + overflow-wrap: anywhere; + word-break: keep-all; + scrollbar-color: #8da4c5 transparent; + scrollbar-width: thin; +} + +.Search-results > li { + width: 100%; +} + +.Search-results .Dropdown-header { + padding: 10px 16px 8px; + color: var(--ablecloud-brand-ink); + font-size: 14px; + font-weight: 750; +} + +.Search-results > li > a { + min-width: 0; + padding: 11px 16px; + color: var(--ablecloud-brand-ink); +} + +.Search-results > li > a:hover, +.Search-results > li.active > a { + background: var(--ablecloud-brand-primary-faint); +} + +.Search-results .DiscussionSearchResult > a, +.Search-results .SolutionSearchResult > a { + display: block !important; + min-height: 0; + padding: 13px 16px; + border-top: 1px solid #edf2f8; +} + +.Search-results .SolutionSearchResult-tags { + display: block; + width: auto; + margin: 0 0 8px; +} + +.Search-results .SolutionSearchResult-tags .TagsLabel { + display: flex; + flex-wrap: wrap; + gap: 5px; +} + +.Search-results .SolutionSearchResult-tags .TagLabel { + max-width: 100%; + margin: 0; + border-radius: 5px; +} + +.Search-results .DiscussionSearchResult-title { + width: auto; + margin: 0; + color: var(--ablecloud-brand-ink); + font-size: 15px; + font-weight: 750; + line-height: 1.45; + overflow-wrap: anywhere; + word-break: keep-all; +} + +.Search-results .DiscussionSearchResult-excerpt { + display: -webkit-box; + width: auto; + margin: 6px 0 0; + overflow: hidden; + color: var(--ablecloud-brand-muted); + font-size: 13.5px; + line-height: 1.55; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; +} + +.Search-results .SolutionSearchResult-bestAnswer { + display: -webkit-box; + width: auto; + margin-top: 9px; + padding: 9px 11px; + border-left: 3px solid #34a66f; + border-radius: 8px; + background: var(--ablecloud-brand-solution); + color: #315f4a; + -webkit-line-clamp: 2; +} + +.Search-results mark { + padding: 0 2px; + border-radius: 3px; + background: #fff1a8; + color: inherit; + font-weight: 750; +} + +@media (max-width: 767px) { + .Header-secondary .Search .Search-results { + right: 12px; + left: 12px; + width: auto !important; + max-height: calc(100vh - 94px) !important; + } + + .Search-results .DiscussionSearchResult > a, + .Search-results .SolutionSearchResult > a { + padding: 12px 14px; + } +} + :root { + --ablecloud-page-shell-width: 1165px; + --ablecloud-page-rail-space: 80px; --ablecloud-brand-primary: #155eef; --ablecloud-brand-primary-hover: #004eeb; --ablecloud-brand-primary-deep: #0b3b91; @@ -847,7 +1038,7 @@ body { .TagTile:focus-within { border-color: #9db9e4; border-top-color: var(--tag-bg); - box-shadow: var(--ablecloud-shadow); + box-shadow: var(--ablecloud-shadow-soft); transform: translateY(-2px); } @@ -1208,6 +1399,64 @@ body { color: var(--ablecloud-brand-muted); } + .ablecloud-DiscussionMeta { + display: flex; + min-width: 0; + align-items: center; + gap: 6px; + overflow: hidden; + color: var(--ablecloud-brand-muted); + font-size: 12px; + font-weight: 560; + line-height: 20px; + white-space: nowrap; + } + + .ablecloud-DiscussionMeta-category { + overflow: hidden; + color: var(--ablecloud-brand-primary-deep); + font-weight: 720; + text-overflow: ellipsis; + } + + .ablecloud-DiscussionMeta-separator { + flex: 0 0 auto; + color: #9aacc3; + } + + .ablecloud-DiscussionMeta-author, + .ablecloud-DiscussionMeta-time { + flex: 0 0 auto; + } + + .ablecloud-DiscussionSummary { + display: -webkit-box; + margin: 6px 0 0; + overflow: hidden; + color: #536b89; + font-size: 14px; + font-weight: 430; + line-height: 1.55; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + white-space: normal; + } + + .ablecloud-DiscussionThumbnail { + display: block; + overflow: hidden; + border: 1px solid var(--ablecloud-brand-border); + border-radius: 10px; + background: #eaf1fb; + } + + .ablecloud-DiscussionThumbnail > img { + display: block; + width: 100%; + height: 100%; + object-fit: cover; + } + .Badge--answered { background: #078248; color: #fff; @@ -1401,37 +1650,52 @@ body { } .App-content { - padding-bottom: 64px; + padding-bottom: 24px; } + // The legacy footer bar is replaced with a fixed vertical quick-link rail. + // Keep the existing links and accessible names without reserving a bottom row. #console-nav-footer { - height: 56px; - padding: 0; - border-top: 1px solid var(--ablecloud-brand-border); - background: rgba(255, 255, 255, 0.97); - box-shadow: 0 -6px 20px rgba(24, 71, 142, 0.08); + position: fixed; + top: 50%; + // Escape the whole function so LESS does not fold max() to 12px at build time. + right: ~"max(12px, calc((100vw - 1165px) / 2 + 16px))"; + bottom: auto; + left: auto; + width: 48px; + height: auto; + padding: 8px 0; + border: 1px solid #c3d5ef; + border-radius: 999px; + background: rgba(255, 255, 255, 0.94); + box-shadow: 0 8px 24px rgba(24, 71, 142, 0.12); + backdrop-filter: blur(12px); + transform: translateY(-50%); z-index: 1030; } #footer-content { display: flex; - width: 100%; - max-width: 1100px; + width: auto; + max-width: none; + flex-direction: column; align-items: center; - justify-content: space-between; - height: 100%; + justify-content: center; + gap: 8px; + height: auto; box-sizing: border-box; - margin: 0 auto; - padding: 0 15px; + margin: 0; + padding: 0; } #floatleft, #floatright { display: flex; float: none; + flex-direction: column; align-items: center; - gap: 9px; - height: 100%; + gap: 8px; + height: auto; margin: 0; } @@ -1730,109 +1994,351 @@ body { box-shadow: 0 20px 52px rgba(24, 71, 142, 0.18), 0 0 0 1px rgba(21, 94, 239, 0.1) !important; } - .ComposerBody-header .DiscussionComposer-changeTags { - display: inline-flex; - align-items: center; - min-height: 36px; - padding: 0 11px; - border: 1px solid var(--ablecloud-brand-border); - border-radius: 9px; - background: var(--ablecloud-brand-primary-faint); - color: var(--ablecloud-brand-primary-deep); - font-weight: 650; - } + @media (min-width: 768px) { + .Composer.active:not(.fullScreen) { + min-height: 336px; + } - .ComposerBody-header .DiscussionComposer-changeTags:hover, - .ComposerBody-header .DiscussionComposer-changeTags:focus-visible { - border-color: #8cb0ec; - outline: none !important; - background: #eaf2ff; - box-shadow: 0 0 0 2px rgba(21, 94, 239, 0.14); - } + .Composer.active:not(.fullScreen) .Composer-content { + height: 100% !important; + box-sizing: border-box; + } - .ComposerBody-header .item-discussionTitle input.FormControl { - padding: 0 12px; - border: 1px solid transparent; - background: var(--ablecloud-brand-primary-faint); - box-shadow: none; - } + .Composer.active:not(.fullScreen) .ComposerBody, + .Composer.active:not(.fullScreen) .ComposerBody-content { + height: 100% !important; + min-height: 0; + } - .ComposerBody-header .item-discussionTitle input.FormControl:focus, - .ComposerBody-header .item-discussionTitle input.FormControl:focus-visible, - .Composer .TextEditor-editor:focus, - .Composer .TextEditor-editor:focus-visible { - border-color: var(--ablecloud-brand-primary) !important; - outline: none !important; - background: #fff !important; - box-shadow: 0 0 0 2px rgba(21, 94, 239, 0.14); - } + .Composer.active:not(.fullScreen) .ComposerBody-content, + .Composer.active:not(.fullScreen) .TextEditor { + display: flex; + flex-direction: column; + } - .Composer .TextEditor-editor { - padding: 12px 14px; - border: 1px solid var(--ablecloud-brand-border); - border-radius: 11px; - background: #fbfdff; - } + .Composer.active:not(.fullScreen) .ComposerBody-header, + .Composer.active:not(.fullScreen) .TextEditor-controls { + flex: 0 0 auto; + } - .Composer-controls .Button { - border: 1px solid transparent; - border-radius: 999px; - color: var(--ablecloud-brand-muted); - } + .Composer.active:not(.fullScreen) .ComposerBody-editor, + .Composer.active:not(.fullScreen) .TextEditor, + .Composer.active:not(.fullScreen) .TextEditor-editorContainer { + height: auto !important; + min-height: 0; + flex: 1 1 auto; + } - .Composer-controls .Button:hover, - .Composer-controls .Button:focus-visible { - border-color: var(--ablecloud-brand-border); - outline: none !important; - background: var(--ablecloud-brand-primary-faint); - color: var(--ablecloud-brand-primary-deep); - box-shadow: 0 0 0 2px rgba(21, 94, 239, 0.12); - } + .Composer.active:not(.fullScreen) .ComposerBody-mentionsWrapper, + .Composer.active:not(.fullScreen) .ComposerBody-emojiWrapper { + height: 100%; + min-height: 0; + } - .Composer-header, - .Modal-header { - border-bottom: 1px solid var(--ablecloud-brand-border); - } + .Composer.active:not(.fullScreen) .TextEditor-editor { + height: 100% !important; + padding-bottom: 12px; + } - .TextEditor-editor { - min-height: 180px; - color: var(--ablecloud-brand-ink); - font-size: 16px; - line-height: 1.7; - } + .Composer.active:not(.fullScreen) .TextEditor-controls { + position: static; + bottom: auto; + } - .TagSelectionModal .Modal-content { - box-shadow: 0 24px 60px rgba(24, 71, 142, 0.2); - } + &.ablecloud-new-discussion-open { + overflow: hidden; + } - .TagSelectionModal .Modal-header { - background: var(--ablecloud-brand-primary-faint); - } + &.ablecloud-new-discussion-open .App-composer { + position: fixed; + z-index: 1040; + inset: 0; + display: flex; + width: 100vw; + height: 100vh; + align-items: center; + justify-content: center; + padding: 76px 24px 24px; + background: rgba(20, 42, 75, 0.46); + pointer-events: auto; + } - .TagSelectionModal-form { - display: flex; - align-items: center; - gap: 12px; - padding: 16px 20px; - border-bottom: 1px solid var(--ablecloud-brand-border); - background: #f8fbff; - } + &.ablecloud-new-discussion-open .App-composer > .container { + display: flex; + width: 100%; + max-width: 960px; + height: 100%; + align-items: center; + justify-content: center; + margin: 0; + padding: 0; + } - .TagSelectionModal .Modal-body { - padding: 0; - } + &.ablecloud-new-discussion-open .Composer:not(.fullScreen) { + position: relative; + bottom: auto !important; + width: 100%; + max-width: 960px; + height: calc(100vh - 120px) !important; + max-height: 650px; + min-height: 520px; + margin: 0 !important; + overflow: visible !important; + border-radius: 18px; + background: #fff; + box-shadow: 0 28px 80px rgba(10, 35, 72, 0.34) !important; + } - .TagSelectionModal-form-input { - flex: 1 1 auto; - min-width: 0; - } + &.ablecloud-new-discussion-open .Composer:not(.fullScreen) .Composer-handle, + &.ablecloud-new-discussion-open .Composer:not(.fullScreen) .Composer-controls .item-minimize, + &.ablecloud-new-discussion-open .Composer:not(.fullScreen) .Composer-controls .item-fullScreen { + display: none; + } - .TagSelectionModal .TagsInput.FormControl { - min-height: 44px; - padding: 4px 10px; - border: 1px solid var(--ablecloud-brand-border); - border-radius: 10px; - background: #fff; + &.ablecloud-new-discussion-open .Composer:not(.fullScreen) .Composer-content, + &.ablecloud-new-discussion-open .Composer:not(.fullScreen) .ComposerBody, + &.ablecloud-new-discussion-open .Composer:not(.fullScreen) .ComposerBody-content { + height: 100% !important; + min-height: 0; + } + + &.ablecloud-new-discussion-open .Composer:not(.fullScreen) .ComposerBody-content, + &.ablecloud-new-discussion-open .Composer:not(.fullScreen) .TextEditor { + display: flex; + flex-direction: column; + } + + &.ablecloud-new-discussion-open .Composer:not(.fullScreen) .ComposerBody-header, + &.ablecloud-new-discussion-open .Composer:not(.fullScreen) .TextEditor-controls { + flex: 0 0 auto; + } + + &.ablecloud-new-discussion-open .Composer:not(.fullScreen) .ComposerBody-editor { + display: flex; + height: auto !important; + min-height: 0; + flex: 1 1 auto; + flex-direction: column; + } + + &.ablecloud-new-discussion-open .Composer:not(.fullScreen) .TextEditor { + height: 100% !important; + min-height: 0; + flex: 1 1 auto; + } + + &.ablecloud-new-discussion-open .Composer:not(.fullScreen) .TextEditor-editorContainer { + height: auto !important; + min-height: 0; + flex: 1 1 auto; + } + + &.ablecloud-new-discussion-open .Composer:not(.fullScreen) .ComposerBody-mentionsWrapper, + &.ablecloud-new-discussion-open .Composer:not(.fullScreen) .ComposerBody-emojiWrapper, + &.ablecloud-new-discussion-open .Composer:not(.fullScreen) .TextEditor-editor { + height: 100% !important; + min-height: 0; + } + } + + .ablecloud-EmojiPicker { + position: absolute; + right: 20px; + bottom: 76px; + z-index: 1090; + width: 344px; + padding: 14px; + border: 1px solid #b9cdec; + border-radius: 14px; + background: #fff; + box-shadow: 0 18px 48px rgba(24, 71, 142, 0.24); + } + + .ablecloud-EmojiPicker-header { + display: flex; + align-items: center; + justify-content: space-between; + color: var(--ablecloud-brand-ink); + font-size: 15px; + } + + .ablecloud-EmojiPicker-close { + display: inline-flex; + width: 30px; + height: 30px; + align-items: center; + justify-content: center; + padding: 0; + border: 0; + border-radius: 50%; + background: transparent; + color: var(--ablecloud-brand-muted); + font-size: 22px; + line-height: 1; + } + + .ablecloud-EmojiPicker-close:hover, + .ablecloud-EmojiPicker-close:focus-visible { + outline: none; + background: #eaf2ff; + color: var(--ablecloud-brand-primary-deep); + } + + .ablecloud-EmojiPicker-help { + margin: 2px 0 12px; + color: var(--ablecloud-brand-muted); + font-size: 13px; + } + + .ablecloud-EmojiPicker-grid { + display: grid; + grid-template-columns: repeat(6, minmax(0, 1fr)); + gap: 7px; + } + + .ablecloud-EmojiPicker-option { + display: inline-flex; + min-width: 0; + height: 43px; + align-items: center; + justify-content: center; + padding: 0; + border: 1px solid transparent; + border-radius: 10px; + background: #f5f8fd; + font-family: "Segoe UI Emoji", "Apple Color Emoji", sans-serif; + font-size: 23px; + line-height: 1; + } + + .ablecloud-EmojiPicker-option:hover, + .ablecloud-EmojiPicker-option:focus-visible { + border-color: #8cb0ec; + outline: none; + background: #eaf2ff; + box-shadow: 0 0 0 2px rgba(21, 94, 239, 0.12); + } + + .EmojiDropdown > li:not(.Dropdown-header) > button { + display: flex; + align-items: center; + gap: 0; + font-size: 0; + } + + .EmojiDropdown > li:not(.Dropdown-header) > button img.emoji { + width: 24px; + height: 24px; + margin: 0 auto; + } + + .ComposerBody-header .DiscussionComposer-changeTags { + display: inline-flex; + align-items: center; + min-height: 36px; + padding: 0 11px; + border: 1px solid var(--ablecloud-brand-border); + border-radius: 9px; + background: var(--ablecloud-brand-primary-faint); + color: var(--ablecloud-brand-primary-deep); + font-weight: 650; + } + + .ComposerBody-header .DiscussionComposer-changeTags:hover, + .ComposerBody-header .DiscussionComposer-changeTags:focus-visible { + border-color: #8cb0ec; + outline: none !important; + background: #eaf2ff; + box-shadow: 0 0 0 2px rgba(21, 94, 239, 0.14); + } + + .ComposerBody-header .item-discussionTitle input.FormControl { + padding: 0 12px; + border: 1px solid transparent; + background: var(--ablecloud-brand-primary-faint); + box-shadow: none; + } + + .ComposerBody-header .item-discussionTitle input.FormControl:focus, + .ComposerBody-header .item-discussionTitle input.FormControl:focus-visible, + .Composer .TextEditor-editor:focus, + .Composer .TextEditor-editor:focus-visible { + border-color: var(--ablecloud-brand-primary) !important; + outline: none !important; + background: #fff !important; + box-shadow: 0 0 0 2px rgba(21, 94, 239, 0.14); + } + + .Composer .TextEditor-editor { + padding: 12px 14px; + border: 1px solid var(--ablecloud-brand-border); + border-radius: 11px; + background: #fbfdff; + } + + .DiscussionList-loadMore[data-ablecloud-auto-load="true"][data-ablecloud-loading="true"] .Button { + pointer-events: none; + } + + .Composer-controls .Button { + border: 1px solid transparent; + border-radius: 999px; + color: var(--ablecloud-brand-muted); + } + + .Composer-controls .Button:hover, + .Composer-controls .Button:focus-visible { + border-color: var(--ablecloud-brand-border); + outline: none !important; + background: var(--ablecloud-brand-primary-faint); + color: var(--ablecloud-brand-primary-deep); + box-shadow: 0 0 0 2px rgba(21, 94, 239, 0.12); + } + + .Composer-header, + .Modal-header { + border-bottom: 1px solid var(--ablecloud-brand-border); + } + + .TextEditor-editor { + min-height: 180px; + color: var(--ablecloud-brand-ink); + font-size: 16px; + line-height: 1.7; + } + + .TagSelectionModal .Modal-content { + box-shadow: 0 24px 60px rgba(24, 71, 142, 0.2); + } + + .TagSelectionModal .Modal-header { + background: var(--ablecloud-brand-primary-faint); + } + + .TagSelectionModal-form { + display: flex; + align-items: center; + gap: 12px; + padding: 16px 20px; + border-bottom: 1px solid var(--ablecloud-brand-border); + background: #f8fbff; + } + + .TagSelectionModal .Modal-body { + padding: 0; + } + + .TagSelectionModal-form-input { + flex: 1 1 auto; + min-width: 0; + } + + .TagSelectionModal .TagsInput.FormControl { + min-height: 44px; + padding: 4px 10px; + border: 1px solid var(--ablecloud-brand-border); + border-radius: 10px; + background: #fff; box-shadow: none; } @@ -1931,59 +2437,404 @@ body { } @media (min-width: 768px) { - body { - .App--index > .App-navigation { - display: none !important; - } + /* + * Shared desktop shell + * + * Flarum's default desktop container is 1100px while the discussion feed + * uses a 1165px shell (1100px content + the adjacent quick-link rail). + * Apply that same outer edge to tags, profile, settings and security pages. + * The extra 65px is reserved on the right, so the established 1070px inner + * content width does not stretch and never sits underneath the fixed rail. + */ + .App-header > .container, + .WelcomeHero > .container, + .TagHero > .container, + .UserHero .container, + .TagsPage > .container, + .UserPage > .container { + width: 100%; + max-width: var(--ablecloud-page-shell-width); + box-sizing: border-box; + } - .DiscussionListItem { - margin-bottom: 9px; - z-index: 0; - overflow: visible; + .WelcomeHero > .container, + .TagHero > .container, + .UserHero .container, + .TagsPage > .container, + .UserPage > .container { + padding-right: var(--ablecloud-page-rail-space); + } + + html:has(body > .App--index), + body:has(> .App--index) { + height: 100%; + overflow: hidden; + } + + html:has(body > .App--index) { + // Reserve the same vertical-scrollbar gutter used by discussion pages so + // the 1165px index and discussion shells share identical outer edges. + scrollbar-gutter: stable; + } + + body { + // Align the top navigation and welcome area with the 1165px page shell. + // The inner content remains 1100px; the added width represents the quick-link rail. + .App--index .App-header > .container, + .App--discussion .App-header > .container, + .App--index .WelcomeHero > .container { + width: 100%; + max-width: 1165px; + box-sizing: border-box; } - .DiscussionListItem:hover, - .DiscussionListItem:focus-within, - .DiscussionListItem:has(.DiscussionListItem-controls.open) { - z-index: 1050; + // Keep the desktop index shell anchored below the fixed header. The full + // right column scrolls while a narrow reserved rail sits immediately next + // to the unchanged 1100px content area. + .App--index { + height: 100%; + min-height: 0; + padding-bottom: 0; + overflow: hidden; } - .DiscussionListItem-content { - min-height: 62px; - padding: 11px 64px 11px 52px; + .App--index > .App-content { + position: fixed; + top: 52px; + right: 0; + bottom: 0; + left: 0; + height: auto; + box-sizing: border-box; + padding-bottom: 0; + overflow: hidden; } - .DiscussionListItem-main { + .App--index .IndexPage { + position: absolute; + inset: 0; display: flex; - align-items: center; - gap: 14px; - min-width: 0; - min-height: 38px; height: auto; - margin-right: 0 !important; - padding: 0 !important; + min-height: 0; + flex-direction: column; + overflow: hidden; } - .DiscussionListItem-author { - margin-top: 1px !important; + .App--index .WelcomeHero { + flex: 0 0 auto; } - .DiscussionListItem-title { + .App--index .IndexPage > .container { + width: 100%; + // Preserve the original 1100px content width and add only the 65px + // needed for the 48px quick-link rail and its 17px separation. + max-width: 1165px; flex: 1 1 auto; - min-width: 180px; - margin: 0 !important; + min-height: 0; + box-sizing: border-box; + padding-right: 80px; + padding-bottom: 0; overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; } - .DiscussionListItem-info { - display: flex; - flex: 0 1 280px; - align-items: center; - height: auto !important; - min-width: 0; - margin: 0; + .App--index .IndexPage > .container > .sideNavContainer { + height: 100%; + min-height: 0; + overflow: hidden; + } + + .App--index .IndexPage-nav { + height: 100%; + min-height: 0; + box-sizing: border-box; + overflow-y: auto; + overscroll-behavior: contain; + -ms-overflow-style: none; + scrollbar-width: none; + } + + .App--index .IndexPage-results { + display: flex; + height: auto; + min-height: 0; + flex-direction: column; + overflow-x: hidden; + overflow-y: auto; + overscroll-behavior: contain; + -ms-overflow-style: none; + scrollbar-width: none; + } + + .App--index .IndexPage-toolbar { + flex: 0 0 auto; + padding-top: 20px; + } + + .App--index .IndexPage-results .DiscussionList { + flex: 0 0 auto; + min-height: auto; + height: auto; + box-sizing: border-box; + padding-right: 6px; + overflow: visible; + } + + // Follow Reddit's flat feed composition: no list container card and no + // nested row cards. ABLESTACK color, type, tags and actions remain intact. + .App--index .IndexPage-results .DiscussionList-discussions { + margin: 0; + border: 0; + border-radius: 0; + background: transparent; + box-shadow: none; + } + + .App--index .IndexPage-results .DiscussionList-discussions > li:not(:last-child) { + border-bottom: 1px solid var(--ablecloud-brand-border); + } + + .App--index .IndexPage-results .DiscussionListItem { + margin: 0; + border: 0; + border-radius: 0; + background: transparent; + box-shadow: none; + transform: none; + } + + .App--index .IndexPage-results .DiscussionListItem:hover, + .App--index .IndexPage-results .DiscussionListItem:focus-within, + .App--index .IndexPage-results .DiscussionListItem:has(.DiscussionListItem-controls.open) { + border-color: transparent; + background: rgba(21, 94, 239, 0.055); + box-shadow: none; + transform: none; + } + + .App--index .IndexPage-results .DiscussionListItem-content { + display: grid; + min-height: 136px; + grid-template-columns: auto minmax(0, 1fr); + column-gap: 8px; + align-content: start; + padding: 14px 64px 14px 64px; + } + + .App--index .IndexPage-results .DiscussionListItem-author { + position: absolute !important; + top: 15px; + left: 12px; + margin-left: 0 !important; + margin-top: 0 !important; + } + + .App--index .IndexPage-results .ablecloud-DiscussionMeta { + grid-row: 1; + grid-column: 2; + min-height: 20px; + padding-right: 0; + } + + .App--index .IndexPage-results .DiscussionListItem-content:not(:has(.item-bestAnswer)) .ablecloud-DiscussionMeta { + grid-column-start: 1; + grid-column-end: -1; + } + + .App--index .IndexPage-results .DiscussionListItem-main { + display: block; + grid-row: 2; + grid-column-start: 1; + grid-column-end: -1; + min-height: 0; + margin-top: 3px; + } + + .App--index .IndexPage-results .DiscussionListItem-title { + display: -webkit-box; + min-width: 0; + margin: 0 !important; + overflow: hidden; + color: var(--ablecloud-brand-ink); + font-size: 18px; + font-weight: 760; + line-height: 1.4; + text-overflow: initial; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + white-space: normal; + } + + .App--index .IndexPage-results .ablecloud-DiscussionMain--withThumbnail { + display: grid; + grid-template-columns: minmax(0, 1fr) 124px; + grid-template-rows: auto auto 1fr; + column-gap: 16px; + min-height: 92px; + } + + .App--index .IndexPage-results .ablecloud-DiscussionMain--withThumbnail .DiscussionListItem-title { + grid-row: 1; + grid-column: 1; + } + + .App--index .IndexPage-results .ablecloud-DiscussionMain--withThumbnail .ablecloud-DiscussionSummary { + grid-row: 2; + grid-column: 1; + } + + .App--index .IndexPage-results .ablecloud-DiscussionMain--withThumbnail .DiscussionListItem-info { + width: 100%; + max-width: 100%; + grid-row: 3; + grid-column: 1; + align-self: end; + } + + .App--index .IndexPage-results .ablecloud-DiscussionThumbnail { + width: 124px; + height: 92px; + grid-column: 2; + grid-row-start: 1; + grid-row-end: 4; + } + + .App--index .IndexPage-results .DiscussionListItem-info { + display: flex; + width: calc(~"100% - 96px"); + max-width: calc(~"100% - 96px"); + margin-top: 10px; + } + + .App--index .IndexPage-results .DiscussionListItem-info > .item-tags { + display: flex; + max-width: 100%; + overflow: hidden; + } + + .App--index .IndexPage-results .DiscussionListItem-badges { + position: static; + z-index: 2; + width: auto; + grid-row: 1; + grid-column: 1; + margin: 0; + } + + .App--index .IndexPage-results .DiscussionListItem-badges:empty { + display: none; + } + + .App--index .IndexPage-results .DiscussionListItem-badges .Badge--bestAnswer { + display: inline-flex; + width: auto; + min-width: 24px; + height: 24px; + align-items: center; + gap: 5px; + padding: 0 9px; + border-radius: 999px; + background: #d9f5e6; + color: #056038; + font-size: 12px; + font-weight: 730; + line-height: 24px; + } + + .App--index .IndexPage-results .DiscussionListItem-badges .Badge--bestAnswer::after { + content: "ํ•ด๊ฒฐ๋จ"; + } + + .App--index .IndexPage-results .DiscussionListItem-badges .Badge-icon { + font-size: 11px; + } + + .App--index .IndexPage-results .DiscussionListItem-count, + .App--index .IndexPage-results .DiscussionListItem:has(.DiscussionListItem-controls) .DiscussionListItem-count { + top: auto; + right: 14px; + bottom: 16px; + transform: none; + } + + .App--index .IndexPage-results .DiscussionListItem-controls { + top: 8px; + right: 8px; + transform: none; + } + + .App--index .IndexPage-nav::-webkit-scrollbar { + display: none; + width: 0; + height: 0; + } + + .App--index .IndexPage-results::-webkit-scrollbar { + display: none; + width: 0; + height: 0; + } + + // Flarum adds .affix to this list while the user page scrolls. Once the + // list becomes position: fixed, percentage width resolves against the + // viewport instead of the 280px navigation column and covers the content. + .UserPage .UserPage-nav > ul.affix { + width: 280px !important; + max-width: 280px; + box-sizing: border-box; + } + + .App--index > .App-navigation { + display: none !important; + } + + .DiscussionListItem { + margin-bottom: 9px; + z-index: 0; + overflow: visible; + } + + .DiscussionListItem:hover, + .DiscussionListItem:focus-within, + .DiscussionListItem:has(.DiscussionListItem-controls.open) { + z-index: 1050; + } + + .DiscussionListItem-content { + min-height: 62px; + padding: 11px 64px 11px 52px; + } + + .DiscussionListItem-main { + display: flex; + align-items: center; + gap: 14px; + min-width: 0; + min-height: 38px; + height: auto; + margin-right: 0 !important; + padding: 0 !important; + } + + .DiscussionListItem-author { + margin-top: 1px !important; + } + + .DiscussionListItem-title { + flex: 1 1 auto; + min-width: 180px; + margin: 0 !important; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .DiscussionListItem-info { + display: flex; + flex: 0 1 280px; + align-items: center; + height: auto !important; + min-width: 0; + margin: 0; } .DiscussionListItem-info > .item-tags { @@ -2097,6 +2948,28 @@ body { scrollbar-width: thin; } + // Keep the discussion context available, but never let the hover pane sit + // on top of the composer while the user is writing a new topic or reply. + // The explicit class is synchronized by forum.js and avoids depending on + // :has() support or on the composer being mounted below .App. + html.ablecloud-composer-open .DiscussionPage-list, + .App.ablecloud-composer-open .DiscussionPage-list, + .App:has(.Composer.visible:not(.minimized)) .DiscussionPage-list { + display: none !important; + } + + html.ablecloud-composer-open .DiscussionListItem, + html.ablecloud-composer-open .DiscussionListItem:hover, + html.ablecloud-composer-open .DiscussionListItem:focus-within, + html.ablecloud-composer-open .DiscussionListItem:has(.DiscussionListItem-controls.open) { + z-index: 0 !important; + } + + .App.hasPane.panePinned.ablecloud-composer-open .App-composer, + .App.hasPane.panePinned:has(.Composer.visible:not(.minimized)) .App-composer { + left: 0; + } + .DiscussionPage-list::-webkit-scrollbar { width: 6px; } @@ -2137,25 +3010,120 @@ body { } .DiscussionPage-list .DiscussionListItem-content { + display: grid; width: auto; - min-height: 60px; - padding: 10px 104px 10px 50px !important; + min-height: 104px; + grid-template-columns: minmax(0, 1fr); + grid-template-rows: auto auto; + align-content: start; + padding: 10px 12px 12px 50px !important; + } + + .DiscussionPage-list .DiscussionListItem-author { + position: absolute !important; + top: 11px; + left: 10px; + margin: 0 !important; + } + + .DiscussionPage-list .ablecloud-DiscussionMeta { + min-width: 0; + grid-row: 1; + grid-column: 1; + gap: 4px; + padding-right: 0; + font-size: 11px; + } + + .DiscussionPage-list .ablecloud-DiscussionMeta-category { + max-width: 118px; + flex: 0 1 auto; } .DiscussionPage-list .DiscussionListItem-main { + display: grid !important; width: 100% !important; - min-height: 38px; + min-width: 0; + min-height: 0; + grid-row: 2; + grid-column: 1; + grid-template-columns: minmax(0, 1fr); + grid-template-rows: auto auto; gap: 0; + margin-top: 4px; + padding-bottom: 22px !important; } .DiscussionPage-list .DiscussionListItem-title { + display: -webkit-box; + width: auto !important; min-width: 0; + grid-row: 1; + grid-column: 1; + overflow: hidden; font-size: 14px; + line-height: 1.4; + text-overflow: initial; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + white-space: normal; + } + + .DiscussionPage-list .ablecloud-DiscussionSummary { + grid-row: 2; + grid-column: 1; + margin: 4px 0 0; + font-size: 12px; + line-height: 1.45; + -webkit-line-clamp: 2; + } + + .DiscussionPage-list .ablecloud-DiscussionMain--withThumbnail { + grid-template-columns: minmax(0, 1fr) 72px; + grid-template-rows: auto auto; + column-gap: 10px; + } + + .DiscussionPage-list .ablecloud-DiscussionMain--withThumbnail .DiscussionListItem-title, + .DiscussionPage-list .ablecloud-DiscussionMain--withThumbnail .ablecloud-DiscussionSummary { + grid-column: 1; + } + + .DiscussionPage-list .ablecloud-DiscussionThumbnail { + width: 72px; + height: 54px; + grid-row-start: 1; + grid-row-end: 3; + grid-column: 2; + align-self: start; } .DiscussionPage-list .DiscussionListItem-info { display: none; } + + .DiscussionPage-list .DiscussionListItem-count, + .DiscussionPage-list .DiscussionListItem:has(.DiscussionListItem-controls) .DiscussionListItem-count { + top: auto; + right: 12px; + bottom: 12px; + transform: none; + } + + .DiscussionPage-list .DiscussionListItem-controls { + top: 10px; + right: 10px; + z-index: 1060; + transform: none; + } + + .DiscussionPage-list .DiscussionListItem-controls > .Dropdown-toggle { + width: 32px; + min-width: 32px; + height: 32px; + min-height: 32px; + padding: 0; + } } } @@ -2169,57 +3137,266 @@ body { min-height: 54px; } - .WelcomeHero { + // The mobile title control already carries the current discussion filter. + // Do not leave the desktop side-navigation card behind as an empty pill. + .App--index .IndexPage-nav { + display: block !important; + width: 0; + height: 0; + margin: 0; + overflow: visible; + pointer-events: none; + } + + .App--index .IndexPage-nav > ul { + width: 0; + height: 0; + margin: 0; padding: 0; + overflow: visible; + border: 0; + border-radius: 0; + background: transparent; + box-shadow: none; } - .WelcomeHero .container { - padding: 17px 15px; + .App--index .IndexPage-nav .item-nav, + .App--index .IndexPage-nav .item-newDiscussion { + pointer-events: auto; } - .TagsPage-content { - margin-top: 18px; - padding: 0 12px; + /* + * Mobile navigation drawer + * + * Desktop header controls are a horizontal flex row. Reset them to a + * vertical, scrollable menu while the Flarum drawer is open so every + * action remains inside the 270px drawer. + */ + .App.drawerOpen .App-drawer { + overflow-x: hidden; + overflow-y: auto; + overscroll-behavior: contain; + background: #fff; + box-shadow: 16px 0 36px rgba(20, 42, 75, 0.22); + scrollbar-width: none; } - .TagTiles { - grid-template-columns: minmax(0, 1fr); - gap: 14px; + .App.drawerOpen .App-drawer::-webkit-scrollbar { + display: none; } - .TagTile, - .TagTile.colored { - min-height: 0; - border-radius: 14px; + .App.drawerOpen .App-header, + .App.drawerOpen .App-header > .container { + width: 270px; + min-height: 100%; + height: auto; + box-sizing: border-box; + overflow: visible; + background: #fff; } - .TagTile-info { - padding: 17px 16px 12px; + &.ablecloud-composer-open .Composer:not(.fullScreen)::before { + z-index: 0; + pointer-events: none; } - .TagTile-info > .icon { - width: 34px; - height: 34px; - margin-right: 8px; - font-size: 16px; + .App.drawerOpen .App-header > .container { + padding: 0 14px 24px; } - .TagTile-name { - font-size: 17px; + .App.drawerOpen .Header-title { + display: flex; + width: 100%; + min-height: 66px; + height: 66px; + align-items: center; + box-sizing: border-box; + padding: 0 48px 0 2px; + border-bottom: 1px solid var(--ablecloud-brand-border); } - .TagTile-description { - min-height: 0; - margin-top: 12px; - font-size: 13px; + .App.drawerOpen .Header-title img { + width: auto; + max-width: 190px; + max-height: 30px; } - .TagTile-lastPostedDiscussion { - margin: 0 12px 12px; - padding: 10px 11px; + .App.drawerOpen .Header-primary, + .App.drawerOpen .Header-secondary, + .App.drawerOpen .Header-secondary > ul { + width: 100%; + height: auto; + min-height: 0; } - .TagsPage-content > .TagCloud { + .App.drawerOpen .Header-secondary > ul { + display: flex; + flex-direction: column; + align-items: stretch; + gap: 4px; + margin: 0; + padding: 12px 0 0; + } + + .App.drawerOpen .Header-secondary > ul > li { + display: block !important; + width: 100%; + height: auto; + min-height: 46px; + margin: 0; + } + + .App.drawerOpen .Header-secondary > ul > li > .Button, + .App.drawerOpen .Header-secondary .Dropdown-toggle { + display: flex; + width: 100%; + min-height: 46px; + align-items: center; + justify-content: flex-start; + box-sizing: border-box; + padding: 10px 12px; + border: 0; + border-radius: 10px; + background: transparent; + color: #405878; + box-shadow: none; + text-align: left; + } + + .App.drawerOpen .Header-secondary > ul > li > .Button:hover, + .App.drawerOpen .Header-secondary > ul > li > .Button:focus-visible, + .App.drawerOpen .Header-secondary .Dropdown-toggle:hover, + .App.drawerOpen .Header-secondary .Dropdown-toggle:focus-visible { + background: var(--ablecloud-brand-primary-soft); + color: var(--ablecloud-brand-primary-deep); + transform: none; + } + + .App.drawerOpen .Header-secondary .item-search, + .App.drawerOpen .Header-secondary .Search, + .App.drawerOpen .Header-secondary .Search-input, + .App.drawerOpen .Header-secondary .Search-input input { + width: 100% !important; + max-width: none; + box-sizing: border-box; + } + + .App.drawerOpen .Header-secondary .Search-input input { + padding-left: 42px; + } + + .App.drawerOpen .Header-secondary .item-locale .dropdown:not(.open) > .Dropdown-menu, + .App.drawerOpen .Header-secondary .item-session .dropdown:not(.open) > .Dropdown-menu { + display: none !important; + } + + .App.drawerOpen .Header-secondary .item-locale .dropdown.open > .Dropdown-menu, + .App.drawerOpen .Header-secondary .item-session .dropdown.open > .Dropdown-menu { + position: static; + display: block !important; + visibility: visible !important; + float: none; + width: 100%; + min-width: 0; + max-height: none; + box-sizing: border-box; + margin: 4px 0 0; + padding: 6px; + border: 1px solid var(--ablecloud-brand-border); + border-radius: 10px; + background: var(--ablecloud-brand-primary-faint); + box-shadow: none; + transform: none; + } + + // Flarum inserts a full-viewport dropdown backdrop on touch devices. + // The submenu is already contained by our drawer, so that backdrop must + // not cover or tint the drawer contents. + .App.drawerOpen .Header-secondary .Dropdown-menu > .dropdown-backdrop { + display: none !important; + } + + .App.drawerOpen .App-navigation { + z-index: 1060; + width: 270px; + pointer-events: none; + } + + .App.drawerOpen .Navigation-drawer { + position: fixed; + top: 11px; + left: 214px; + display: inline-flex; + width: 44px; + min-width: 44px; + height: 44px; + min-height: 44px; + align-items: center; + justify-content: center; + padding: 0; + border: 1px solid var(--ablecloud-brand-border); + border-radius: 50%; + background: #f4f8ff; + color: var(--ablecloud-brand-primary-deep); + box-shadow: 0 4px 12px rgba(24, 71, 142, 0.12); + pointer-events: auto; + } + + .App.drawerOpen .Navigation-drawer .fa-bars { + --ablecloud-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6 6l12 12M18 6 6 18' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E"); + } + + .WelcomeHero { + padding: 0; + } + + .WelcomeHero .container { + padding: 17px 15px; + } + + .TagsPage-content { + margin-top: 18px; + padding: 0 12px; + } + + .TagTiles { + grid-template-columns: minmax(0, 1fr); + gap: 14px; + } + + .TagTile, + .TagTile.colored { + min-height: 0; + border-radius: 14px; + } + + .TagTile-info { + padding: 17px 16px 12px; + } + + .TagTile-info > .icon { + width: 34px; + height: 34px; + margin-right: 8px; + font-size: 16px; + } + + .TagTile-name { + font-size: 17px; + } + + .TagTile-description { + min-height: 0; + margin-top: 12px; + font-size: 13px; + } + + .TagTile-lastPostedDiscussion { + margin: 0 12px 12px; + padding: 10px 11px; + } + + .TagsPage-content > .TagCloud { grid-template-columns: minmax(0, 1fr); gap: 10px; margin-top: 20px; @@ -2382,8 +3559,69 @@ body { } .DiscussionListItem-content { - min-height: 88px; - padding: 17px 44px 17px 60px !important; + display: grid; + min-height: 136px; + grid-template-columns: auto minmax(0, 1fr); + column-gap: 7px; + align-content: start; + padding: 14px 44px 14px 60px !important; + } + + .ablecloud-DiscussionMeta { + grid-row: 1; + grid-column: 2; + padding-right: 8px; + font-size: 11px; + } + + .DiscussionListItem-content:not(:has(.item-bestAnswer)) .ablecloud-DiscussionMeta { + grid-column-start: 1; + grid-column-end: -1; + } + + .ablecloud-DiscussionMeta-category { + max-width: 42%; + } + + .ablecloud-DiscussionSummary { + margin-top: 5px; + font-size: 13px; + -webkit-line-clamp: 2; + } + + .ablecloud-DiscussionMain--withThumbnail { + display: grid; + grid-template-columns: minmax(0, 1fr) 88px; + grid-template-rows: auto auto 1fr; + column-gap: 10px; + min-height: 72px; + } + + .ablecloud-DiscussionMain--withThumbnail .DiscussionListItem-title, + .ablecloud-DiscussionMain--withThumbnail .ablecloud-DiscussionSummary, + .ablecloud-DiscussionMain--withThumbnail .DiscussionListItem-info { + grid-column: 1; + } + + .ablecloud-DiscussionMain--withThumbnail .DiscussionListItem-title { + grid-row: 1; + } + + .ablecloud-DiscussionMain--withThumbnail .ablecloud-DiscussionSummary { + grid-row: 2; + } + + .ablecloud-DiscussionMain--withThumbnail .DiscussionListItem-info { + grid-row: 3; + align-self: end; + } + + .ablecloud-DiscussionThumbnail { + width: 88px; + height: 72px; + grid-column: 2; + grid-row-start: 1; + grid-row-end: 4; } .DiscussionListItem-title { @@ -2397,10 +3635,37 @@ body { .DiscussionListItem-main { min-width: 0; width: 100% !important; + grid-row: 2; + grid-column-start: 1; + grid-column-end: -1; margin-right: 0 !important; display: block; } + .DiscussionListItem-info { + margin-top: 9px; + padding-right: 58px; + } + + .DiscussionListItem-count { + top: auto; + right: 12px; + bottom: 15px; + transform: none; + } + + .DiscussionListItem-badges { + position: static; + width: auto; + grid-row: 1; + grid-column: 1; + margin: 0; + } + + .DiscussionListItem-badges:empty { + display: none; + } + .IndexPage .DiscussionListItem-title, .DiscussionListItem-title { width: auto !important; @@ -2428,23 +3693,30 @@ body { } .App-content { - padding-bottom: 58px; + padding-bottom: 16px; } #console-nav-footer { - height: 52px; - padding: 0; + top: auto; + right: 8px; + bottom: 16px; + width: 44px; + height: auto; + padding: 6px 0; + transform: none; } #footer-content { + flex-direction: column; justify-content: center; - gap: 16px; - padding: 0 12px; + gap: 7px; + padding: 0; } #floatleft, #floatright { - gap: 8px; + flex-direction: column; + gap: 7px; } #footer-content .feedback-menu-left, @@ -2484,6 +3756,273 @@ body { border-radius: 14px 14px 0 0; } + // All mobile dialogs share a fixed header, visible close action and an + // independently scrollable body inside the viewport. + .Modal-content { + display: flex; + max-height: calc(100vh - 24px); + flex-direction: column; + overflow: hidden; + border-radius: 14px; + } + + .Modal-header { + position: relative; + min-height: 58px; + flex: 0 0 auto; + box-sizing: border-box; + padding: 17px 62px 14px; + border-bottom: 1px solid var(--ablecloud-brand-border); + background: var(--ablecloud-brand-primary-faint); + } + + .Modal-body { + min-height: 0; + flex: 1 1 auto; + overflow-x: hidden; + overflow-y: auto; + overscroll-behavior: contain; + } + + .Modal-footer { + flex: 0 0 auto; + } + + .Modal-close { + top: 7px !important; + right: auto !important; + left: 9px !important; + width: 44px; + height: 44px; + } + + .Modal-close .Button { + display: inline-flex; + width: 44px; + min-width: 44px; + height: 44px; + min-height: 44px; + align-items: center; + justify-content: center; + padding: 0; + border: 1px solid var(--ablecloud-brand-border); + border-radius: 50%; + background: #fff; + color: var(--ablecloud-brand-primary-deep); + box-shadow: 0 4px 12px rgba(24, 71, 142, 0.1); + } + + .Modal-close .Button-icon { + display: none !important; + } + + .Modal-close .Button::after { + color: currentColor; + content: "\00d7"; + font-size: 28px; + font-weight: 400; + line-height: 1; + } + + &.ablecloud-new-discussion-open { + overflow: hidden; + } + + &.ablecloud-new-discussion-open .App-composer { + position: fixed; + z-index: 1040; + inset: 54px 0 0; + display: flex; + width: 100vw; + height: calc(100vh - 54px); + align-items: center; + justify-content: center; + padding: 8px; + background: rgba(20, 42, 75, 0.46); + pointer-events: auto; + } + + &.ablecloud-new-discussion-open .App-composer > .container { + width: 100%; + height: 100%; + margin: 0; + padding: 0; + } + + &.ablecloud-new-discussion-open .Composer:not(.fullScreen) { + position: relative; + bottom: auto !important; + width: 100%; + height: 100% !important; + min-height: 0; + margin: 0 !important; + overflow: visible !important; + border-radius: 14px; + background: #fff; + } + + &.ablecloud-new-discussion-open .Composer:not(.fullScreen) .Composer-handle, + &.ablecloud-new-discussion-open .Composer:not(.fullScreen) .Composer-controls .item-minimize, + &.ablecloud-new-discussion-open .Composer:not(.fullScreen) .Composer-controls .item-fullScreen { + display: none; + } + + &.ablecloud-new-discussion-open .Composer:not(.fullScreen) .Composer-controls { + position: absolute; + top: 8px; + right: auto; + left: 8px; + z-index: 12; + display: block; + margin: 0; + } + + &.ablecloud-new-discussion-open .Composer:not(.fullScreen) .Composer-controls .item-close { + position: static; + display: block !important; + width: 44px; + height: 44px; + margin: 0; + } + + &.ablecloud-new-discussion-open .Composer:not(.fullScreen) .Composer-controls .item-close .Button { + display: inline-flex; + width: 44px; + min-width: 44px; + height: 44px; + min-height: 44px; + align-items: center; + justify-content: center; + padding: 0; + border: 1px solid var(--ablecloud-brand-border); + border-radius: 50%; + background: #fff; + color: var(--ablecloud-brand-primary-deep); + box-shadow: 0 4px 12px rgba(24, 71, 142, 0.1); + } + + &.ablecloud-new-discussion-open .Composer:not(.fullScreen) .Composer-controls .item-close .Button-icon { + display: none; + } + + &.ablecloud-new-discussion-open .Composer:not(.fullScreen) .Composer-controls .item-close .Button::after { + color: currentColor; + content: "\00d7"; + font-size: 28px; + font-weight: 400; + line-height: 1; + } + + &.ablecloud-new-discussion-open .Composer:not(.fullScreen) .ComposerBody-header > .item-title { + padding-right: 48px; + padding-left: 48px; + } + + &.ablecloud-new-discussion-open .Composer:not(.fullScreen) .Composer-content, + &.ablecloud-new-discussion-open .Composer:not(.fullScreen) .ComposerBody, + &.ablecloud-new-discussion-open .Composer:not(.fullScreen) .ComposerBody-content { + height: 100% !important; + min-height: 0; + } + + &.ablecloud-new-discussion-open .Composer:not(.fullScreen) .ComposerBody-content, + &.ablecloud-new-discussion-open .Composer:not(.fullScreen) .TextEditor { + display: flex; + flex-direction: column; + } + + &.ablecloud-new-discussion-open .Composer:not(.fullScreen) .ComposerBody-editor { + display: flex; + height: auto !important; + min-height: 0; + flex: 1 1 auto; + flex-direction: column; + } + + &.ablecloud-new-discussion-open .Composer:not(.fullScreen) .TextEditor { + height: 100% !important; + min-height: 0; + flex: 1 1 auto; + } + + &.ablecloud-new-discussion-open .Composer:not(.fullScreen) .TextEditor-editorContainer { + height: auto !important; + min-height: 0; + flex: 1 1 auto; + } + + &.ablecloud-new-discussion-open .Composer:not(.fullScreen) .ComposerBody-mentionsWrapper, + &.ablecloud-new-discussion-open .Composer:not(.fullScreen) .ComposerBody-emojiWrapper, + &.ablecloud-new-discussion-open .Composer:not(.fullScreen) .TextEditor-editor { + height: 100% !important; + min-height: 0; + } + + // Use the same explicit close action for new discussions and replies. + // Mobile users should never have to minimize a composer to dismiss it. + &.ablecloud-composer-open .Composer:not(.fullScreen) .Composer-handle, + &.ablecloud-composer-open .Composer:not(.fullScreen) .Composer-controls .item-minimize, + &.ablecloud-composer-open .Composer:not(.fullScreen) .Composer-controls .item-fullScreen { + display: none !important; + } + + &.ablecloud-composer-open .Composer:not(.fullScreen) .Composer-controls { + position: absolute; + top: 8px; + right: auto; + left: 8px; + z-index: 1002; + display: block; + margin: 0; + } + + &.ablecloud-composer-open .Composer:not(.fullScreen) .Composer-controls .item-close { + position: static; + display: block !important; + width: 44px; + height: 44px; + margin: 0; + } + + &.ablecloud-composer-open .Composer:not(.fullScreen) .Composer-controls .item-close .Button { + display: inline-flex; + width: 44px; + min-width: 44px; + height: 44px; + min-height: 44px; + align-items: center; + justify-content: center; + padding: 0; + border: 1px solid var(--ablecloud-brand-border); + border-radius: 50%; + background: #fff; + color: var(--ablecloud-brand-primary-deep); + box-shadow: 0 4px 12px rgba(24, 71, 142, 0.1); + } + + &.ablecloud-composer-open .Composer:not(.fullScreen) .Composer-controls .item-close .Button-icon { + display: none !important; + } + + &.ablecloud-composer-open .Composer:not(.fullScreen) .Composer-controls .item-close .Button::after { + color: currentColor; + content: "\00d7"; + font-size: 28px; + font-weight: 400; + line-height: 1; + } + + &.ablecloud-composer-open .Composer:not(.fullScreen) .ComposerBody-header > .item-title { + padding-left: 48px; + } + + .ablecloud-EmojiPicker { + right: 12px; + bottom: 72px; + width: calc(100% - 24px); + max-width: 344px; + } + .Modal-content { border-radius: 14px; } @@ -2534,6 +4073,393 @@ body { } } +/* + * Discussion reading flow + * + * Keep the established 1100px content width and the adjacent quick-link rail. + * The index no longer uses a welcome banner. Discussion pages use a compact, + * content-aligned title block and the native Flarum vertical navigator. + */ +.App--index .WelcomeHero { + display: none !important; +} + +.App--discussion .DiscussionHero { + border-bottom: 1px solid var(--ablecloud-brand-border); + background: #ffffff !important; + color: var(--ablecloud-brand-ink) !important; + text-align: left; +} + +.App--discussion .DiscussionHero .container { + width: 100%; + max-width: 1165px; + box-sizing: border-box; + padding: 17px 80px 15px 15px; +} + +.App--discussion .DiscussionHero-items { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 8px 10px; + margin: 0; +} + +.App--discussion .DiscussionHero-items > li { + display: block; + float: none; + margin: 0; +} + +.App--discussion .DiscussionHero-items > .item-badges { + order: 1; +} + +.App--discussion .DiscussionHero-items > .item-tags { + min-width: 0; + order: 2; +} + +.App--discussion .DiscussionHero-items > .item-title { + flex: 0 0 100%; + order: 3; +} + +.App--discussion .DiscussionHero-items > .item-ablecloudMeta { + flex: 0 0 100%; + order: 4; +} + +.App--discussion .DiscussionHero-title { + max-width: 900px; + margin: 2px 0 0; + color: var(--ablecloud-brand-ink) !important; + font-size: 26px; + font-weight: 780; + letter-spacing: -0.025em; + line-height: 1.38; +} + +.App--discussion .DiscussionHero .Badge--bestAnswer { + display: inline-flex; + align-items: center; + gap: 5px; + width: auto; + height: 28px; + padding: 0 10px; + border: 1px solid #86d2aa; + border-radius: 999px; + background: #d9f5e6; + color: #056038; + box-shadow: none; +} + +.App--discussion .DiscussionHero .Badge--bestAnswer::after { + content: "ํ•ด๊ฒฐ๋จ"; + font-size: 12px; + font-weight: 750; +} + +.App--discussion .DiscussionHero .Badge--bestAnswer .Badge-icon { + font-size: 12px; +} + +.ablecloud-DiscussionDetailMeta { + display: flex !important; + align-items: center; + min-height: 30px; + gap: 7px; + color: var(--ablecloud-brand-muted); + font-size: 14px; + line-height: 1.45; +} + +.ablecloud-DiscussionDetailMeta-author { + color: var(--ablecloud-brand-primary-deep); + font-weight: 720; +} + +.App--discussion .DiscussionHero .ablecloud-DiscussionDetailMeta-author { + color: var(--ablecloud-brand-primary-deep) !important; +} + +.ablecloud-DiscussionDetailMeta-separator { + color: #9aa9bc; +} + +.ablecloud-SolutionJump { + display: inline-flex; + align-items: center; + min-height: 30px; + margin-left: auto; + padding: 4px 10px; + border: 1px solid #86d2aa; + border-radius: 999px; + background: #f2fbf6; + color: #056038 !important; + font-size: 13px; + font-weight: 750; +} + +.ablecloud-SolutionJump::before { + margin-right: 6px; + content: "โ†“"; + font-weight: 900; +} + +.App--discussion .DiscussionPage-discussion > .container { + width: 100%; + max-width: 1165px; + box-sizing: border-box; + padding-right: 80px; +} + +.App--discussion .DiscussionPage-stream, +.App--discussion .PostStream { + width: 100%; + max-width: 1100px; + box-sizing: border-box; +} +.App--discussion .PostStream { + margin: 0; +} + +.App--discussion .PostStream-item, +.App--discussion .PostStream-item:has(.Post-actions) { + margin: 0 !important; + padding: 0; + border-bottom: 1px solid var(--ablecloud-brand-border); +} + +.App--discussion .PostStream-item:last-child { + border-bottom: 0; +} + +.App--discussion .PostStream-item article.Post { + margin: 0; + padding-top: 24px; + padding-bottom: 24px; +} + +.App--discussion article.Post > div { + border: 0; + border-radius: 0; + background: transparent; + box-shadow: none; + padding: 0; +} + +.App--discussion article.Post > div:hover { + border-color: transparent; + box-shadow: none; +} + +.App--discussion .Post-header { + margin-bottom: 12px; +} + +.App--discussion .Post-body { + max-width: none; + color: #26364d; + font-size: 16px; + line-height: 1.82; +} + +.App--discussion .Post-body p, +.App--discussion .Post-body li { + max-width: 82ch; +} + +.App--discussion .Post-body blockquote { + max-width: 82ch; + margin: 18px 0; + padding: 12px 16px; + border-left: 3px solid #8cb0ec; + border-radius: 0 10px 10px 0; + background: #f4f8ff; + color: #425875; +} + +.App--discussion .Post-body pre { + max-width: 100%; + border: 1px solid #d5dfec; + border-radius: 11px; + background: #f6f8fb; + color: #203047; + overflow: auto; +} + +.App--discussion .Post-body pre.ablecloud-TechnicalBlock--collapsed { + max-height: 480px; +} + +.ablecloud-TechnicalBlock-toggle { + min-height: 36px; + margin-top: 8px; + padding: 0 11px; + border: 1px solid var(--ablecloud-brand-border); + background: #f4f8ff; + color: var(--ablecloud-brand-primary-deep); + font-size: 13px; +} + +.App--discussion .Post-actions { + position: static; + right: auto; + bottom: auto; + float: none; + max-width: none; + margin: 18px 0 0; + opacity: 1 !important; +} + +.App--discussion .Post-actions > ul { + justify-content: flex-start; +} + +.App--discussion article.TechFlowPost--assistant > div, +.App--discussion article.Post:has(> div > .Post-header .PostUser-name a[href="/u/TechFlow-Assistant"]) > div, +.App--discussion article.TechFlowPost--knowledge-base > div, +.App--discussion article.Post.Post--bestAnswer > div, +.App--discussion article.TechFlowPost--needs-information > div { + padding: 20px 22px; + border-radius: 14px; + box-shadow: none; +} + +.App--discussion article.TechFlowPost--assistant > div, +.App--discussion article.Post:has(> div > .Post-header .PostUser-name a[href="/u/TechFlow-Assistant"]) > div { + border: 1px solid #a8c5f2; +} + +.App--discussion article.TechFlowPost--knowledge-base > div, +.App--discussion article.Post.Post--bestAnswer > div { + border: 1px solid #86d2aa; + border-left: 5px solid #078248; +} + +.App--discussion article.TechFlowPost--needs-information > div { + border: 1px solid #e4b955; + border-left: 5px solid #d39a18; +} + +.App--discussion .item-bestAnswerPost { + display: none !important; +} + +.App--discussion #ablecloud-solution-post { + scroll-margin-top: 76px; +} + +.ablecloud-InlineReplyPrompt { + margin: 0 20px 24px 105px; +} + +.ablecloud-InlineReplyPrompt-button { + display: flex; + align-items: center; + width: 100%; + min-height: 48px; + justify-content: flex-start; + padding: 0 16px; + border: 1px dashed #9bb8e4; + border-radius: 12px; + background: #f7faff; + color: #4c6382; + font-weight: 650; + box-shadow: none; +} + +.ablecloud-InlineReplyPrompt-button::before { + width: 18px; + height: 18px; + margin-right: 9px; + background: currentColor; + content: ""; + -webkit-mask-image: var(--ablecloud-icon-reply); + mask-image: var(--ablecloud-icon-reply); + -webkit-mask-position: center; + mask-position: center; + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-size: contain; + mask-size: contain; + --ablecloud-icon-reply: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m9 7-5 5 5 5M5 12h8a6 6 0 0 1 6 6v1' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); +} + +.ablecloud-InlineReplyPrompt-button:hover, +.ablecloud-InlineReplyPrompt-button:focus-visible { + border-color: var(--ablecloud-brand-primary); + background: #eef5ff; + color: var(--ablecloud-brand-primary-deep); +} + +@media (min-width: 768px) { + .App--discussion .DiscussionHero { + margin-top: 16px; + } +} + +@media (max-width: 767px) { + .App--discussion .DiscussionHero .container { + padding: 17px 15px 15px; + } + + .App--discussion .DiscussionHero-title { + font-size: 21px; + line-height: 1.4; + } + + .ablecloud-DiscussionDetailMeta { + flex-wrap: wrap; + font-size: 13px; + } + + .ablecloud-SolutionJump { + margin-left: 0; + } + + .App--discussion .DiscussionPage-stream, + .App--discussion .PostStream { + width: 100% !important; + padding-right: 8px; + padding-left: 8px; + } + + .App--discussion .DiscussionPage-discussion > .container { + max-width: none; + padding-right: 15px; + padding-left: 15px; + } + + .App--discussion .PostStream-item article.Post { + padding-top: 18px; + padding-bottom: 18px; + } + + .App--discussion article.TechFlowPost--assistant > div, + .App--discussion article.Post:has(> div > .Post-header .PostUser-name a[href="/u/TechFlow-Assistant"]) > div, + .App--discussion article.TechFlowPost--knowledge-base > div, + .App--discussion article.Post.Post--bestAnswer > div, + .App--discussion article.TechFlowPost--needs-information > div { + padding: 16px; + } + + .App--discussion .Post-actions { + position: static; + margin-top: 16px; + } + + .App--discussion .PostStream-item:has(.Post-actions) { + padding-bottom: 0; + } + + .ablecloud-InlineReplyPrompt { + margin: 0 12px 18px 60px; + } +} + @media (prefers-reduced-motion: reduce) { body *, body *::before, diff --git a/deploy/flarum/tests/test_community_theme.py b/deploy/flarum/tests/test_community_theme.py index 9191ce3..a0e1706 100644 --- a/deploy/flarum/tests/test_community_theme.py +++ b/deploy/flarum/tests/test_community_theme.py @@ -98,6 +98,22 @@ def test_embedded_svg_icon_and_navigation_contract(self) -> None: ".fa-paper-plane", ".fa-photo-video", ".fa-smile", + ".fa-eye", + ".fa-eye-slash", + ".fa-bold", + ".fa-italic", + ".fa-code", + ".fa-quote-left", + ".fa-link", + ".fa-image", + ".fa-list-ul", + ".fa-list-ol", + ".fa-plus", + ".fa-strikethrough", + ".fa-subscript", + ".fa-superscript", + ".fa-terminal", + ".fa-caret-square-right", ): with self.subTest(icon=icon): self.assertIn(icon, self.less) @@ -107,7 +123,7 @@ def test_embedded_svg_icon_and_navigation_contract(self) -> None: self.assertIn("font-size: 18px", self.less) self.assertIn(".Header-secondary > ul", self.less) self.assertIn("height: 44px", self.less) - self.assertIn("max-width: 1100px", self.less) + self.assertIn("max-width: 1165px", self.less) self.assertIn("--ablecloud-footer-icon", self.less) self.assertGreaterEqual(len(re.findall(r"min-height:\s*44px", self.less)), 3) self.assertGreaterEqual(contrast("#155eef", "#ffffff"), 4.5) @@ -134,10 +150,193 @@ def test_tag_date_format_contract(self) -> None: self.assertIn("data-ablecloud-start-from-top", self.forum_js) self.assertIn("/(\\/d\\/[^/]+)\\/\\d+\\/?$/", self.forum_js) self.assertIn("openDiscussionFromTop", self.forum_js) - self.assertIn("window.location.assign(href)", self.forum_js) + self.assertNotIn("window.location.assign(href)", self.forum_js) + self.assertNotIn("event.preventDefault()", self.forum_js.split("function openDiscussionFromTop", 1)[1].split("function captureDiscussionListSnapshot", 1)[0]) + self.assertIn("window.scrollTo(0, 0)", self.forum_js) self.assertIn("document.addEventListener('click', openDiscussionFromTop, true)", self.forum_js) + self.assertIn("captureDiscussionListSnapshot", self.forum_js) + self.assertIn("ensureFallbackDiscussionPane", self.forum_js) + self.assertIn("ablecloud-community-discussion-list", self.forum_js) + self.assertIn("data-ablecloud-fallback-pane", self.forum_js) + self.assertIn("forumApp.pane.enable()", self.forum_js) + self.assertNotIn("addFallbackPanePinButton", self.forum_js) + self.assertIn("document.addEventListener('mousemove', showFallbackPaneFromEdge", self.forum_js) + self.assertIn("syncComposerPaneState", self.forum_js) + self.assertIn("app.classList.toggle('ablecloud-composer-open', composerOpen)", self.forum_js) + self.assertIn("document.documentElement.classList.toggle('ablecloud-composer-open', composerOpen)", self.forum_js) + self.assertIn("composerOpen && forumApp.pane", self.forum_js) + self.assertIn("forumApp.pane.hide()", self.forum_js) + + def test_infinite_scroll_emoji_and_new_discussion_dialog_contract(self) -> None: + for script_contract in ( + "enhanceInfiniteDiscussionLoading", + "new window.IntersectionObserver", + "rootMargin: '0px 0px 120px 0px'", + "currentButton.click()", + "data-ablecloud-auto-load", + "handleEmojiPaletteClick", + "ablecloud-EmojiPicker", + "data-ablecloud-emoji", + "document.execCommand('insertText'", + "์‚ฝ์ž…ํ•  ์ด๋ชจ์ง€๋ฅผ ์„ ํƒํ•˜์„ธ์š”.", + "ablecloud-new-discussion-open", + "attributeFilter: ['class']", + "window.setTimeout(schedule, 150)", + "window.setTimeout(schedule, 600)", + ): + with self.subTest(script_contract=script_contract): + self.assertIn(script_contract, self.forum_js) + + for style_contract in ( + "&.ablecloud-new-discussion-open .App-composer", + "background: rgba(20, 42, 75, 0.46)", + "max-width: 960px", + "height: calc(100vh - 120px) !important", + ".ablecloud-EmojiPicker-grid", + "grid-template-columns: repeat(6, minmax(0, 1fr))", + ".EmojiDropdown > li:not(.Dropdown-header) > button", + ): + with self.subTest(style_contract=style_contract): + self.assertIn(style_contract, self.less) + + def test_mobile_navigation_and_dialog_contract(self) -> None: + for script_contract in ( + "syncMobileNavigationState", + "ํƒ์ƒ‰ ์„œ๋ž ๋‹ซ๊ธฐ", + "ํƒ์ƒ‰ ์„œ๋ž ์—ด๊ธฐ", + "handleMobileOverlayKeydown", + "handleMobileNavigationClick", + "handleMobileComposerCloseClick", + "document.body.classList.contains('ablecloud-composer-open')", + "forumApp.composer.hide()", + "์ž‘์„ฑ ์ค‘์ธ ๋‚ด์šฉ์„ ์ทจ์†Œํ•˜๊ณ  ๋Œ€ํ™”์ƒ์ž๋ฅผ ๋‹ซ์œผ์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?", + "drawerToggle.click()", + ): + with self.subTest(script_contract=script_contract): + self.assertIn(script_contract, self.forum_js) + + for style_contract in ( + ".App--index .IndexPage-nav", + ".App--index .IndexPage-nav > ul", + "pointer-events: none", + ".App.drawerOpen .App-drawer", + ".App.drawerOpen .Header-secondary > ul", + ".dropdown:not(.open) > .Dropdown-menu", + ".App.drawerOpen .Header-secondary .Dropdown-menu > .dropdown-backdrop", + "flex-direction: column", + ".App.drawerOpen .Navigation-drawer", + ".App.drawerOpen .Navigation-drawer .fa-bars", + ".Modal-body", + "max-height: calc(100vh - 24px)", + ".Modal-close .Button", + ".Modal-close .Button::after", + ".Composer-controls .item-close", + ".Composer-controls .item-close .Button::after", + "&.ablecloud-composer-open .Composer:not(.fullScreen) .Composer-controls .item-minimize", + "&.ablecloud-composer-open .Composer:not(.fullScreen) .Composer-controls .item-close", + "z-index: 1002", + "display: block !important", + ): + with self.subTest(style_contract=style_contract): + self.assertIn(style_contract, self.less) + + def test_reddit_style_discussion_item_structure_contract(self) -> None: + for script_contract in ( + "enhanceDiscussionListItems", + "forumApp.store.getById('discussions'", + "discussion.firstPost()", + "firstPost.contentPlain()", + "ablecloud-DiscussionMeta", + "ablecloud-DiscussionSummary", + "formatDiscussionCreatedAt", + "data-ablecloud-post-structure", + "meta.setAttribute('aria-label', 'ํ† ๋ก  ์ž‘์„ฑ ์ •๋ณด')", + "badges.setAttribute('aria-label', 'ํ•ด๊ฒฐ๋œ ํ† ๋ก ')", + "findDiscussionThumbnail", + "firstPost.contentHtml()", + "template.content.querySelector('img[src]')", + "thumbnailImage.loading = 'lazy'", + "ablecloud-DiscussionMain--withThumbnail", + "ablecloud-DiscussionThumbnail", + ): + with self.subTest(script_contract=script_contract): + self.assertIn(script_contract, self.forum_js) + + for style_contract in ( + ".ablecloud-DiscussionMeta", + ".ablecloud-DiscussionMeta-category", + ".ablecloud-DiscussionSummary", + "-webkit-line-clamp: 2", + ".App--index .IndexPage-results .DiscussionListItem-badges .Badge--bestAnswer::after", + 'content: "ํ•ด๊ฒฐ๋จ"', + "min-height: 136px", + "font-size: 18px", + ".ablecloud-DiscussionThumbnail", + "object-fit: cover", + "grid-template-columns: minmax(0, 1fr) 124px", + "grid-template-columns: minmax(0, 1fr) 88px", + ): + with self.subTest(style_contract=style_contract): + self.assertIn(style_contract, self.less) + + def test_discussion_detail_reading_flow_contract(self) -> None: + for script_contract in ( + "enhanceDiscussionDetail", + "data-ablecloud-reading-flow", + "ablecloud-DiscussionDetailMeta", + "ablecloud-solution-post", + "ํ•ด๊ฒฐ ๋‹ต๋ณ€ ๋ณด๊ธฐ", + "scrollToSolution", + "ablecloud-InlineReplyPrompt", + "๋‹ต๋ณ€์„ ์ž‘์„ฑํ•ด ์ฃผ์„ธ์š”", + "enhanceLongTechnicalBlocks", + "์ „์ฒด ๋กœ๊ทธ ๋ณด๊ธฐ", + "duplicateSolution.setAttribute('aria-hidden', 'true')", + ): + with self.subTest(script_contract=script_contract): + self.assertIn(script_contract, self.forum_js) + + for style_contract in ( + ".App--index .WelcomeHero", + ".App--discussion .DiscussionHero", + ".App--discussion .DiscussionHero-title", + ".ablecloud-DiscussionDetailMeta", + ".ablecloud-SolutionJump", + ".App--discussion .DiscussionPage-stream", + ".App--discussion .PostStream-item", + ".App--discussion .item-bestAnswerPost", + ".ablecloud-InlineReplyPrompt", + ".ablecloud-TechnicalBlock--collapsed", + "native Flarum vertical navigator", + ): + with self.subTest(style_contract=style_contract): + self.assertIn(style_contract, self.less) + + self.assertIn("display: none !important", self.less) + self.assertIn("max-width: 1100px", self.less) + self.assertIn("max-width: 1165px", self.less) + self.assertIn("padding-right: 80px", self.less) + self.assertIn("scrollbar-gutter: stable", self.less) + self.assertIn(".App--discussion .App-header > .container", self.less) + self.assertIn("margin-top: 16px", self.less) + self.assertIn("justify-content: flex-start", self.less) def test_compact_hero_and_navigation_width_contract(self) -> None: + self.assertIn("--ablecloud-page-shell-width: 1165px", self.less) + self.assertIn("--ablecloud-page-rail-space: 80px", self.less) + for shell_selector in ( + ".App-header > .container", + ".WelcomeHero > .container", + ".TagHero > .container", + ".UserHero .container", + ".TagsPage > .container", + ".UserPage > .container", + ): + with self.subTest(shell_selector=shell_selector): + self.assertIn(shell_selector, self.less) + self.assertIn("max-width: var(--ablecloud-page-shell-width)", self.less) + self.assertIn("padding-right: var(--ablecloud-page-rail-space)", self.less) + self.assertIn("Shared desktop shell", self.less) self.assertIn(".WelcomeHero .container", self.less) self.assertIn("font-size: 28px", self.less) self.assertIn("width: 280px", self.less) @@ -179,8 +378,35 @@ def test_compact_hero_and_navigation_width_contract(self) -> None: self.assertIn("visibility: hidden", self.less) self.assertIn(".fa-bolt", self.less) self.assertIn(".DiscussionPage-list", self.less) + self.assertIn(".DiscussionPage-list .ablecloud-DiscussionMeta", self.less) + self.assertIn(".DiscussionPage-list .ablecloud-DiscussionMain--withThumbnail", self.less) + self.assertIn("grid-template-columns: minmax(0, 1fr) 72px", self.less) + self.assertIn(".DiscussionPage-list .ablecloud-DiscussionSummary", self.less) + self.assertIn(".DiscussionPage-list .DiscussionListItem-count", self.less) self.assertIn("height: calc(100vh - 68px)", self.less) self.assertIn("scrollbar-width: thin", self.less) + self.assertIn("html.ablecloud-composer-open .DiscussionPage-list", self.less) + self.assertIn(".App.ablecloud-composer-open .DiscussionPage-list", self.less) + self.assertIn(".App.hasPane.panePinned.ablecloud-composer-open .App-composer", self.less) + self.assertIn(".DiscussionPage-list .DiscussionListItem-controls", self.less) + self.assertIn("top: 10px", self.less) + self.assertIn("width: 32px", self.less) + self.assertIn(".Composer.active:not(.fullScreen)", self.less) + self.assertIn("min-height: 336px", self.less) + self.assertIn(".Composer.active:not(.fullScreen) .Composer-content", self.less) + self.assertIn("height: 100% !important", self.less) + self.assertIn(".Composer.active:not(.fullScreen) .ComposerBody-content", self.less) + self.assertIn(".Composer.active:not(.fullScreen) .TextEditor", self.less) + self.assertIn("display: flex", self.less) + self.assertIn("flex-direction: column", self.less) + self.assertIn("flex: 1 1 auto", self.less) + self.assertIn(".Composer.active:not(.fullScreen) .TextEditor-editorContainer", self.less) + self.assertIn(".Composer.active:not(.fullScreen) .TextEditor-controls", self.less) + self.assertIn("position: static", self.less) + self.assertIn("bottom: auto", self.less) + self.assertIn("padding-bottom: 12px", self.less) + self.assertIn("html.ablecloud-composer-open .DiscussionListItem:hover", self.less) + self.assertIn("z-index: 0 !important", self.less) self.assertIn(".Search-input input:focus-visible", self.less) self.assertIn("outline: none !important", self.less) self.assertIn('.Header-secondary .Dropdown-menu .Button-icon:not([class*="fa-"])', self.less) @@ -194,6 +420,16 @@ def test_compact_hero_and_navigation_width_contract(self) -> None: self.assertIn("transform: none", self.less) self.assertIn('.Search-input input[type="search"]::-webkit-search-cancel-button', self.less) self.assertIn("-webkit-appearance: none", self.less) + self.assertIn(".fa-times-circle", self.less) + self.assertIn(".Search .Search-input::before", self.less) + self.assertIn(".Search .Search-clear .fa-times-circle::before", self.less) + self.assertIn(".Header-secondary .Search .Search-results", self.less) + self.assertIn(".Search-results .SolutionSearchResult > a", self.less) + self.assertIn("display: block !important", self.less) + self.assertIn('width: ~"min(560px, calc(100vw - 24px))"', self.less) + self.assertIn(".Search-results .SolutionSearchResult-tags .TagsLabel", self.less) + self.assertIn(".Search-results .SolutionSearchResult-bestAnswer", self.less) + self.assertIn("-webkit-line-clamp: 2", self.less) self.assertIn(".DiscussionListItem-count::before", self.less) self.assertIn(".DiscussionListItem.unread .DiscussionListItem-count:hover::before", self.less) self.assertIn(".Post-actions", self.less) @@ -213,9 +449,33 @@ def test_compact_hero_and_navigation_width_contract(self) -> None: self.assertIn(".App--index > .App-navigation", self.less) self.assertIn("display: none !important", self.less) self.assertNotIn(".App--index > .App-navigation .Navigation-drawer", self.less) + self.assertIn(".App--index > .App-content", self.less) + self.assertIn("html:has(body > .App--index)", self.less) + self.assertIn("body:has(> .App--index)", self.less) + self.assertIn(".App--index .IndexPage-results .DiscussionList", self.less) + self.assertIn(".App--index .IndexPage-results .DiscussionList-discussions", self.less) + self.assertIn("reserved rail sits immediately next", self.less) + self.assertIn("Follow Reddit's flat feed composition", self.less) + self.assertIn(".App--index .IndexPage-results::-webkit-scrollbar", self.less) + self.assertIn(".App--index .IndexPage-nav::-webkit-scrollbar", self.less) + self.assertIn("scrollbar-width: none", self.less) + self.assertGreaterEqual(self.less.count("scrollbar-width: none"), 2) + self.assertIn("overflow: visible", self.less) + self.assertIn("flex: 0 0 auto", self.less) + self.assertIn("background: rgba(21, 94, 239, 0.055)", self.less) + self.assertIn("overscroll-behavior: contain", self.less) + self.assertIn("position: fixed", self.less) + self.assertIn("bottom: 0", self.less) + self.assertNotIn("bottom: 56px", self.less) + deploy_script = (ROOT / "deploy" / "flarum" / "apply-community-theme-update.sh").read_text(encoding="utf-8") + self.assertIn('chown -R www-data:www-data "$extension_root"', deploy_script) + self.assertIn('find "$extension_root" -type d -exec chmod 0755 {} +', deploy_script) self.assertIn(".UserHero .UserCard-profile", self.less) self.assertIn(".UserPage .UserPage-nav", self.less) self.assertIn(".UserPage .UserPage-content", self.less) + self.assertIn(".UserPage .UserPage-nav > ul.affix", self.less) + self.assertIn("width: 280px !important", self.less) + self.assertIn("max-width: 280px", self.less) self.assertIn(".UserHero .UserCard-avatar", self.less) self.assertIn("position: absolute", self.less) self.assertIn("likes_link: ์ข‹์•„์š”", self.korean) @@ -251,6 +511,16 @@ def test_compact_hero_and_navigation_width_contract(self) -> None: self.assertIn("#floatright .feedback-menu-right:first-of-type", self.less) self.assertIn("#floatright .feedback-menu-right:last-of-type", self.less) self.assertIn("mask-image: var(--ablecloud-footer-icon)", self.less) + self.assertIn("legacy footer bar is replaced with a fixed vertical quick-link rail", self.less) + self.assertIn(".App--index .App-header > .container", self.less) + self.assertIn(".App--index .WelcomeHero > .container", self.less) + self.assertIn("Align the top navigation and welcome area", self.less) + self.assertIn('right: ~"max(12px, calc((100vw - 1165px) / 2 + 16px))"', self.less) + self.assertIn("flex-direction: column", self.less) + self.assertIn("max-width: 1165px", self.less) + self.assertIn("padding-right: 80px", self.less) + self.assertIn("transform: translateY(-50%)", self.less) + self.assertIn("bottom: 16px", self.less) def test_rehearsal_is_staging_only_and_preserves_content(self) -> None: self.assertIn('APP_ROOT" == "/srv/techflow-flarum-staging/app"', self.rehearsal) @@ -266,6 +536,17 @@ def test_rehearsal_is_staging_only_and_preserves_content(self) -> None: self.assertIn("flarum-likes.forum.user.likes_link", locale_warmer) self.assertIn("fof-upload.forum.buttons.media", locale_warmer) + def test_production_theme_update_is_path_scoped(self) -> None: + updater = ( + ROOT / "deploy" / "flarum" / "apply-community-theme-update.sh" + ).read_text(encoding="utf-8") + self.assertIn('/var/www/html/extensions/ablecloud-community-theme', updater) + self.assertIn('/tmp/ablecloud-community-theme-*', updater) + self.assertIn('/var/backups/techflow-flarum/theme-*', updater) + self.assertIn('rsync -a --delete "$source_root/" "$extension_root/"', updater) + self.assertIn("UserPage-nav > ul.affix", updater) + self.assertIn("RESULT=PASS", updater) + if __name__ == "__main__": unittest.main() diff --git a/docs/evidence/issue-73/community-index-fixed-feed-validation.json b/docs/evidence/issue-73/community-index-fixed-feed-validation.json new file mode 100644 index 0000000..c93b8fb --- /dev/null +++ b/docs/evidence/issue-73/community-index-fixed-feed-validation.json @@ -0,0 +1,324 @@ +{ + "schema_version": "2.0", + "executed_at": "2026-08-20T04:09:15Z", + "scope": "Flarum index infinite loading, Korean emoji insertion, central new-discussion dialog, Reddit-style discussion list and detail reading flow", + "design_reference": "https://www.reddit.com/new/?feed=home", + "navigation_reference": "https://community.ablecloud.io/settings", + "change": { + "document_scroll": "disabled on desktop index", + "fixed_regions": ["left navigation", "right quick-link rail"], + "scroll_region": ".IndexPage-results including .IndexPage-toolbar and .DiscussionList, scrollbar hidden", + "discussion_list_owns_scroll": false, + "toolbar_scrolls_with_feed": true, + "discussion_loading": "IntersectionObserver automatically activates the native load-more action near the feed end", + "load_more_button": "retained only as a no-IntersectionObserver fallback", + "emoji_picker": "24 common Unicode emoji with Korean labels; native internal English names hidden", + "new_discussion_composer": "central modal dialog with a full-viewport backdrop; reply composer unchanged", + "feed": "flat transparent feed with row separators and no outer container card", + "individual_row_card": false, + "discussion_item_structure": [ + "author avatar", + "solved status and primary category, author, created time metadata", + "two-line title", + "first-post summary clamped to two lines", + "optional first-image thumbnail on the right", + "existing tag labels", + "reply count and existing discussion controls" + ], + "summary_source": "already-included Flarum firstPost.contentPlain model; no per-item API request", + "row_height_px": "variable, 137 to 218 in validated samples", + "left_navigation_width_px": 280, + "left_navigation_internal_scroll": "enabled when needed, scrollbar hidden", + "bottom_footer": "removed from visual layout", + "quick_links": ["ABLECLOUD Home", "Blog", "Online Docs"], + "quick_link_layout": "fixed vertical rail on the right, independent of feed scroll", + "original_content_width_px": 1100, + "desktop_shell_width_px": 1165, + "quick_link_area_expansion_px": 65, + "desktop_feed_width_px": 758, + "desktop_feed_to_quick_link_gap_px": 16, + "reclaimed_footer_height_px": 56, + "welcome_hero": "hidden on the index", + "top_shell_alignment": "header uses the same 1165px desktop shell as index content" + }, + "wsl": { + "environment": "WSL Ubuntu 24.04", + "url": "http://localhost:18080/", + "browser": { + "viewport": { "width": 1680, "height": 825 }, + "enhanced_discussion_count": 20, + "automatic_discussion_counts": [20, 40, 60], + "sample_discussion_ids": [144, 140, 139, 138, 137], + "sample_row_heights_px": [137, 137, 137, 156, 156], + "title_line_height_px": 25, + "summary_line_heights_px": [22, 22, 22, 43, 43], + "first_item_tags": ["ABLESTACK VM", "ABLESTACK ์˜ค๋ฅ˜/์žฅ์• ", "Mold"], + "thumbnail_count": 4, + "thumbnail_sample_discussion_id": 139, + "thumbnail_size_px": { "width": 124, "height": 92 }, + "thumbnail_loaded": true, + "thumbnail_source": "first image in firstPost.contentHtml", + "canonical_discussion_url": "http://localhost:18080/d/144-gasangmeosin-webkonsol-yeongyeol-oryu", + "discussion_open_scroll_y": 0, + "right_column_scrollbar": "hidden", + "left_navigation_scrollbar": "hidden", + "shell_width_px": 1165, + "preserved_content_width_px": 1100, + "feed_width_px": 758, + "feed_height_px": 615, + "quick_link_rail_size_px": { "width": 48, "height": 148 }, + "quick_link_count": 3, + "quick_link_rail_y_before_scroll_px": 339, + "quick_link_rail_y_after_feed_scroll_px": 339, + "feed_scroll_top_px": 360, + "feed_to_quick_link_gap_px": 16, + "mobile_viewport": { "width": 390, "height": 844 }, + "mobile_quick_link_rail_size_px": { "width": 44, "height": 136 }, + "header_shell": { "x": 250, "right": 1415, "width": 1165 }, + "welcome_hero_display": "none", + "index_content_shell": { "x": 250, "right": 1415, "width": 1165 }, + "top_and_content_shell_aligned": true + }, + "compiled_css_grid_contract": "grid-column-start:1;grid-column-end:-1", + "result": "PASS" + }, + "production": { + "host": "172.16.0.234", + "url": "https://community.ablecloud.io/", + "backup_root": "/var/backups/techflow-flarum/theme-infinite-emoji-dialog-final-20260820T1430KST", + "theme_less_sha256": "1aff8b1ec4afb8225f960e3cb71666f2707c527b8222239d064df36b9046672b", + "theme_js_sha256": "977a9bf06026ab4ff9ec8d5348fcf07edcaa58d38d7abaf94a11114d0a586779", + "services": { + "nginx": "active", + "php8.3-fpm": "active", + "mariadb": "active", + "techflow-community-monitor.timer": "active" + }, + "local_http_status": 200, + "external_https_status": 200, + "browser": { + "viewport": { "width": 1680, "height": 825 }, + "enhanced_discussion_count": 20, + "automatic_discussion_counts": [20, 40, 60], + "solved_sample_discussion_id": 173, + "solved_sample_row_height_px": 187, + "plain_sample_discussion_id": 144, + "plain_sample_row_height_px": 137, + "thumbnail_count": 5, + "thumbnail_sample_discussion_id": 169, + "thumbnail_sample_row_height_px": 218, + "thumbnail_size_px": { "width": 124, "height": 92 }, + "thumbnail_image_loaded": true, + "thumbnail_natural_size_px": { "width": 1602, "height": 1210 }, + "avatar_position": "absolute at content top-left", + "avatar_inside_content": true, + "solved_badge_metadata_gap_px": 8, + "solved_badge_overlaps_metadata": false, + "metadata_text": "ABLESTACK v4.x Diploยทdhsloveยท1์ผ ์ „", + "document_scroll_y": 0, + "menu_scrollbar": "hidden", + "right_column_scrollbar": "hidden", + "feed_container_border": "none", + "feed_container_background": "transparent", + "row_shadow": "none", + "shell_width_px": 1165, + "preserved_content_width_px": 1100, + "feed_width_px": 758, + "feed_height_px": 615, + "quick_link_rail_size_px": { "width": 48, "height": 148 }, + "quick_link_count": 3, + "quick_link_names": ["ABLECLOUD Home", "Blog", "Online Docs"], + "feed_to_quick_link_gap_px": 16, + "quick_link_rail_y_before_scroll_px": 339, + "quick_link_rail_y_after_feed_scroll_px": 339, + "feed_scroll_top_px": 350, + "bottom_footer_visible": false, + "header_shell": { "x": 250, "right": 1415, "width": 1165 }, + "welcome_hero_display": "none", + "index_content_shell": { "x": 250, "right": 1415, "width": 1165 }, + "top_and_content_shell_aligned": true + }, + "result": "PASS" + }, + "detail_reading_flow": { + "layout": { + "reddit_side_information_columns": false, + "content_width_px": 1165, + "inner_stream_width_px": 845, + "title_alignment": "left", + "navigation": "native 150px sticky Flarum vertical navigator with reply, follow, first/latest post and unread position", + "navigation_quick_link_overlap_px": 0, + "header_to_first_tag_gap_px": 18, + "header_hero_body_outer_edges_match": true, + "index_and_discussion_shell_edges_match": true, + "normal_posts": "flat with 1px separators", + "semantic_cards": ["AI technical support", "needs more information", "final solution"], + "duplicate_selected_answer_preview": "hidden", + "inline_reply_prompt": "๋‹ต๋ณ€์„ ์ž‘์„ฑํ•ด ์ฃผ์„ธ์š”", + "long_log_behavior": "collapsed above 480px with Korean expand and collapse controls" + }, + "wsl": { + "url": "http://localhost:18080/d/144-gasangmeosin-webkonsol-yeongyeol-oryu", + "title": "๊ฐ€์ƒ๋จธ์‹  ์›น์ฝ˜์†” ์—ฐ๊ฒฐ ์˜ค๋ฅ˜", + "author_meta": "sijo ยท 8์ผ ์ „", + "post_count": 4, + "first_post_text": "mold์—์„œ ๊ฐ€์ƒ๋จธ์‹  ์ฝ˜์†”๋ณด๊ธฐ๊ฐ€ ์—ฐ๊ฒฐ์ค‘์—์„œ ๋”์ด์ƒ ์ง„ํ–‰ ๋˜์ง€๊ฐ€ ์•Š์Šต๋‹ˆ๋‹ค.", + "header_shell": { "x": 250, "right": 1415, "width": 1165 }, + "shell": { "x": 250, "right": 1415, "width": 1165 }, + "navigation_width_px": 150, + "navigation_height_px": 467, + "stream_width_px": 845, + "header_to_first_tag_gap_px": 18, + "navigation_quick_link_overlap_px": 0, + "duplicate_solution_visible": false, + "result": "PASS" + }, + "production": { + "url": "https://community.ablecloud.io/d/173-v2keseo-vcenter-eobsi-esxi-hoseuteueseo-gasangmeosin-igwan-gineungeul-jiweonhanayo", + "title": "v2k์—์„œ vCenter ์—†์ด ESXi ํ˜ธ์ŠคํŠธ์—์„œ ๊ฐ€์ƒ๋จธ์‹  ์ด๊ด€ ๊ธฐ๋Šฅ์„ ์ง€์›ํ•˜๋‚˜์š”?", + "author_meta": "dhslove ยท 2์ผ ์ „", + "solved_badge": "ํ•ด๊ฒฐ๋จ", + "post_count": 5, + "post_numbers": [1, 2, 3, 4, 5], + "solution_post_number": 5, + "solution_headings": ["์ฆ์ƒ", "์›์ธ", "ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•", "์ถ”๊ฐ€ ๊ณ ๋ ค์‚ฌํ•ญ", "์ ์šฉ ๋ฒ„์ „"], + "solution_applied_version": "ABLESTACK Diplo", + "solution_jump_final_top_px": 76, + "duplicate_solution_visible": false, + "inline_reply_opened_native_composer": true, + "header_shell": { "x": 250, "right": 1415, "width": 1165 }, + "shell": { "x": 250, "right": 1415, "width": 1165 }, + "navigation_width_px": 150, + "navigation_height_px": 517, + "stream_width_px": 845, + "header_to_first_tag_gap_px": 18, + "navigation_quick_link_overlap_px": 0, + "quick_links": ["ABLECLOUD Home", "Blog", "Online Docs"], + "result": "PASS" + } + }, + "narrow_discussion_drawer": { + "width_px": 400, + "width_changed": false, + "layout": [ + "author avatar fixed at the left edge", + "category, author and date metadata on the first row", + "title on an independent two-line row", + "summary on an independent two-line row", + "optional 72x54 thumbnail in a dedicated right column", + "reply count fixed to the bottom-right without overlapping text" + ], + "content_right_padding_px": 12, + "title_and_summary_share_horizontal_flex_row": false, + "thumbnail_size_px": { "width": 72, "height": 54 }, + "wsl_rehearsal": "issue-73-drawer-icons-v3 PASS", + "result": "PASS" + }, + "composer_icon_fallback": { + "implementation": "embedded SVG masks without Font Awesome webfont dependency", + "mapped_icons": [ + "preview", "bold", "italic", "inline code", "quote", "link", "image", + "unordered list", "ordered list", "additional controls", "strikethrough", + "subscript", "superscript", "spoiler", "code block", "block spoiler" + ], + "wsl_browser": { "visible_icon_width_px": "15 to 16", "broken_glyph_count": 0 }, + "production_browser": { "visible_icon_width_px": "15 to 16", "broken_glyph_count": 0 }, + "result": "PASS" + }, + "infinite_loading_emoji_and_new_discussion_dialog": { + "infinite_loading": { + "trigger": "IntersectionObserver rooted at .IndexPage-results with a 120px bottom margin", + "wsl_browser_counts": [20, 40, 60], + "production_browser_counts": [20, 40, 60], + "manual_button_click_required": false, + "native_button_fallback_retained": true, + "repeated_dom_mutation_removed": true + }, + "emoji_picker": { + "language": "Korean", + "option_count": 24, + "native_internal_name_visible": false, + "native_dropdown_visible": false, + "inserted_value": "๐Ÿ˜Š", + "production_editor_text": "์ด๋ชจ์ง€ ์‚ฝ์ž… ํ™•์ธ ๐Ÿ˜Š ", + "insertion_result": "PASS" + }, + "new_discussion_dialog": { + "scope": "new discussion only; reply composer unchanged", + "first_click_state_sync": true, + "viewport_px": { "width": 1680, "height": 881 }, + "composer_rect_px": { "x": 410.875, "y": 206.5, "width": 843.25, "height": 520, "bottom": 726.5 }, + "backdrop_rect_px": { "x": 0, "y": 0, "width": 1665, "height": 881 }, + "backdrop_color": "rgba(20, 42, 75, 0.46)", + "editor_height_px": 376.5, + "toolbar_height_px": 65.5, + "editor_toolbar_overlap": false, + "minimize_control": "hidden", + "fullscreen_control": "hidden", + "close_control": "visible", + "result": "PASS" + }, + "wsl_rehearsal": "issue-73-browser-final-20260820 PASS", + "production_deployment": "PASS", + "result": "PASS" + }, + "composer_and_discussion_pane": { + "root_cause": [ + "the start-from-top handler forced window.location.assign and discarded Flarum's in-memory discussion-list state", + "the narrow pane inherited the desktop control rule top 50 percent plus translateY minus 50 percent, which placed the ellipsis above the card", + "the active composer moved its footer upward by 20px, overlapping the editor, while hovered discussion rows could rise above the composer at z-index 1050" + ], + "navigation_fix": "retain Flarum SPA navigation, normalize the link target, and reset scroll without preventDefault", + "reload_fallback": "store the already-rendered DiscussionList in session storage and restore a 400px pane after a direct detail reload", + "composer_layout": { + "desktop_active_min_height_px": 336, + "editor_height_px": { "wsl": 180, "production": 180 }, + "footer_height_px": { "wsl": 65.5, "production": 65.5 }, + "editor_footer_gap_px": { "wsl": 0, "production": 0 }, + "editor_footer_overlap": { "wsl": false, "production": false }, + "submit_bottom_inset_px": { "wsl": 29.5, "production": 29.5 }, + "footer_bottom_inset_px": { "wsl": 15.5, "production": 15.5 }, + "footer_inside_composer": { "wsl": true, "production": true }, + "background_discussion_z_index_while_composing": { "wsl": 0, "production": 0 }, + "horizontal_overflow": false + }, + "discussion_pane": { + "width_px": 400, + "item_control_size_px": { "width": 32, "height": 32 }, + "item_control_top_offset_px": { "wsl": 11, "production": 11 }, + "item_control_inside_card": { "wsl": true, "production": true }, + "item_control_transform": "none", + "item_count": { "wsl": 20, "production": 20 }, + "active_item_after_reload": { "wsl": 1, "production": 1 }, + "list_click_scroll_y": { "wsl": 0, "production": 0 }, + "hidden_while_composer_visible": true, + "restored_after_composer_close": true, + "composer_overlap": { "wsl": false, "production": false }, + "explicit_composer_state_class": "ablecloud-composer-open", + "pane_display_while_composing": { "wsl": "none", "production": "none" } + }, + "wsl_rehearsal": "issue-73-composer-layout-flex-20260820 PASS", + "production_browser_https": true, + "result": "PASS" + }, + "validation": { + "theme_contract_tests": { "passed": 14, "failed": 0 }, + "wsl_rehearsal": "issue-73-browser-final-20260820 PASS", + "header_hero_body_alignment": "WSL and production x=250, right=1415, width=1165", + "discussion_item_enhancement_is_idempotent": true, + "summary_uses_existing_store_data": true, + "thumbnail_uses_existing_first_post_html": true, + "thumbnail_loading": "lazy", + "deployment_script_normalizes_permissions": true, + "deployment_script_production_result": "PASS", + "git_diff_check": "PASS", + "temporary_remote_assets_removed": true + }, + "protected_scope": { + "database_schema_changed": false, + "community_content_changed": false, + "techflow_ai_services_changed": false, + "github_chat_webhook_changed": false + }, + "decision": "PASS" +} diff --git a/docs/evidence/issue-73/community-mobile-navigation-dialog-validation.json b/docs/evidence/issue-73/community-mobile-navigation-dialog-validation.json new file mode 100644 index 0000000..91ec630 --- /dev/null +++ b/docs/evidence/issue-73/community-mobile-navigation-dialog-validation.json @@ -0,0 +1,87 @@ +{ + "schema_version": "1.0", + "executed_at": "2026-08-20T15:17:00+09:00", + "scope": "Mobile drawer submenu backdrop and new-discussion/reply close-interface correction", + "viewport": {"width": 390, "height": 844}, + "wsl": { + "url": "http://localhost:18080", + "environment": "Ubuntu 24.04 WSL", + "rehearsal_result": "/srv/techflow-flarum-staging/rehearsals/issue-73/issue73-mobile-submenu-reply-final-20260820/result.json", + "rehearsal_cycle": ["enable", "validate", "disable", "rollback-validate", "re-enable"], + "content_integrity": { + "users": 39, + "discussions": 117, + "posts": 305, + "content_sha256": "efc9dfbd1f9d6a235378d240494dd94a192d52ea6e956f9167d4a2d44eb11cb7", + "uploads_sha256": "19cdf52600535380969a176d81bf66678b200a4247694641d00b470b8637a97c" + }, + "browser": { + "empty_navigation_pill": false, + "index_navigation_rect": {"width": 0, "height": 0, "background": "transparent", "border": "none"}, + "drawer_rect": {"width": 270, "height": 844}, + "drawer_hidden_dropdowns_take_space": false, + "locale_submenu_rect": {"x": 24, "y": 202, "width": 222, "height": 131.1875}, + "locale_transition": ["ko", "en", "ko"], + "touch_dropdown_backdrop_hidden": true, + "drawer_close_button": {"width": 44, "height": 44, "label": "ํƒ์ƒ‰ ์„œ๋ž ๋‹ซ๊ธฐ"}, + "drawer_backdrop_after_close": 0, + "composer_close_button": {"width": 44, "height": 44, "label": "๋‹ซ๊ธฐ", "pointer_target": true}, + "composer_close_result": "PASS", + "reply_close_button": {"x": 9, "y": 9, "width": 44, "height": 44, "pointer_target": true}, + "reply_minimize_display": "none", + "reply_fullscreen_display": "none", + "reply_close_result": "PASS", + "tag_modal_close_button": {"width": 44, "height": 44, "marker": "ร—"}, + "tag_modal_close_result": "PASS" + }, + "result": "PASS" + }, + "production": { + "url": "https://community.ablecloud.io", + "host": "172.16.0.234", + "backup_root": "/var/backups/techflow-flarum/theme-mobile-submenu-reply-20260820T151526KST", + "source_less_sha256": "1424f73c1a8b4416e569d7bb32d6fa29a9694fcb9013c2f672030e8138915ca6", + "vendor_less_sha256": "1424f73c1a8b4416e569d7bb32d6fa29a9694fcb9013c2f672030e8138915ca6", + "source_js_sha256": "5ad7bb5e00dda6c36c5e97c194d09aefc137c9834cdf9059916f5f0bf956e8cb", + "vendor_js_sha256": "5ad7bb5e00dda6c36c5e97c194d09aefc137c9834cdf9059916f5f0bf956e8cb", + "local_http_status": 200, + "external_https_status": 200, + "discussion_https_status": 200, + "services": {"nginx": "active", "php8.3-fpm": "active", "mariadb": "active"}, + "browser": { + "empty_navigation_pill": false, + "index_navigation_rect": {"width": 0, "height": 0, "background": "transparent", "border": "none"}, + "drawer_rect": {"width": 270, "height": 844}, + "drawer_hidden_dropdowns_take_space": false, + "locale_submenu_rect": {"x": 24, "y": 202, "width": 222, "height": 131.1875}, + "locale_transition": ["ko", "en", "ko"], + "touch_dropdown_backdrop_hidden": true, + "drawer_backdrop_after_close": 0, + "composer_rect": {"x": 8, "y": 62, "width": 374, "height": 335.1875}, + "composer_close_button": {"x": 17, "y": 71, "width": 44, "height": 44, "pointer_target": true}, + "composer_close_result": "PASS", + "reply_close_button": {"x": 9, "y": 9, "width": 44, "height": 44, "pointer_target": true}, + "reply_minimize_display": "none", + "reply_fullscreen_display": "none", + "reply_close_result": "PASS", + "tag_modal_close_button": {"x": 10, "y": 8, "width": 44, "height": 44, "marker": "ร—"}, + "tag_modal_body_scroll": "auto", + "tag_modal_close_result": "PASS", + "visual_check": "PASS" + }, + "result": "PASS" + }, + "tests": { + "command": "python3 -m unittest -v deploy.flarum.tests.test_community_theme", + "passed": 15, + "failed": 0, + "git_diff_check": "PASS" + }, + "protected_scope": { + "database_schema_changed": false, + "discussion_or_attachment_changed": false, + "techflow_ai_changed": false, + "github_chat_webhook_changed": false + }, + "result": "PASS" +} diff --git a/docs/evidence/issue-73/community-search-ui-validation.json b/docs/evidence/issue-73/community-search-ui-validation.json new file mode 100644 index 0000000..88b928a --- /dev/null +++ b/docs/evidence/issue-73/community-search-ui-validation.json @@ -0,0 +1,63 @@ +{ + "schema_version": "1.0", + "executed_at": "2026-08-20T13:34:58+09:00", + "scope": "Community header search input icons and discussion search result readability", + "query": "v2k", + "root_cause": [ + "the solved-search extension rendered tags, title, question excerpt and selected-answer excerpt as a horizontal flex row inside the 400px native search menu", + "the theme icon fallback did not map fa-times-circle and the search prefix used an operating-system symbol glyph" + ], + "implementation": { + "result_panel_width_px": 560, + "result_layout": "single vertical reading flow: tags, title, question excerpt, selected-answer excerpt", + "question_excerpt_lines": 2, + "selected_answer_excerpt_lines": 2, + "mobile_panel": "12px viewport side insets and viewport-height scrolling", + "search_icon": "embedded SVG mask", + "clear_icon": "embedded SVG mask for fa-times-circle", + "external_icon_font_dependency": false + }, + "wsl": { + "url": "http://localhost:18080/", + "rehearsal": "search-ui-20260820-r3 PASS", + "panel_rect_px": { "x": 403, "y": 67, "width": 560, "height": 456 }, + "solution_result_count": 1, + "result_section_overlap": false, + "search_icon_mask_present": true, + "clear_icon_mask_present": true, + "result": "PASS" + }, + "production": { + "url": "https://community.ablecloud.io/", + "backup_root": "/var/backups/techflow-flarum/theme-search-ui-20260820", + "theme_less_sha256": "61598a1d4be2c48b21cf839546546f5c036c545c580b8d54f763f17127b662b5", + "panel_rect_px": { "x": 403, "y": 67, "width": 560, "height": 518 }, + "solution_result_count": 2, + "discussion_result_count_including_solutions": 4, + "result_section_overlap": false, + "search_icon_mask_present": true, + "clear_icon_mask_present": true, + "services": { + "nginx": "active", + "php8.3-fpm": "active", + "mariadb": "active" + }, + "local_http_status": 200, + "external_browser_https": true, + "temporary_remote_assets_removed": true, + "result": "PASS" + }, + "validation": { + "theme_contract_tests": { "passed": 14, "failed": 0 }, + "git_diff_check": "PASS", + "source_and_vendor_hash_match": true + }, + "protected_scope": { + "database_schema_changed": false, + "community_content_changed": false, + "attachments_changed": false, + "techflow_ai_services_changed": false, + "github_chat_webhook_changed": false + }, + "decision": "PASS" +} diff --git a/docs/evidence/issue-73/community-shared-shell-width-validation.json b/docs/evidence/issue-73/community-shared-shell-width-validation.json new file mode 100644 index 0000000..b3470e0 --- /dev/null +++ b/docs/evidence/issue-73/community-shared-shell-width-validation.json @@ -0,0 +1,71 @@ +{ + "schema_version": "1.0", + "executed_at": "2026-08-20T14:21:54+09:00", + "scope": "Align tags, tag hero, user profile, settings and security pages to the discussion-list desktop shell", + "desktop_contract": { + "shell_width_px": 1165, + "shell_left_px_at_1280": 50, + "shell_right_px_at_1280": 1215, + "inner_content_right_px": 1135, + "quick_link_rail_left_px": 1143.5, + "quick_link_clearance_px": 8.5, + "reserved_right_space_px": 80, + "mobile_override": false + }, + "before": { + "tags_shell_width_px": 1100, + "user_shell_width_px": 1100, + "settings_shell_width_px": 1100, + "security_shell_width_px": 1100, + "left_px_at_1280": 82.5, + "right_px_at_1280": 1182.5 + }, + "wsl": { + "url": "http://localhost:18080", + "environment": "Ubuntu 24.04 WSL", + "rehearsal_result": "/srv/techflow-flarum-staging/rehearsals/issue-73/issue73-shell-width-20260820/result.json", + "rehearsal_cycle": ["enable", "validate", "disable", "rollback-validate", "re-enable"], + "result": "PASS", + "browser": { + "viewport": {"width": 1280, "height": 720}, + "tags": {"header_width": 1165, "hero_width": 1165, "page_width": 1165, "content_width": 1070}, + "profile": {"header_width": 1165, "hero_width": 1165, "page_width": 1165, "content_right": 1135}, + "settings": {"header_width": 1165, "hero_width": 1165, "page_width": 1165, "content_right": 1135}, + "security": {"header_width": 1165, "hero_width": 1165, "page_width": 1165, "content_right": 1135}, + "quick_link_overlap": false + } + }, + "production": { + "url": "https://community.ablecloud.io", + "host": "172.16.0.234", + "backup_root": "/var/backups/techflow-flarum/theme-shell-width-20260820T142154KST", + "source_less_sha256": "6bff0d72cdee7420aa38cd195229787a39cc64a351b8b18eee61955235a48746", + "vendor_less_sha256": "6bff0d72cdee7420aa38cd195229787a39cc64a351b8b18eee61955235a48746", + "local_http_status": 200, + "external_https_status": 200, + "services": {"nginx": "active", "php8.3-fpm": "active", "mariadb": "active"}, + "browser": { + "viewport": {"width": 1280, "height": 720}, + "discussion_list_reference": {"header_width": 1165, "page_width": 1165, "content_right": 1135}, + "tags": {"header_width": 1165, "hero_width": 1165, "page_width": 1165, "content_width": 1070}, + "profile": {"header_width": 1165, "hero_width": 1165, "page_width": 1165, "content_right": 1135}, + "settings": {"header_width": 1165, "hero_width": 1165, "page_width": 1165, "content_right": 1135}, + "security": {"header_width": 1165, "hero_width": 1165, "page_width": 1165, "content_right": 1135}, + "quick_link_overlap": false, + "visual_check": "PASS" + } + }, + "tests": { + "command": "python -m unittest discover -s deploy/flarum/tests -p test_*.py -v", + "passed": 14, + "failed": 0, + "git_diff_check": "PASS" + }, + "protected_scope": { + "database_schema_changed": false, + "discussion_or_attachment_changed": false, + "techflow_ai_changed": false, + "github_chat_webhook_changed": false + }, + "result": "PASS" +} diff --git a/docs/evidence/issue-73/community-user-nav-affix-validation.json b/docs/evidence/issue-73/community-user-nav-affix-validation.json new file mode 100644 index 0000000..560b165 --- /dev/null +++ b/docs/evidence/issue-73/community-user-nav-affix-validation.json @@ -0,0 +1,61 @@ +{ + "schema_version": "1.0", + "executed_at": "2026-08-19T10:49:00Z", + "scope": "Flarum user profile and settings navigation scroll overlap", + "root_cause": "When Flarum adds the affix class, the fixed list resolved width: 100% against the viewport instead of the 280px navigation column.", + "change": { + "selector": ".UserPage .UserPage-nav > ul.affix", + "width_px": 280, + "max_width_px": 280 + }, + "wsl": { + "environment": "WSL Ubuntu 24.04", + "url": "http://localhost:18080/u/pdh1974", + "rehearsal_result": "/srv/techflow-flarum-staging/rehearsals/issue-73/issue73-user-nav-affix/result.json", + "cycle": "PASS", + "browser": { + "viewport": { "width": 1280, "height": 720 }, + "scroll_y": 520, + "menu_width_px": 280, + "menu_right_px": 377.5, + "content_left_px": 427.5, + "overlap": false + } + }, + "production": { + "host": "172.16.0.234", + "url": "https://community.ablecloud.io/u/pdh1974", + "backup_root": "/var/backups/techflow-flarum/theme-nav-affix-20260819T104516Z", + "services": { + "nginx": "active", + "php8.3-fpm": "active", + "mariadb": "active" + }, + "local_http_status": 200, + "external_https_status": 200, + "theme_sha256": "376eefd1d1e00898ed8438566824df11ceb137f970b2ee91297f3b131cb8f08d", + "vendor_sha256_matches": true, + "compiled_css_contains_affix_rule": true, + "browser": { + "viewport": { "width": 1150, "height": 881 }, + "scroll_y": 520, + "menu_width_px": 280, + "menu_right_px": 312.5, + "content_left_px": 362.5, + "overlap": false + } + }, + "validation": { + "theme_contract_tests": { "passed": 11, "failed": 0 }, + "deployment_script_shell_syntax": "PASS", + "git_diff_check": "PASS", + "temporary_remote_assets_removed": true + }, + "protected_scope": { + "database_schema_changed": false, + "community_content_changed": false, + "techflow_ai_services_changed": false, + "github_chat_webhook_changed": false + }, + "decision": "PASS" +} diff --git a/docs/reports/issue-73-community-ui-validation.md b/docs/reports/issue-73-community-ui-validation.md index 99c0449..ba7f978 100644 --- a/docs/reports/issue-73-community-ui-validation.md +++ b/docs/reports/issue-73-community-ui-validation.md @@ -4,7 +4,7 @@ ABLESTACK ๋ธŒ๋žœ๋“œ ๊ธฐ๋ฐ˜ Flarum ์ „์šฉ ํ…Œ๋งˆ ํ™•์žฅ์„ ๊ตฌํ˜„ํ•˜๊ณ  WSL Ubuntu 24.04์˜ Flarum 1.8.18 ๋ณต์ œ ํ™˜๊ฒฝ์—์„œ ํ™œ์„ฑํ™”, ๊ธฐ๋Šฅ ๊ฒ€์ฆ, ๋น„ํ™œ์„ฑํ™” ๋กค๋ฐฑ, ์žฌํ™œ์„ฑํ™”๋ฅผ ์™„๋ฃŒํ–ˆ๋‹ค. ๋ฐ์Šคํฌํ†ฑ 1440x900๊ณผ ๋ชจ๋ฐ”์ผ 390x844์—์„œ ํ™ˆยท๋ชฉ๋กยทํƒœ๊ทธยท๊ฒ€์ƒ‰ยท๋กœ๊ทธ์ธยท์ž‘์„ฑยทํ”„๋กœํ•„ยท์„ค์ •ยท๋ณด์•ˆยทํ•ด๊ฒฐ ๋‹ต๋ณ€ยท๊ฒŒ์‹œ๋ฌผ ์ž‘์—… ๋ฒ„ํŠผ์„ ์‹ค์ œ ๋ Œ๋”๋งํ–ˆ์œผ๋ฉฐ ํ•œ๊ธ€ ์›๋ฌธ ๋ฒˆ์—ญ ํ‚ค์™€ ํƒœ๊ทธยทํ”„๋กœํ•„ยท์„ค์ •ยท๋ณด์•ˆ ์ง์ ‘ ๋…ธ์ถœ ์˜๋ฌธ์€ ๋ชจ๋‘ 0๊ฑด์ด์—ˆ๋‹ค. -Flarum Core, Vendor ์›๋ณธ, DB Schema์™€ Community ์ฝ˜ํ…์ธ ๋Š” ์ˆ˜์ •ํ•˜์ง€ ์•Š์•˜๋‹ค. ์‚ฌ์šฉ์ž 39๋ช…, ํ† ์˜ 117๊ฑด, ๊ฒŒ์‹œ๋ฌผ 305๊ฑด, ์ฝ˜ํ…์ธ ์™€ ์ฒจ๋ถ€ ํ•ด์‹œ๋Š” ์ „์ฒด ์ฃผ๊ธฐ ์ „ํ›„ ๋™์ผํ–ˆ๋‹ค. ์šด์˜ Community์—๋Š” ์ ์šฉํ•˜์ง€ ์•Š์•˜์œผ๋ฉฐ ํŒ์ •์€ **์šด์˜ ์ ์šฉ ์ „ Conditional GO**๋‹ค. +Flarum Core, Vendor ์›๋ณธ, DB Schema์™€ Community ์ฝ˜ํ…์ธ ๋Š” ์ˆ˜์ •ํ•˜์ง€ ์•Š์•˜๋‹ค. ์‚ฌ์šฉ์ž 39๋ช…, ํ† ์˜ 117๊ฑด, ๊ฒŒ์‹œ๋ฌผ 305๊ฑด, ์ฝ˜ํ…์ธ ์™€ ์ฒจ๋ถ€ ํ•ด์‹œ๋Š” ์ „์ฒด ์ฃผ๊ธฐ ์ „ํ›„ ๋™์ผํ–ˆ๋‹ค. ์ดํ›„ ์Šน์ธ๋œ ํ›„์† ๊ฐœ์„ ์„ WSL๊ณผ ์šด์˜ Community์— ๊ฐ™์€ ๋…๋ฆฝ ํ…Œ๋งˆ ํ™•์žฅ์œผ๋กœ ์ ์šฉํ–ˆ์œผ๋ฉฐ ์ตœ์ข… ํŒ์ •์€ **PASS**๋‹ค. ## ์™„๋ฃŒ ๋ฒ”์œ„ @@ -36,7 +36,9 @@ Flarum Core, Vendor ์›๋ณธ, DB Schema์™€ Community ์ฝ˜ํ…์ธ ๋Š” ์ˆ˜์ •ํ•˜์ง€ ์•Š ABLESTACK Primary `#155EEF`, Ink `#15253E`, Canvas `#F3F7FC`๋ฅผ ๊ธฐ์ค€์œผ๋กœ ํ—ค๋”, Hero, ํƒ์ƒ‰, ํ† ์˜ ์นด๋“œ, ๊ฒŒ์‹œ๋ฌผ, ๊ฒ€์ƒ‰, ๋กœ๊ทธ์ธ, ์ž‘์„ฑ๊ธฐ์™€ ์ฒจ๋ถ€ ๋ฒ„ํŠผ์„ ๋ธ”๋ฃจ ๊ณ„์—ด๋กœ ํ†ต์ผํ–ˆ๋‹ค. ๋ฐฐ๊ฒฝ์—๋Š” ์˜…์€ ๋ธ”๋ฃจ Gradient๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์นด๋“œ์—๋Š” 14~16px Radius, ๋ธ”๋ฃจ ๊ณ„์—ด ๊ฒฝ๊ณ„์™€ ๋‚ฎ์€ ๊ทธ๋ฆผ์ž๋ฅผ ์ ์šฉํ–ˆ๋‹ค. -๋ธŒ๋ผ์šฐ์ € ๊ณ„์‚ฐ๊ฐ’์œผ๋กœ ์ƒ์„ธ ๊ฒŒ์‹œ๋ฌผ ์นด๋“œ๋Š” ๋ฐ์Šคํฌํ†ฑ `21px 25px 23px`, ๋ชจ๋ฐ”์ผ `18px 16px 20px`์˜ ๋‚ด๋ถ€ ์—ฌ๋ฐฑ์„ ํ™•๋ณดํ–ˆ๋‹ค. ๋ฐ์Šคํฌํ†ฑ ํ† ๋ก  ๋ชฉ๋ก์€ ์ œ๋ชฉยท์นดํ…Œ๊ณ ๋ฆฌ ํƒœ๊ทธยท๋Œ“๊ธ€ ์ˆ˜๋ฅผ ๊ฐ™์€ ์ค„์— ๋ฐฐ์น˜ํ•˜๊ณ  ์นด๋“œ ๋†’์ด๋ฅผ ์•ฝ 110px์—์„œ `62px`๋กœ ์ค„์˜€๋‹ค. ๋ชจ๋ฐ”์ผ์€ ์ œ๋ชฉ 2์ค„๊ณผ ๋งˆ์ง€๋ง‰ ์‘๋‹ต ์ •๋ณด๋ฅผ ์œ ์ง€ํ•œ๋‹ค. +๋ธŒ๋ผ์šฐ์ € ๊ณ„์‚ฐ๊ฐ’์œผ๋กœ ์ƒ์„ธ ๊ฒŒ์‹œ๋ฌผ ์นด๋“œ๋Š” ๋ฐ์Šคํฌํ†ฑ `21px 25px 23px`, ๋ชจ๋ฐ”์ผ `18px 16px 20px`์˜ ๋‚ด๋ถ€ ์—ฌ๋ฐฑ์„ ํ™•๋ณดํ–ˆ๋‹ค. ์ตœ์ข… ํ† ๋ก  ๋ชฉ๋ก์€ Reddit New์˜ ๊ฒŒ์‹œ๋ฌผ ํ•œ ๊ฑด ๊ตฌ์กฐ๋ฅผ ์ฐธ๊ณ ํ•ด ์ž‘์„ฑ์žยท๋ถ„๋ฅ˜ยท์ž‘์„ฑ ์‹œ๊ฐ, ์ œ๋ชฉ, ์ฒซ ๊ธ€ ์š”์•ฝ, ํƒœ๊ทธ, ๋Œ“๊ธ€ ์ˆ˜๋ฅผ ์œ„์—์„œ ์•„๋ž˜๋กœ ์ฝ๋„๋ก ์žฌ๊ตฌ์„ฑํ–ˆ๋‹ค. ์ œ๋ชฉ๊ณผ ์š”์•ฝ์€ ๊ฐ๊ฐ ์ตœ๋Œ€ ๋‘ ์ค„์ด๋ฉฐ ํ–‰ ๋†’์ด๋Š” ์ฝ˜ํ…์ธ ์™€ ํ•ด๊ฒฐ ์ƒํƒœ์— ๋”ฐ๋ผ `137~187px`๋กœ ๋‹ฌ๋ผ์ง„๋‹ค. + +์ฒซ ๊ธ€ ์š”์•ฝ์€ Flarum ๋ชฉ๋ก ์‘๋‹ต์— ์ด๋ฏธ ํฌํ•จ๋œ `firstPost.contentPlain()`์„ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ํ† ๋ก ๋งˆ๋‹ค ๋ณ„๋„ API ์š”์ฒญ์„ ๋งŒ๋“ค์ง€ ์•Š๋Š”๋‹ค. ํ•ด๊ฒฐ ๋ฐฐ์ง€์™€ ์ž‘์„ฑ ์ •๋ณด๋Š” ๊ฐ™์€ ํ—ค๋” Grid์˜ ๋…๋ฆฝ ์—ด์— ๋ฐฐ์น˜ํ•˜๊ณ  8px ๊ฐ„๊ฒฉ์„ ๋ณด์žฅํ•œ๋‹ค. ์‚ฌ์šฉ์ž ์•„๋ฐ”ํƒ€๋Š” ํ–‰ ์ขŒ์ธก ์ƒ๋‹จ์— ๊ณ ์ •ํ•˜๋ฉฐ ํ•ด๊ฒฐ ์—ฌ๋ถ€์™€ ๋ฌด๊ด€ํ•˜๊ฒŒ ๋ณธ๋ฌธ ์•ˆ์— ์™„์ „ํžˆ ํ‘œ์‹œ๋œ๋‹ค. ์šด์˜ ํ† ๋ก  #173์—์„œ ๋ฐฐ์ง€-์ž‘์„ฑ ์ •๋ณด ๊ฒน์นจ `false`, ์ผ๋ฐ˜ ํ† ๋ก  #144์™€ ํ•ด๊ฒฐ ํ† ๋ก  #173 ๋ชจ๋‘ ์•„๋ฐ”ํƒ€๊ฐ€ ๋ณธ๋ฌธ ๊ฒฝ๊ณ„ ์•ˆ์— ์žˆ์Œ์„ ๋ธŒ๋ผ์šฐ์ € ๊ณ„์‚ฐ๊ฐ’์œผ๋กœ ํ™•์ธํ–ˆ๋‹ค. ๊ธฐ์กด ๋ชฉ๋ก ๋งํฌ๋Š” `/d/{slug}/4`์ฒ˜๋Ÿผ ๋งˆ์ง€๋ง‰ ๊ฒŒ์‹œ๋ฌผ ๋ฒˆํ˜ธ๋ฅผ ํฌํ•จํ•ด ํ† ๋ก ์„ ์—ด์ž๋งˆ์ž ๋งˆ์ง€๋ง‰ ์‘๋‹ต์œผ๋กœ ์ด๋™ํ–ˆ๋‹ค. ๋ชฉ๋ก์˜ `.DiscussionListItem-main` ๋งํฌ์— ํ•œํ•ด์„œ ๊ฒŒ์‹œ๋ฌผ ๋ฒˆํ˜ธ๋ฅผ ์ œ๊ฑฐํ•œ ํ† ๋ก  ๊ธฐ๋ณธ ์ฃผ์†Œ๋กœ ์ •๊ทœํ™”ํ•˜๊ณ , ํด๋ฆญ ์‹œ ํ•ด๋‹น ์ฃผ์†Œ๋ฅผ ์ƒˆ ๋ฌธ์„œ๋กœ ์—ด๋„๋ก ํ–ˆ๋‹ค. ๋ธŒ๋ผ์šฐ์ € ๊ฒ€์ฆ์—์„œ ๊ฒฝ๋กœ๋Š” `/d/144-gasangmeosin-webkonsol-yeongyeol-oryu`, `scrollY=0`, ์ฒซ ๊ฒŒ์‹œ๋ฌผ์€ ํ™”๋ฉด ์•ˆ์— ํ‘œ์‹œ๋๋‹ค. ์•Œ๋ฆผ์ด๋‚˜ ์ƒ์„ธ ๋‚ด๋ถ€์˜ ํŠน์ • ๋‹ต๋ณ€ ๋งํฌ๋Š” ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๋Š”๋‹ค. @@ -123,7 +125,7 @@ Hero์˜ ์œ„ยท์•„๋ž˜ ํŒจ๋”ฉ์€ ๋ฐ์Šคํฌํ†ฑ `19px`, ๋ชจ๋ฐ”์ผ `17px`๋กœ ๋Œ€์นญํ™” | ํ™”๋ฉด | ๊ฒ€์ฆ ๋‚ด์šฉ | ๊ฒฐ๊ณผ | |---|---|---| -| ํ™ˆ Desktop | ๋ธŒ๋žœ๋“œ Hero, ์ขŒ์ธก ํƒ์ƒ‰, 62px ๋‹จ์ผ ํ–‰ ๋ชฉ๋ก | PASS | +| ํ™ˆ Desktop | ๋ธŒ๋žœ๋“œ Hero, ์ขŒ์ธก ํƒ์ƒ‰, ์ž‘์„ฑ ์ •๋ณดยท์ œ๋ชฉยท์š”์•ฝยทํƒœ๊ทธยท๋Œ“๊ธ€๋กœ ๊ตฌ์„ฑํ•œ 137~187px ๊ฐ€๋ณ€ ๋†’์ด ๋ชฉ๋ก | PASS | | ๋ชฉ๋กโ†’์ƒ์„ธ ์ง„์ž… | ๊ฒŒ์‹œ๋ฌผ ๋ฒˆํ˜ธ ์ œ๊ฑฐ, `scrollY=0`, ์ฒซ ๊ฒŒ์‹œ๋ฌผ ํ‘œ์‹œ | PASS | | ํ† ๋ก  ๋ชฉ๋ก ๋ฉ”๋‰ด | ํ…Œ๋‘๋ฆฌยท๋ฐฐ๊ฒฝยท๊ทธ๋ฆผ์ž ์—†๋Š” ์ดํด๋ฆฝ์Šค ์•„์ด์ฝ˜ ์ƒ์‹œ ํ‘œ์‹œ, ์„ธ๋กœ ์ค‘์‹ฌ ์˜ค์ฐจ 0px, ๋ฉ”๋‰ด `z-index: 1080`, ๋’ค์ชฝ ํ–‰ ๊ฐ€๋ฆผ ์—†์Œ | PASS | | ์ƒ์„ธ ๋ชฉ๋ก ํŒจ๋„ | 400px ์นด๋“œ ๋ชฉ๋ก, Header ๊ฒน์นจ 0px, 6px ์Šคํฌ๋กค๋ฐ”, ์นด๋“œ ๊ฐ„๊ฒฉ 8px, ํ˜„์žฌ ํ† ๋ก  ๊ฐ•์กฐ | PASS | @@ -144,7 +146,7 @@ Hero์˜ ์œ„ยท์•„๋ž˜ ํŒจ๋”ฉ์€ ๋ฐ์Šคํฌํ†ฑ `19px`, ๋ชจ๋ฐ”์ผ `17px`๋กœ ๋Œ€์นญํ™” | ํ”„๋กœํ•„ | 165px ๋ธ”๋ฃจ Hero, 96px ์•„๋ฐ”ํƒ€, 280px ๋ฉ”๋‰ด, ๋ชจ๋ฐ”์ผ ํ•œ ์—ด, ์ง์ ‘ ์˜๋ฌธ 0๊ฑด | PASS | | ์„ค์ • | 780px ์ฝ˜ํ…์ธ  ์นด๋“œ, ๊ณตํ†ต ๋ฒ„ํŠผยท์•Œ๋ฆผ ํ‘œ, ์ง์ ‘ ์˜๋ฌธ 0๊ฑด, ๋ชจ๋ฐ”์ผ ํ‘œ 302pxยท๊ฐ€๋กœ ๋„˜์นจ 0px | PASS | | ๋ณด์•ˆ | ํ† ํฐยท์„ธ์…˜ยท๋กœ๊ทธ์•„์›ƒ ๊ณตํ†ต ์นด๋“œ, `์ƒˆ ํ† ํฐ` ํ•œ๊ธ€ํ™”, ๋ชจ๋ฐ”์ผ ์นด๋“œ 336pxยท๊ฐ€๋กœ ๋„˜์นจ 0px | PASS | -| HeaderยทFooter | ๊ณตํ†ต 1100px ์ปจํ…Œ์ด๋„ˆยท15px ๋‚ด๋ถ€ ์—ฌ๋ฐฑ, ์›ํ˜• SVG ๋งํฌ, ์ฝ˜ํ…์ธ  ๊ฒน์นจ ๋ฐฉ์ง€ | PASS | +| Headerยทํ€ต ๋งํฌ | ํ•˜๋‹จ Footer ์ œ๊ฑฐ, ๊ธฐ์กด 1100px ์ฝ˜ํ…์ธ  ํญ ์œ ์ง€, ๋ฐ”๋กœ ์˜ค๋ฅธ์ชฝ ์„ธ๋กœํ˜• ์›ํ˜• SVG ๋งํฌ 3๊ฐœ | PASS | | ๊ธ€์“ฐ๊ธฐยท์ฒจ๋ถ€ | ComposerยทFoF Upload ์Šคํƒ€์ผ ๊ณ„์•ฝ, Issue #72 ์ •์ฑ… ๋ถˆ๋ณ€ | PASS | ## ๋กค๋ฐฑ ๊ฒฐ๊ณผ @@ -155,7 +157,7 @@ Hero์˜ ์œ„ยท์•„๋ž˜ ํŒจ๋”ฉ์€ ๋ฐ์Šคํฌํ†ฑ `19px`, ๋ชจ๋ฐ”์ผ `17px`๋กœ ๋Œ€์นญํ™” ## ์šด์˜ ์˜ํ–ฅ๊ณผ ๋‚จ์€ ๊ฒฐ์ • -- ์šด์˜ Community ๋ณ€๊ฒฝ: ์—†์Œ +- ์šด์˜ Community ๋ณ€๊ฒฝ: ์Šน์ธ๋œ `ablecloud/community-theme` ํ™”๋ฉด ์ž์‚ฐ ์ ์šฉ - GitHubโ†’Chat ๋ณดํ˜ธ ์„œ๋น„์Šค ๋ณ€๊ฒฝ: ์—†์Œ - TechFlow AI GatewayยทPoller ๋ณ€๊ฒฝ: ์—†์Œ - ์—…๋กœ๋“œ 1 GiB/10 GiB ์ •์ฑ… ๋ณ€๊ฒฝ: ์—†์Œ @@ -165,4 +167,534 @@ Hero์˜ ์œ„ยท์•„๋ž˜ ํŒจ๋”ฉ์€ ๋ฐ์Šคํฌํ†ฑ `19px`, ๋ชจ๋ฐ”์ผ `17px`๋กœ ๋Œ€์นญํ™” ## ํŒ์ • -Issue #73์˜ ์„ค๊ณ„ยท๊ตฌํ˜„ยทWSL ๊ธฐ๋Šฅ ๊ฒ€์ฆยท๋กค๋ฐฑยท๋ฌธ์„œํ™” ์กฐ๊ฑด์„ ์ถฉ์กฑํ–ˆ๋‹ค. ์šด์˜ ์ ์šฉ์€ ๋ณ„๋„ ์Šน์ธ ์ „๊นŒ์ง€ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š๋Š”๋‹ค. ๊ฒ€ํ† ์ž๊ฐ€ ์šด์˜ ๋ฐ˜์˜์„ ์Šน์ธํ•˜๋ฉด Runbook์— ๋”ฐ๋ผ ํ…Œ๋งˆ ํ™•์žฅ๋งŒ ์ ์šฉํ•˜๊ณ  ๊ฐ™์€ ๊ฒ€์ฆ์„ ๋ฐ˜๋ณตํ•œ๋‹ค. +Issue #73์˜ ์„ค๊ณ„ยท๊ตฌํ˜„ยทWSL ๊ธฐ๋Šฅ ๊ฒ€์ฆยท๋กค๋ฐฑยท์šด์˜ ์ ์šฉยท๋ฌธ์„œํ™” ์กฐ๊ฑด์„ ์ถฉ์กฑํ–ˆ๋‹ค. ์šด์˜ ์ ์šฉ์€ ํ…Œ๋งˆ ํ™•์žฅ์—๋งŒ ํ•œ์ •ํ–ˆ์œผ๋ฉฐ Runbook์˜ ๋ฐฑ์—…ยท๋ฐฐํฌยท๋ธŒ๋ผ์šฐ์ € ๊ฒ€์ฆยท์„œ๋น„์Šค ํ™•์ธ ์ ˆ์ฐจ๋ฅผ ๋ชจ๋‘ ํ†ต๊ณผํ–ˆ๋‹ค. + +## 2026-08-19 ์šด์˜ ํ›„์† ์ ์šฉ + +์šด์˜ ์Šน์ธ ํ›„ ์‚ฌ์šฉ์ž ํ”„๋กœํ•„ยท์„ค์ • ๋ฉ”๋‰ด ๊ฒน์นจ ๋ณด์™„๊ณผ ์ฒซ ํ™”๋ฉด Redditํ˜• ํ‰๋ฉด ํ”ผ๋“œ๋ฅผ +WSL๊ณผ ์šด์˜ Community์— ์ ์šฉํ–ˆ๋‹ค. ๋ฐ์Šคํฌํ†ฑ ์ฒซ ํ™”๋ฉด์€ Welcome Hero, 280px ์ขŒ์ธก +๋ฉ”๋‰ด์™€ Footer๋ฅผ ๊ณ ์ •ํ•œ๋‹ค. ์šฐ์ธก `.IndexPage-results` ์ „์ฒด๊ฐ€ ์Šคํฌ๋กค๋˜๋ฏ€๋กœ `์ตœ์‹ ` +์„ ํƒ ์ฝค๋ณด๊ฐ€ ์žˆ๋Š” ๋„๊ตฌ๋ง‰๋Œ€์™€ ํ† ๋ก  ํ–‰์ด ํ•จ๊ป˜ ์ด๋™ํ•œ๋‹ค. ์ขŒ์ธก ๋ฉ”๋‰ด๋Š” ๋†’์ด๊ฐ€ ๋ถ€์กฑํ•  +๋•Œ ๋‚ด๋ถ€ ์Šคํฌ๋กคํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ์Šคํฌ๋กค๋ฐ”๋Š” ํ‘œ์‹œํ•˜์ง€ ์•Š๋Š”๋‹ค. ์šฐ์ธก ์Šคํฌ๋กค๋ฐ” ์—ญ์‹œ +์ˆจ๊ธฐ๋˜ ๋งˆ์šฐ์Šค ํœ ยทํŠธ๋ž™ํŒจ๋“œยทํ‚ค๋ณด๋“œ ์Šคํฌ๋กค ๋™์ž‘์€ ์œ ์ง€ํ•œ๋‹ค. + +๋ชฉ๋ก ๋ฐ”๊นฅ Container์™€ ๊ฐœ๋ณ„ ํ–‰์˜ BorderยทBackgroundยทShadow๋ฅผ ๋ชจ๋‘ ์ œ๊ฑฐํ–ˆ๋‹ค. +Reddit New์˜ ํ‰๋ฉด ํ”ผ๋“œ์ฒ˜๋Ÿผ ํˆฌ๋ช…ํ•œ ๋ฐฐ๊ฒฝ ์œ„์—์„œ 1px ๊ตฌ๋ถ„์„ ๋งŒ ์‚ฌ์šฉํ•˜๊ณ , +ABLESTACK์˜ ๋ธ”๋ฃจ ์ƒ‰์ƒยทํฐํŠธยทํƒœ๊ทธยท๋Œ“๊ธ€ ์ˆ˜ยท๋”๋ณด๊ธฐ ๊ธฐ๋Šฅ์€ ์œ ์ง€ํ–ˆ๋‹ค. + +WSL๊ณผ ์šด์˜ 1280x720์—์„œ ๋ฌธ์„œ `scrollY=0`, ์ขŒ์ธก ๋ฉ”๋‰ด Y์ขŒํ‘œ `179.890625`๋ฅผ +์œ ์ง€ํ–ˆ๋‹ค. ์šฐ์ธก ์˜์—ญ `scrollTop`์€ `0โ†’360`, ๋„๊ตฌ๋ง‰๋Œ€ Y์ขŒํ‘œ๋Š” +`209.890625โ†’-150.109375`๋กœ ํ•จ๊ป˜ ์ด๋™ํ–ˆ์œผ๋ฉฐ `.DiscussionList`์˜ `scrollTop`์€ +`0`์„ ์œ ์ง€ํ–ˆ๋‹ค. ์ขŒ์ธก ๋ฉ”๋‰ด๋Š” `0โ†’260`์œผ๋กœ ๋‚ด๋ถ€ ์Šคํฌ๋กค๋์ง€๋งŒ ์Šคํฌ๋กค๋ฐ” ๊ณ„์‚ฐ๊ฐ’์€ +`none`์ด์—ˆ๋‹ค. ํ–‰ ๋†’์ด๋Š” `58px`, ์™ธ๋ถ€ HTTPS๋Š” `200`, NginxยทPHP-FPMยทMariaDB๋Š” +๋ชจ๋‘ `active`๋‹ค. ์šด์˜ ์ ์šฉ ์ „ ๋ฐฑ์—…์€ +`/var/backups/techflow-flarum/theme-reddit-flat-feed-hidden-scroll-20260819T115539Z`์— +๋ณด์กดํ–ˆ๋‹ค. +SCP ์ž„์‹œ ๊ฒฝ๋กœ๊ฐ€ 0700์ธ ๊ฒฝ์šฐ์—๋„ ๋ฐฐํฌ ์Šคํฌ๋ฆฝํŠธ๊ฐ€ Extension ๊ฒฝ๋กœ๋ฅผ +Composer์™€ PHP-FPM์ด ์ฝ์„ ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์œผ๋กœ ์ •๊ทœํ™”ํ•˜๋Š” ๊ฒƒ์„ ์šด์˜ ์žฌ์ ์šฉ +`RESULT=PASS`๋กœ ํ™•์ธํ–ˆ๋‹ค. ์ƒ์„ธ ์ฆ์ ์€ +`docs/evidence/issue-73/community-index-fixed-feed-validation.json`์— ๊ธฐ๋กํ–ˆ๋‹ค. + +### Redditํ˜• ๊ฒŒ์‹œ๋ฌผ ํ•œ ๊ฑด ๊ตฌ์กฐ ์ตœ์ข… ์ ์šฉ + +ํ‰๋ฉด ํ”ผ๋“œ์˜ ์ƒ‰์ƒยทํฐํŠธยท1px ๊ตฌ๋ถ„์„ ์€ ์œ ์ง€ํ•˜๋ฉด์„œ ๊ฐ ํ† ๋ก  ํ•œ ๊ฑด์˜ ๋‚ด๋ถ€ ๊ตฌ์กฐ๋งŒ +Reddit New์— ๋งž์ถฐ ์žฌ๊ตฌ์„ฑํ–ˆ๋‹ค. ์‚ฌ์šฉ์ž ์•„๋ฐ”ํƒ€ ๋‹ค์Œ์— ํ•ด๊ฒฐ ์ƒํƒœ, ๋Œ€ํ‘œ ์นดํ…Œ๊ณ ๋ฆฌ, +์ž‘์„ฑ์ž์™€ ์ž‘์„ฑ ์‹œ๊ฐ์„ ํ•œ ํ–‰์œผ๋กœ ํ‘œ์‹œํ•˜๊ณ , ์ œ๋ชฉยท์ฒซ ๊ฒŒ์‹œ๋ฌผ ์š”์•ฝยท์ „์ฒด ํƒœ๊ทธยท๋Œ“๊ธ€ ์ˆ˜๋ฅผ +์ฐจ๋ก€๋กœ ๋ฐฐ์น˜ํ•œ๋‹ค. ์š”์•ฝ์€ ์ด๋ฏธ ๋‚ด๋ ค์˜จ Flarum Store ๋ฐ์ดํ„ฐ๋งŒ ์‚ฌ์šฉํ•˜๋ฉฐ ํ•ญ๋ชฉ๋ณ„ ์ถ”๊ฐ€ +๋„คํŠธ์›Œํฌ ์š”์ฒญ์€ ์—†๋‹ค. + +์ดˆ๊ธฐ ์šด์˜ ํ™•์ธ์—์„œ ํ•ด๊ฒฐ ๋ฐฐ์ง€ ํญ๊ณผ ๊ธฐ์กด ๋ฐฐ์ง€ Container ํญ์ด ๋‹ฌ๋ผ ์ž‘์„ฑ ์ •๋ณด๋ฅผ +๊ฐ€๋ฆฌ๋Š” ํ˜„์ƒ์ด ํ™•์ธ๋˜์—ˆ๋‹ค. ๋ฐฐ์ง€์™€ ์ž‘์„ฑ ์ •๋ณด๋ฅผ Grid์˜ ๋‘ ์—ด๋กœ ๋ถ„๋ฆฌํ•˜๊ณ  LESS์—์„œ +๋‚˜๋ˆ—์…ˆ์œผ๋กœ ์˜คํ•ด๋˜๋Š” `grid-column: 1 / -1` ๋Œ€์‹  `grid-column-start/end`๋ฅผ ๋ช…์‹œํ–ˆ๋‹ค. +์‚ฌ์šฉ์ž ์•„๋ฐ”ํƒ€์—๋Š” ๊ธฐ์กด Flarum์˜ `margin-left: -52px`๊ฐ€ ๋‚จ์•„ ์ขŒ์ธก์œผ๋กœ ์ž˜๋ ธ์œผ๋ฏ€๋กœ +`position: absolute`, `left: 12px`, `margin-left: 0`์„ ํ•จ๊ป˜ ๊ณ ์ •ํ–ˆ๋‹ค. + +์ตœ์ข… ์šด์˜ ๋ธŒ๋ผ์šฐ์ € ๊ฒ€์ฆ ๊ฒฐ๊ณผ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค. + +| ํ•ญ๋ชฉ | ๊ฒฐ๊ณผ | +|---|---| +| ๊ตฌ์กฐ๊ฐ€ ์ ์šฉ๋œ ํ˜„์žฌ ๋ชฉ๋ก | 20๊ฑด | +| ํ•ด๊ฒฐ ํ† ๋ก  #173 ํ–‰ ๋†’์ด | 187px | +| ์ผ๋ฐ˜ ํ† ๋ก  #144 ํ–‰ ๋†’์ด | 137px | +| ํ•ด๊ฒฐ ๋ฐฐ์ง€์™€ ์ž‘์„ฑ ์ •๋ณด ๊ฐ„๊ฒฉ | 8px | +| ํ•ด๊ฒฐ ๋ฐฐ์ง€์™€ ์ž‘์„ฑ ์ •๋ณด ๊ฒน์นจ | ์—†์Œ | +| ํ•ด๊ฒฐยท์ผ๋ฐ˜ ํ† ๋ก  ์•„๋ฐ”ํƒ€ | ๋ชจ๋‘ ํ–‰ ์ขŒ์ธก ์ƒ๋‹จ, ๋ณธ๋ฌธ ๊ฒฝ๊ณ„ ์•ˆ | +| ์ด๋ฏธ์ง€ ํฌํ•จ ํ† ๋ก  | ์ฒซ ์ด๋ฏธ์ง€ 124ร—92px ์šฐ์ธก ์ธ๋„ค์ผ, ์ง€์—ฐ ๋กœ๋”ฉ | +| ์ด๋ฏธ์ง€ ์—†๋Š” ํ† ๋ก  | ์ธ๋„ค์ผ ์—ด ์—†์ด ๊ธฐ์กด ํญ ์œ ์ง€ | +| ๋ฌธ์„œ `scrollY` | 0 | +| ์ขŒ์ธกยท์šฐ์ธก ์Šคํฌ๋กค๋ฐ” | ์ˆจ๊น€ ์œ ์ง€ | +| ์™ธ๋ถ€ HTTPS | 200 | +| NginxยทPHP-FPMยทMariaDBยทMonitor Timer | ๋ชจ๋‘ active | + +์ฒซ ๊ฒŒ์‹œ๋ฌผ HTML์— ์ด๋ฏธ์ง€๊ฐ€ ์žˆ์œผ๋ฉด ์ฒซ ์ด๋ฏธ์ง€๋ฅผ ๋ชฉ๋ก ์˜ค๋ฅธ์ชฝ์— Redditํ˜• ์ธ๋„ค์ผ๋กœ +ํ‘œ์‹œํ•œ๋‹ค. Flarum Store์— ์ด๋ฏธ ํฌํ•จ๋œ `firstPost.contentHtml()`์„ ์ฝ์œผ๋ฏ€๋กœ ํ† ๋ก ๋ณ„ +์ถ”๊ฐ€ API ์š”์ฒญ์€ ์—†๊ณ , `loading=lazy`์™€ `decoding=async`๋กœ ํ™”๋ฉด ๊ฐ€๊นŒ์ด์— ์˜ฌ ๋•Œ๋งŒ +์ด๋ฏธ์ง€๋ฅผ ๋ถˆ๋Ÿฌ์˜จ๋‹ค. ํ—ˆ์šฉ URL์€ HTTPยทHTTPS๋กœ ์ œํ•œํ•˜๋ฉฐ ์ด๋ฏธ์ง€๊ฐ€ ์—†๋Š” ๊ธ€์€ ๋ณ„๋„ +๊ณต๊ฐ„์„ ๋งŒ๋“ค์ง€ ์•Š๋Š”๋‹ค. WSL ํ† ๋ก  #139์™€ ์šด์˜ ํ† ๋ก  #169์—์„œ 124ร—92px ์ธ๋„ค์ผ, +์›๋ณธ ์ด๋ฏธ์ง€ ์ •์ƒ ๋กœ๋“œ, ์‚ฌ์šฉ์ž ์•„์ด์ฝ˜ยทํ•ด๊ฒฐ ๋ฐฐ์ง€ยท์ œ๋ชฉยท์š”์•ฝ ๋น„์ค‘์ฒฉ์„ ํ™•์ธํ–ˆ๋‹ค. + +์ตœ์ข… ์šด์˜ ๋ฐฑ์—…์€ +`/var/backups/techflow-flarum/theme-reddit-post-thumbnail-20260819T122946Z`์— +๋ณด์กดํ•œ๋‹ค. ์ตœ์ข… LESS SHA-256์€ +`f8c28acbe4aa4c38b6fd25dc3d941ebfbb1dd4c664f684ba4196c17aaa674b73`์ด๋ฉฐ, +์ž„์‹œ ์ „์†ก ์ž์‚ฐ์€ ์‚ญ์ œํ–ˆ๋‹ค. + +### ํ•˜๋‹จ Footer ์ œ๊ฑฐ์™€ ์šฐ์ธก ๊ณ ์ • ํ€ต ๋งํฌ ์ ์šฉ + +ํ™”๋ฉด ํ•˜๋‹จ์„ ๊ณ„์† ์ฐจ์ง€ํ•˜๋˜ 56px ๊ณ ์ • Footer๋Š” ์‹œ๊ฐ์  ๋ ˆ์ด์•„์›ƒ์—์„œ ์ œ๊ฑฐํ–ˆ๋‹ค. +๊ธฐ์กด `ABLECLOUD Home`, `Blog`, `Online Docs` ๋งํฌ์™€ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ์ด๋ฆ„์€ ๊ทธ๋Œ€๋กœ +์œ ์ง€ํ•˜๋˜, ์ฝ˜ํ…์ธ  ์˜ค๋ฅธ์ชฝ์— ์›ํ˜• ์•„์ด์ฝ˜ 3๊ฐœ๋ฅผ ์„ธ๋กœ๋กœ ๋ฐฐ์น˜ํ•œ ๊ณ ์ • ํ€ต ๋งํฌ๋กœ +์ „ํ™˜ํ–ˆ๋‹ค. ํ€ต ๋งํฌ๋Š” ํ† ๋ก  ๋ชฉ๋ก์˜ ์Šคํฌ๋กค ์˜์—ญ ๋ฐ–์— ์žˆ์œผ๋ฏ€๋กœ ๋ชฉ๋ก์„ ์ด๋™ํ•ด๋„ +์œ„์น˜๊ฐ€ ๋ณ€ํ•˜์ง€ ์•Š๋Š”๋‹ค. + +Footer๊ฐ€ ์ฐจ์ง€ํ•˜๋˜ ํ•˜๋‹จ ๊ณต๊ฐ„์€ ํšŒ์ˆ˜ํ•˜๋˜ ๊ธฐ์กด ์ฝ˜ํ…์ธ  ํญ์€ ๋ฐ”๊พธ์ง€ ์•Š์•˜๋‹ค. +1100px ์ฝ˜ํ…์ธ  ์˜์—ญ ์˜ค๋ฅธ์ชฝ์— 48px ํ€ต ๋งํฌ์™€ 17px ๋ฐฐ์น˜ ์˜์—ญ๋งŒ ์ถ”๊ฐ€ํ•ด ์ „์ฒด ์…ธ์„ +1165px๋กœ ํ™•์žฅํ–ˆ๋‹ค. 1680ร—825 ํ™”๋ฉด์—์„œ ์‹ค์ œ ํ† ๋ก  ๋ชฉ๋ก ํญ์€ ๊ธฐ์กด๊ณผ ๊ฐ™์€ 758px์ด๊ณ , +๋ชฉ๋ก ์˜ค๋ฅธ์ชฝ์—์„œ 16px ๋–จ์–ด์ง„ ์ง€์ ์— ํ€ต ๋งํฌ๊ฐ€ ๋ฐ”๋กœ ์ด์–ด์ง„๋‹ค. ๋ชฉ๋ก ๋†’์ด๋Š” +615px์ด๋ฉฐ ํ™”๋ฉด ํ•˜๋‹จ๊นŒ์ง€ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. + +| ๊ฒ€์ฆ ํ•ญ๋ชฉ | WSL | ์šด์˜ | +|---|---:|---:| +| ์ „์ฒด ์…ธ ํญ | 1165px | 1165px | +| ๊ธฐ์กด ์ฝ˜ํ…์ธ  ํญ | 1100px | 1100px | +| ํ† ๋ก  ๋ชฉ๋ก ํญ | 758px | 758px | +| ํ€ต ๋งํฌ ํฌ๊ธฐ | 48ร—148px | 48ร—148px | +| ํ€ต ๋งํฌ ๊ฐœ์ˆ˜ | 3 | 3 | +| ๋ชฉ๋ก-ํ€ต ๋งํฌ ๊ฐ„๊ฒฉ | 16px | 16px | +| ๋ชฉ๋ก ์Šคํฌ๋กค ํ›„ ํ€ต ๋งํฌ Y | 339px โ†’ 339px | 339px โ†’ 339px | +| ํ•˜๋‹จ ๊ณ ์ • Footer | ์—†์Œ | ์—†์Œ | + +๋ชจ๋ฐ”์ผ 390ร—844์—์„œ๋Š” 44ร—136px ํ€ต ๋งํฌ๋ฅผ ํ™”๋ฉด ์˜ค๋ฅธ์ชฝ ์•„๋ž˜์— ๋ฐฐ์น˜ํ•ด ๋ณธ๋ฌธ ํญ์„ +์นจ๋ฒ”ํ•˜์ง€ ์•Š๋„๋ก ํ–ˆ๋‹ค. WSL๊ณผ ์šด์˜์˜ ๋ฐ์Šคํฌํ†ฑยท๋ชจ๋ฐ”์ผ ๋ธŒ๋ผ์šฐ์ € ๊ฒ€์ฆ, ํ…Œ๋งˆ ๊ณ„์•ฝ +ํ…Œ์ŠคํŠธ 12๊ฑด, ์„œ๋น„์Šค ์ƒํƒœ์™€ HTTP 200 ๊ฒ€์‚ฌ๋ฅผ ๋ชจ๋‘ ํ†ต๊ณผํ–ˆ๋‹ค. ์šด์˜ ์ ์šฉ ์ „ ๋ฐฑ์—…์€ +`/var/backups/techflow-flarum/theme-adjacent-quick-links-20260819T125523Z`์— +๋ณด์กดํ–ˆ๊ณ  ์ตœ์ข… LESS SHA-256์€ +`f586c66c079e71d75b26a9f29a54d15bacd2f63d87eb76cfd69c223300df86cf`์ด๋‹ค. + +### ์ƒ๋‹จ ํ—ค๋”์™€ ๋ณธ๋ฌธ ์…ธ ํญ ์ •๋ ฌ + +ํ€ต ๋งํฌ ์˜์—ญ์„ ์ถ”๊ฐ€ํ•œ ๋’ค ๋ณธ๋ฌธ ์…ธ์€ 1165px์ด์—ˆ์ง€๋งŒ ์ƒ๋‹จ ํ—ค๋”์™€ ํ™˜์˜ ์˜์—ญ์€ +Flarum ๊ธฐ๋ณธ 1100px์— ๋‚จ์•„ ์ขŒ์šฐ ์™ธ๊ณฝ์„ ์ด ๋งž์ง€ ์•Š์•˜๋‹ค. ๋ฐ์Šคํฌํ†ฑ ์ฒซ ํ™”๋ฉด์—๋งŒ +ํ—ค๋”์™€ ํ™˜์˜ ์˜์—ญ์˜ ์ตœ๋Œ€ ํญ์„ 1165px๋กœ ์ง€์ •ํ•ด ๋ณธ๋ฌธ ์…ธ๊ณผ ๊ฐ™์€ ๊ธฐ์ค€์„ ์„ ์‚ฌ์šฉํ–ˆ๋‹ค. +๋ชจ๋ฐ”์ผ๊ณผ ์ƒ์„ธยท์„ค์ • ํ™”๋ฉด์˜ ํญ ๊ทœ์น™์€ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š์•˜๋‹ค. + +1680ร—825 ํ™”๋ฉด์˜ WSL๊ณผ ์šด์˜ ์ธก์ •๊ฐ’์€ ๋™์ผํ•˜๋‹ค. + +| ์˜์—ญ | X | ์˜ค๋ฅธ์ชฝ | ํญ | +|---|---:|---:|---:| +| ์ƒ๋‹จ ํ—ค๋” | 258px | 1423px | 1165px | +| ํ™˜์˜ ์˜์—ญ | 258px | 1423px | 1165px | +| ๋ณธ๋ฌธ ์…ธ | 258px | 1423px | 1165px | + +์šด์˜ ์ ์šฉ ์ „ ๋ฐฑ์—…์€ +`/var/backups/techflow-flarum/theme-header-shell-align-20260819T154310Z`์— ๋ณด์กดํ–ˆ๋‹ค. +์ตœ์ข… LESS SHA-256์€ +`2f2557d903df02aedc7a3a252a97048f9cbcf9b91e36fbaebf2aa84bae15cccc`์ด๋ฉฐ, +HTTP 200๊ณผ NginxยทPHP-FPMยทMariaDBยทMonitor Timer `active`๋ฅผ ํ™•์ธํ–ˆ๋‹ค. + +### ๋ฉ”์ธ ํ™˜์˜ ๋ฐฐ๋„ˆ ์ œ๊ฑฐ์™€ ํ† ๋ก  ์ƒ์„ธ ์ฝ๊ธฐ ํ๋ฆ„ ์ ์šฉ + +2026-08-20 ๊ธฐ์ค€์œผ๋กœ ์ฒซ ํ™”๋ฉด์˜ Welcome Hero๋ฅผ ํ‘œ์‹œํ•˜์ง€ ์•Š๋„๋ก ๋ณ€๊ฒฝํ–ˆ๋‹ค. ์ƒ๋‹จ +Header์™€ Index ๋ณธ๋ฌธ์€ ๊ธฐ์กด 1165px ์…ธ์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๋ฉฐ, Redditํ˜• ๋ชฉ๋ก ๊ตฌ์กฐ, +280px ์ขŒ์ธก ๋ฉ”๋‰ด์™€ ์ฝ˜ํ…์ธ  ๋ฐ”๋กœ ์˜† ํ€ต ๋งํฌ ์œ„์น˜๋„ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š์•˜๋‹ค. WSL๊ณผ ์šด์˜์˜ +1680px Viewport์—์„œ Header์™€ Index ๋ณธ๋ฌธ์€ ๋ชจ๋‘ X 258px, ํญ 1165px, ์˜ค๋ฅธ์ชฝ +1423px๋กœ ์ผ์น˜ํ–ˆ๊ณ  ํ˜„์žฌ ๋ชฉ๋ก 20๊ฑด์ด ์ •์ƒ ํ‘œ์‹œ๋๋‹ค. + +ํ† ๋ก  ์ƒ์„ธ๋Š” Reddit์˜ ๊ฒŒ์‹œ๋ฌผ ์ฝ๊ธฐ ์ˆœ์„œ๋งŒ ์ฐธ๊ณ ํ–ˆ๋‹ค. ์ขŒ์šฐ ๋ถ€๊ฐ€ ์ •๋ณด ํŒจ๋„์€ ๋งŒ๋“ค์ง€ +์•Š๊ณ  ๊ธฐ์กด 1100px ์ฝ˜ํ…์ธ  ์•ˆ์—์„œ ์ œ๋ชฉ๊ณผ ๊ฒŒ์‹œ๋ฌผ Stream์„ ๋‹จ์ผ ์—ด๋กœ ๊ตฌ์„ฑํ–ˆ๋‹ค. ๊ธฐ์กด +150px ์šฐ์ธก ํƒ์ƒ‰ ์—ด์˜ ๋‹ต์žฅยทํŒ”๋กœ์šฐยท๊ฒŒ์‹œ๋ฌผ ์ด๋™ ๊ธฐ๋Šฅ์€ ๋ณธ๋ฌธ ์œ„ 64px ๊ฐ€๋กœ ๋„๊ตฌ +๋ชจ์Œ์œผ๋กœ ์˜ฎ๊ฒผ๋‹ค. ์ œ๋ชฉ ์˜์—ญ์€ ํ™˜์˜ ๋ฐฐ๋„ˆ ์™ธํ˜• ๋Œ€์‹  ํฐ ๋ฐฐ๊ฒฝ์—์„œ ํ•ด๊ฒฐ ์ƒํƒœยทํƒœ๊ทธยท +์ œ๋ชฉยท์ž‘์„ฑ์žยท์ž‘์„ฑ ์‹œ๊ฐ์„ ์™ผ์ชฝ ์ •๋ ฌํ•œ๋‹ค. + +์ผ๋ฐ˜ ์งˆ๋ฌธ๊ณผ ๋‹ต๋ณ€์€ Card Border์™€ Shadow ์—†์ด 1px ๊ตฌ๋ถ„์„ ์œผ๋กœ ๋‚˜๋ˆ„๊ณ  AI ๊ธฐ์ˆ ์ง€์›, +์ถ”๊ฐ€ ํ™•์ธ ํ•„์š”, ์ตœ์ข… ํ•ด๊ฒฐ ๊ฐ€์ด๋“œ๋งŒ ์˜๋ฏธ ์ƒ‰์ƒ ์นด๋“œ๋กœ ์œ ์ง€ํ–ˆ๋‹ค. ์งˆ๋ฌธ ๋ฐ”๋กœ ์•„๋ž˜์—๋Š” +๊ธฐ์กด Flarum ๋‹ต์žฅ ์ž‘์„ฑ๊ธฐ๋ฅผ ์—ฌ๋Š” `๋‹ต๋ณ€์„ ์ž‘์„ฑํ•ด ์ฃผ์„ธ์š”` ๋ฒ„ํŠผ์„ ๋ฐฐ์น˜ํ–ˆ๋‹ค. 480px๋ณด๋‹ค +๊ธด `pre` ๋กœ๊ทธ๋Š” ์ฒ˜์Œ์— ์ ‘๊ณ  `์ „์ฒด ๋กœ๊ทธ ๋ณด๊ธฐ`์™€ `๋กœ๊ทธ ์ ‘๊ธฐ`๋กœ ์ „ํ™˜ํ•œ๋‹ค. + +ํ•ด๊ฒฐ๋œ ํ† ๋ก ์€ ์ฒซ ์งˆ๋ฌธ ์•ˆ์— ์ตœ์ข… ๋‹ต๋ณ€ ์ „์ฒด๋ฅผ ๋‹ค์‹œ ๋ณด์—ฌ์ฃผ์ง€ ์•Š๋Š”๋‹ค. ์ƒ๋‹จ์˜ +`ํ•ด๊ฒฐ ๋‹ต๋ณ€ ๋ณด๊ธฐ`๋ฅผ ๋ˆ„๋ฅด๋ฉด Stream ์•ˆ์˜ ์‹ค์ œ ์„ ํƒ ๋‹ต๋ณ€์œผ๋กœ ์ด๋™ํ•œ๋‹ค. ์šด์˜ ํ† ๋ก  +#173์—์„œ ๋‹ค์Œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ธŒ๋ผ์šฐ์ €๋กœ ์ง์ ‘ ํ™•์ธํ–ˆ๋‹ค. + +| ๊ฒ€์ฆ ํ•ญ๋ชฉ | ๊ฒฐ๊ณผ | +|---|---| +| ํ† ๋ก  ์ œ๋ชฉ | v2k์—์„œ vCenter ์—†์ด ESXi ํ˜ธ์ŠคํŠธ์—์„œ ๊ฐ€์ƒ๋จธ์‹  ์ด๊ด€ ๊ธฐ๋Šฅ์„ ์ง€์›ํ•˜๋‚˜์š”? | +| ์ž‘์„ฑ ์ •๋ณด | dhslove ยท 2์ผ ์ „ | +| ํ•ด๊ฒฐ ์ƒํƒœ | ํ•ด๊ฒฐ๋จ | +| ์‹ค์ œ ๊ฒŒ์‹œ๋ฌผ ๋ฒˆํ˜ธ | 1, 2, 3, 4, 5 | +| ์„ ํƒ ๋‹ต๋ณ€ | 5๋ฒˆ ๊ฒŒ์‹œ๋ฌผ | +| ์„ ํƒ ๋‹ต๋ณ€ ๊ตฌ์„ฑ | ์ฆ์ƒยท์›์ธยทํ•ด๊ฒฐ ๋ฐฉ๋ฒ•ยท์ถ”๊ฐ€ ๊ณ ๋ ค์‚ฌํ•ญยท์ ์šฉ ๋ฒ„์ „ | +| ์ ์šฉ ๋ฒ„์ „ ํ‘œ์‹œ | ABLESTACK Diplo | +| ์ค‘๋ณต ์„ ํƒ ๋‹ต๋ณ€ ๋ฏธ๋ฆฌ๋ณด๊ธฐ | ํ‘œ์‹œํ•˜์ง€ ์•Š์Œ | +| ํ•ด๊ฒฐ ๋‹ต๋ณ€ ์ด๋™ ํ›„ ์ƒ๋‹จ ์œ„์น˜ | 76px | +| ์ƒ์„ธ ๋ณธ๋ฌธ ํญ | 1070px (1100px Container ๋‚ด๋ถ€ ์—ฌ๋ฐฑ ํฌํ•จ) | +| ๊ฐ€๋กœ ๋„๊ตฌ ๋ชจ์Œ ๋†’์ด | 64px | + +WSL ํ† ๋ก  #144์—์„œ๋Š” ์ œ๋ชฉ, ์ž‘์„ฑ์ž `sijo`, ๊ฒŒ์‹œ๋ฌผ 4๊ฑด๊ณผ ์ฒซ ์งˆ๋ฌธ ์›๋ฌธ์ด ์‹ค์ œ +๋ฐ์ดํ„ฐ์™€ ์ผ์น˜ํ–ˆ๋‹ค. WSL Cycle์€ ์‚ฌ์šฉ์ž 39๋ช…, ํ† ๋ก  117๊ฑด, ๊ฒŒ์‹œ๋ฌผ 305๊ฑด๊ณผ DBยท์ฒจ๋ถ€ +ํ•ด์‹œ๋ฅผ ์ ์šฉยท๋น„ํ™œ์„ฑํ™” ๋กค๋ฐฑยท์ตœ์ข… ์žฌ์ ์šฉ ์ „ํ›„์— ๋™์ผํ•˜๊ฒŒ ์œ ์ง€ํ–ˆ๊ณ  13๊ฐœ ํ…Œ๋งˆ ๊ณ„์•ฝ +๊ฒ€์‚ฌ๋ฅผ ํ†ต๊ณผํ–ˆ๋‹ค. + +์ตœ์‹  ์šด์˜ ๋ฐฑ์—…์€ +`/var/backups/techflow-flarum/theme-detail-toolbar-20260820T003355Z`์ด๋ฉฐ, LESS์™€ +JS์˜ ์šด์˜ Vendor ํ•ด์‹œ๋Š” ๊ฐ๊ฐ +`e56463d030b90df6c9626ab2d20595ae51c4352923f4a0f9c67a8bbc644fd0fd`, +`05115ef3395d87d0f7327a50bac518376075b3cbd9bbeed356f48ee64521d42e`๋กœ ๋กœ์ปฌ +์›๋ณธ๊ณผ ์ผ์น˜ํ•œ๋‹ค. ์™ธ๋ถ€ HTTPS์™€ ์„œ๋ฒ„ ๋กœ์ปฌ HTTP๋Š” 200์ด๋ฉฐ NginxยทPHP-FPMยทMariaDBยท +Community Monitor Timer๋Š” ๋ชจ๋‘ `active`๋‹ค. DB Schema, ๊ฒŒ์‹œ๋ฌผ, ์ฒจ๋ถ€, TechFlow AI, +GitHubโ†’Chat Webhook์€ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š์•˜๋‹ค. + +### Flarum ์„ธ๋กœ ํƒ์ƒ‰ ๋ณต์›๊ณผ ์‹œ์ž‘ยท์ƒ์„ธ ์…ธ ์ •๋ ฌ + +์ƒ์„ธ ์ฝ๊ธฐ ํ๋ฆ„์„ ๋‹จ์ผ ์—ด๋กœ ๋ณ€๊ฒฝํ•˜๋ฉด์„œ ์ œ๊ฑฐ๋๋˜ ์˜์—ญ์€ Reddit์˜ ๋ถ€๊ฐ€ ์ •๋ณด๊ฐ€ ์•„๋‹ˆ๋ผ +Flarum์˜ ๊ณ ์œ  ํ† ๋ก  ํƒ์ƒ‰ ๊ธฐ๋Šฅ์ด์—ˆ๋‹ค. ์‚ฌ์šฉ์ž ๊ฒ€ํ† ์— ๋”ฐ๋ผ `๋‹ต์žฅ`, `ํŒ”๋กœ์šฐ`, `์›๋ณธ +๊ฒŒ์‹œ๋ฌผ`, `์ตœ์‹  ๊ฒŒ์‹œ๋ฌผ`, ํ˜„์žฌ ๊ฒŒ์‹œ๋ฌผ ์œ„์น˜์™€ ์ฝ์ง€ ์•Š์€ ๊ฒŒ์‹œ๋ฌผ ์ˆ˜๋ฅผ ํ‘œ์‹œํ•˜๋Š” 150px +์„ธ๋กœ ํƒ์ƒ‰ ์˜์—ญ์„ ๊ธฐ์กด Sticky ๋ฐฐ์น˜๋กœ ๋ณต์›ํ–ˆ๋‹ค. Reddit์˜ ์ขŒ์šฐ ์ปค๋ฎค๋‹ˆํ‹ฐ ์ •๋ณด ํŒจ๋„์€ +์ถ”๊ฐ€ํ•˜์ง€ ์•Š์•˜๋‹ค. + +์‹œ์ž‘ ํŽ˜์ด์ง€๋Š” ํ€ต ๋งํฌ ์˜ˆ์•ฝ ์˜์—ญ์„ ํฌํ•จํ•œ 1165px ์…ธ์ด์ง€๋งŒ ์ƒ์„ธ ํŽ˜์ด์ง€๋Š” 1100px์— +๋‚จ์•„ ์žˆ์—ˆ๋‹ค. ์ƒ์„ธ Hero์™€ ๋ณธ๋ฌธ Container๋„ 1165px๋กœ ํ†ต์ผํ•˜๊ณ  ์˜ค๋ฅธ์ชฝ 80px์„ ํ€ต +๋งํฌ์šฉ์œผ๋กœ ์˜ˆ์•ฝํ–ˆ๋‹ค. ์ด์— ๋”ฐ๋ผ ๋ณธ๋ฌธ 845px, ๋ณธ๋ฌธ๊ณผ ํƒ์ƒ‰ ์‚ฌ์ด 75px, ์„ธ๋กœ ํƒ์ƒ‰ +150px์˜ ๊ธฐ์กด ๋น„์œจ์„ ๋ณด์กดํ•˜๋ฉด์„œ ํ€ต ๋งํฌ์™€ ํƒ์ƒ‰ ์˜์—ญ์ด ๊ฒน์น˜์ง€ ์•Š๋Š”๋‹ค. ์‹œ์ž‘ ํŽ˜์ด์ง€๋Š” +`scrollbar-gutter: stable`์„ ์‚ฌ์šฉํ•ด ์ƒ์„ธ ํŽ˜์ด์ง€์˜ ์„ธ๋กœ Scrollbar ๊ณต๊ฐ„๊ณผ ๋™์ผํ•œ +๊ธฐ์ค€์„ ์„ ์‚ฌ์šฉํ•œ๋‹ค. + +๋˜ํ•œ ๊ณ ์ • Header ์‹ค์ œ ํ•˜๋‹จ๋ณด๋‹ค ์ƒ์„ธ Hero๊ฐ€ ๋จผ์ € ์‹œ์ž‘ํ•ด ํƒœ๊ทธ๊ฐ€ Header์— ๋ถ™์–ด ๋ณด์ด๋˜ +๋ฌธ์ œ๋ฅผ ์ˆ˜์ •ํ–ˆ๋‹ค. ๋ฐ์Šคํฌํ†ฑ ์ƒ์„ธ Hero๋ฅผ Header ์•„๋ž˜๋กœ 16px ์ด๋™ํ•˜๊ณ  ๊ธฐ์กด ๋‚ด๋ถ€ ์—ฌ๋ฐฑ์„ +์œ ์ง€ํ–ˆ๋‹ค. WSL๊ณผ ์šด์˜ ๋ธŒ๋ผ์šฐ์ €์—์„œ Header ํ•˜๋‹จ๊ณผ ์ฒซ ํƒœ๊ทธ ์ƒ๋‹จ ๊ฐ„๊ฒฉ์€ 18px์ด์—ˆ๋‹ค. + +| ๊ฒ€์ฆ ํ•ญ๋ชฉ | WSL | ์šด์˜ | +|---|---:|---:| +| ์‹œ์ž‘ Header ์…ธ | X 250 / ํญ 1165 / ์šฐ์ธก 1415 | X 250 / ํญ 1165 / ์šฐ์ธก 1415 | +| ์‹œ์ž‘ ๋ณธ๋ฌธ ์…ธ | X 250 / ํญ 1165 / ์šฐ์ธก 1415 | X 250 / ํญ 1165 / ์šฐ์ธก 1415 | +| ์ƒ์„ธ Hero ์…ธ | X 250 / ํญ 1165 / ์šฐ์ธก 1415 | X 250 / ํญ 1165 / ์šฐ์ธก 1415 | +| ์ƒ์„ธ ๋ณธ๋ฌธ ์…ธ | X 250 / ํญ 1165 / ์šฐ์ธก 1415 | X 250 / ํญ 1165 / ์šฐ์ธก 1415 | +| ๊ฒŒ์‹œ๋ฌผ Stream | 845px | 845px | +| ์„ธ๋กœ ํƒ์ƒ‰ | 150ร—467px | 150ร—517px | +| Header์™€ ์ฒซ ํƒœ๊ทธ ๊ฐ„๊ฒฉ | 18px | 18px | +| ํƒ์ƒ‰๊ณผ ํ€ต ๋งํฌ ๊ฒน์นจ | 0px | 0px | + +์šด์˜ ํ† ๋ก  #173์˜ ๊ฒŒ์‹œ๋ฌผ 1~5, ์„ ํƒ ๋‹ต๋ณ€ #5, ์ค‘๋ณต ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ˆจ๊น€๊ณผ ์ธ๋ผ์ธ ๋‹ต๋ณ€ +์ž‘์„ฑ ๊ธฐ๋Šฅ๋„ ๊ทธ๋Œ€๋กœ ์œ ์ง€๋๋‹ค. ์ตœ์‹  ์šด์˜ ๋ฐฑ์—…์€ +`/var/backups/techflow-flarum/theme-native-nav-shell-20260820T005555Z`์ด๋ฉฐ ์ตœ์ข… +LESS SHA-256์€ +`7a68b73b596cddef4308bdfaa7c4f7b2206ddab818c2bb32c097331a93aa3298`์ด๋‹ค. + +### ์ƒ์„ธ Headerยท์ œ๋ชฉยท๋ณธ๋ฌธ ๊ธฐ์ค€์„  ์ตœ์ข… ํ†ต์ผ + +ํ›„์† ์šด์˜ ํ™”๋ฉด์—์„œ ์ƒ์„ธ ์ œ๋ชฉ๊ณผ ๋ณธ๋ฌธ์€ 1165px ์…ธ์„ ์‚ฌ์šฉํ•˜์ง€๋งŒ Header๋งŒ Flarum +๊ธฐ๋ณธ 1100px ์ปจํ…Œ์ด๋„ˆ์— ๋‚จ์•„ ์–‘์ชฝ ๊ธฐ์ค€์„ ์ด 32.5px์”ฉ ์–ด๊ธ‹๋‚˜๋Š” ๊ฒƒ์„ ํ™•์ธํ–ˆ๋‹ค. +๋ฐ์Šคํฌํ†ฑ ์ƒ์„ธ ํ™”๋ฉด์˜ Header์—๋„ ํŽ˜์ด์ง€ ๊ณตํ†ต 1165px ์…ธ์„ ์ ์šฉํ•ด Header, ์ƒ์„ธ +Hero, ๋ณธ๋ฌธ Container๊ฐ€ ๊ฐ™์€ ์™ธ๊ณฝ์„ ์„ ์‚ฌ์šฉํ•˜๋„๋ก ๊ต์ •ํ–ˆ๋‹ค. ๋‚ด๋ถ€ ๋กœ๊ณ ยท์ œ๋ชฉยท๊ฒŒ์‹œ๋ฌผ์€ +๊ฐ ์…ธ์˜ 15px ์•ˆ์ชฝ ์—ฌ๋ฐฑ์—์„œ ๋™์ผํ•˜๊ฒŒ ์‹œ์ž‘ํ•œ๋‹ค. + +| ๋ธŒ๋ผ์šฐ์ € ์‹ค์ธก ํ•ญ๋ชฉ | WSL | ์šด์˜ | +|---|---:|---:| +| Header ์…ธ | X 250 / ํญ 1165 / ์šฐ์ธก 1415 | X 250 / ํญ 1165 / ์šฐ์ธก 1415 | +| ์ƒ์„ธ Hero ์…ธ | X 250 / ํญ 1165 / ์šฐ์ธก 1415 | X 250 / ํญ 1165 / ์šฐ์ธก 1415 | +| ์ƒ์„ธ ๋ณธ๋ฌธ ์…ธ | X 250 / ํญ 1165 / ์šฐ์ธก 1415 | X 250 / ํญ 1165 / ์šฐ์ธก 1415 | +| ๋กœ๊ณ ยท์ œ๋ชฉยท๋ณธ๋ฌธ ์‹œ์ž‘์„  | X 265 | X 265 | + +WSL ํ† ๋ก  #144์™€ ์šด์˜ ํ† ๋ก  #173์˜ ์‹ค์ œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ธŒ๋ผ์šฐ์ €๋กœ ๋‹ค์‹œ ํ™•์ธํ–ˆ๊ณ , ๊ฒŒ์‹œ๋ฌผ +Stream 845px์™€ Flarum ์„ธ๋กœ ํƒ์ƒ‰ 150px์€ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š์•˜๋‹ค. WSL ๊ณ„์•ฝ ํ…Œ์ŠคํŠธ 13๊ฑด๊ณผ +`issue-73-header-shell-align-v2` ์ ์šฉยท๋น„ํ™œ์„ฑํ™”ยท์žฌ์ ์šฉ ์ฃผ๊ธฐ๊ฐ€ ํ†ต๊ณผํ–ˆ์œผ๋ฉฐ ์ฝ˜ํ…์ธ ์™€ +์ฒจ๋ถ€ ํ•ด์‹œ๋„ ๋ณด์กด๋๋‹ค. ์šด์˜ ํ…Œ๋งˆ Source์™€ Vendor LESS SHA-256์€ ๋ชจ๋‘ +`5408e0c2fe0e52f43e8302adb1fc2f69b9d950de71171963ff172c88159602da`๋กœ +์ผ์น˜ํ•œ๋‹ค. ์ตœ์‹  ์šด์˜ ๋ฐฑ์—…์€ +`/var/backups/techflow-flarum/theme-header-shell-align-20260820T0120KST`์ด๋ฉฐ, +์„œ๋ฒ„ ๋กœ์ปฌ HTTP 200๊ณผ NginxยทPHP-FPMยทMariaDBยทCommunity Monitor Timer `active`๋ฅผ +ํ™•์ธํ–ˆ๋‹ค. DB Schema, ๊ฒŒ์‹œ๋ฌผ, ์ฒจ๋ถ€, TechFlow AI์™€ GitHubโ†’Chat Webhook์€ ๋ณ€๊ฒฝํ•˜์ง€ +์•Š์•˜๋‹ค. + +### ์ข์€ ํ† ๋ก  ๋ชฉ๋ก ์žฌ๋ฐฐ์—ด๊ณผ ์ž‘์„ฑ๊ธฐ ์•„์ด์ฝ˜ ๋ณต๊ตฌ + +์ƒ์„ธ ํ™”๋ฉด ์™ผ์ชฝ์— ๋งˆ์šฐ์Šค๋ฅผ ๊ฐ€์ ธ๊ฐ”์„ ๋•Œ ํ‘œ์‹œ๋˜๋Š” ํ† ๋ก  ๋ชฉ๋ก์˜ 400px ํญ์€ ์œ ์ง€ํ–ˆ๋‹ค. +๊ธฐ์กด์—๋Š” ๋ฐ์Šคํฌํ†ฑ ๋ชฉ๋ก์˜ ๊ฐ€๋กœ Flex ๊ทœ์น™์ด ์ข์€ ๋ชฉ๋ก์—๋„ ์ƒ์†๋˜์–ด ์ œ๋ชฉ, ์š”์•ฝ๊ณผ +์ธ๋„ค์ผ์ด ํ•œ ํ–‰์—์„œ ์„œ๋กœ ํญ์„ ๋นผ์•—๊ณ , ์˜ค๋ฅธ์ชฝ ์—ฌ๋ฐฑ๋„ 104px์ด์–ด์„œ ์ œ๋ชฉ์ด ๋ช‡ ๊ธ€์ž๋งŒ +๋ณด์˜€๋‹ค. + +์ข์€ ๋ชฉ๋ก๋งŒ ๋ณ„๋„ Grid๋กœ ๋ถ„๋ฆฌํ•ด ๋‹ค์Œ ์ˆœ์„œ๋กœ ์žฌ๋ฐฐ์—ดํ–ˆ๋‹ค. + +1. ์™ผ์ชฝ์—๋Š” ์ž‘์„ฑ์ž ์•„๋ฐ”ํƒ€๋ฅผ ๊ณ ์ •ํ•œ๋‹ค. +2. ์ฒซ ํ–‰์—๋Š” ์นดํ…Œ๊ณ ๋ฆฌยท์ž‘์„ฑ์žยท๋‚ ์งœ๋ฅผ ํ‘œ์‹œํ•œ๋‹ค. +3. ์ œ๋ชฉ์€ ๋…๋ฆฝ ํ–‰์—์„œ ์ตœ๋Œ€ ๋‘ ์ค„์„ ์‚ฌ์šฉํ•œ๋‹ค. +4. ์š”์•ฝ์€ ์ œ๋ชฉ ์•„๋ž˜ ๋…๋ฆฝ ํ–‰์—์„œ ์ตœ๋Œ€ ๋‘ ์ค„์„ ์‚ฌ์šฉํ•œ๋‹ค. +5. ์ด๋ฏธ์ง€๊ฐ€ ์žˆ์œผ๋ฉด 72ร—54px ์ธ๋„ค์ผ์„ ์˜ค๋ฅธ์ชฝ ์ „์šฉ ์—ด์— ํ‘œ์‹œํ•œ๋‹ค. +6. ๋Œ“๊ธ€ ์ˆ˜๋Š” ํ…์ŠคํŠธ์™€ ๊ฒน์น˜์ง€ ์•Š๋Š” ์นด๋“œ ์˜ค๋ฅธ์ชฝ ์•„๋ž˜์— ๋‘”๋‹ค. + +์ด์— ๋”ฐ๋ผ ๋ชฉ๋ก ํญ์€ 400px ๊ทธ๋Œ€๋กœ์ด๊ณ  ๋ณธ๋ฌธ ์˜ค๋ฅธ์ชฝ ์—ฌ๋ฐฑ์€ 104px์—์„œ 12px๋กœ +์ค„์—ˆ๋‹ค. ์ œ๋ชฉยท์š”์•ฝยท์ธ๋„ค์ผ์ด ํ•˜๋‚˜์˜ ๊ฐ€๋กœ Flex ํ–‰์„ ๊ณต์œ ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ๊ธด ํ•œ๊ธ€ ์ œ๋ชฉ๊ณผ +์š”์•ฝ์„ ์—ฐ์†ํ•ด์„œ ์ฝ์„ ์ˆ˜ ์žˆ๋‹ค. + +ํ† ๋ก  ์ž‘์„ฑ๊ธฐ์—์„œ๋Š” Font Awesome ์›นํฐํŠธ ๋ถ€์žฌ ์‹œ ๋ฏธ๋ฆฌ๋ณด๊ธฐยท๊ตต๊ฒŒยท๊ธฐ์šธ์ž„ยท์ฝ”๋“œยท์ธ์šฉยท +๋งํฌยท์ด๋ฏธ์ง€ยท๊ธ€๋จธ๋ฆฌ/๋ฒˆํ˜ธ ๋ชฉ๋ก ๋“ฑ์˜ ์•„์ด์ฝ˜์ด `P` ๋˜๋Š” ๊ฒ€์€ ์‚ฌ๊ฐํ˜•์œผ๋กœ ๋ณด์˜€๋‹ค. +๋ฏธ๋ฆฌ๋ณด๊ธฐ์™€ ๊ธฐ๋ณธ ์„œ์‹ 10์ข…, ์ถ”๊ฐ€ ์„œ์‹ 6์ข…์„ ๊ธฐ์กด ๋‚ด์žฅ SVG Mask ์ฒด๊ณ„์— ์ถ”๊ฐ€ํ–ˆ๋‹ค. +WSL๊ณผ ์šด์˜ ์ž‘์„ฑ๊ธฐ์—์„œ ๊ฐ ์•„์ด์ฝ˜์€ 15~16px ํฌ๊ธฐ์™€ ์‹ค์ œ Mask URL์„ ๊ฐ€์ง€๋ฉฐ ๊นจ์ง„ +๋Œ€์ฒด ๋ฌธ์ž๋Š” 0๊ฑด์ด์—ˆ๋‹ค. + +ํ…Œ๋งˆ ๊ณ„์•ฝ ํ…Œ์ŠคํŠธ 13๊ฑด๊ณผ WSL์˜ `issue-73-drawer-icons-v3` ์ ์šฉยท๋น„ํ™œ์„ฑํ™”ยท์žฌ์ ์šฉ +์ฃผ๊ธฐ๋ฅผ ํ†ต๊ณผํ–ˆ๋‹ค. ์šด์˜ Source์™€ Vendor LESS SHA-256์€ ๋ชจ๋‘ +`6bcbbebba32f0ad2a49f30c66820c8891a93491d25a4e389242aaa10f2025551`๋กœ +์ผ์น˜ํ•œ๋‹ค. ์ตœ์‹  ์šด์˜ ๋ฐฑ์—…์€ +`/var/backups/techflow-flarum/theme-drawer-readability-icons-20260820T1045KST`์ด๋ฉฐ, +์„œ๋ฒ„ ๋กœ์ปฌ HTTP 200๊ณผ NginxยทPHP-FPMยทMariaDBยทCommunity Monitor Timer `active`๋ฅผ +ํ™•์ธํ–ˆ๋‹ค. + +### ํ† ๋ก  ๋ชฉ๋ก ๋ ˆ์ด์–ด ๋ณต์›๊ณผ ์ž‘์„ฑ๊ธฐ ์ถฉ๋Œ ๋ฐฉ์ง€ + +๋ชฉ๋ก์—์„œ ์ƒ์„ธ ํ™”๋ฉด์„ ์—ด ๋•Œ ํ•ญ์ƒ ์ฒซ ๊ฒŒ์‹œ๋ฌผ๋ถ€ํ„ฐ ๋ณด์ด๋„๋ก ๋งŒ๋“  ๊ธฐ์กด ์ฒ˜๋ฆฌ๊ฐ€ +`window.location.assign()`์œผ๋กœ ์ „์ฒด ํŽ˜์ด์ง€ ์ด๋™์„ ๊ฐ•์ œํ•˜๊ณ  ์žˆ์—ˆ๋‹ค. ์ด ๊ณผ์ •์—์„œ +Flarum์ด ๋ฉ”๋ชจ๋ฆฌ์— ๋ณด๊ด€ํ•˜๋˜ ํ† ๋ก  ๋ชฉ๋ก ์ƒํƒœ๊ฐ€ ์‚ฌ๋ผ์ ธ ํ™”๋ฉด ์™ผ์ชฝ ๊ฐ€์žฅ์ž๋ฆฌ์— ๋งˆ์šฐ์Šค๋ฅผ +๊ฐ€์ ธ๊ฐ€๋„ 400px ํ† ๋ก  ๋ชฉ๋ก ๋ ˆ์ด์–ด๊ฐ€ ๋งŒ๋“ค์–ด์ง€์ง€ ์•Š์•˜๋‹ค. + +์ „์ฒด ํŽ˜์ด์ง€ ์ด๋™์„ ์ œ๊ฑฐํ•˜๊ณ  Flarum์˜ SPA ํ™”๋ฉด ์ „ํ™˜์„ ์œ ์ง€ํ•˜๋„๋ก ๋ณ€๊ฒฝํ–ˆ๋‹ค. ๋งํฌ๋Š” +์ฒซ ๊ฒŒ์‹œ๋ฌผ ๊ฒฝ๋กœ๋กœ ์ •๊ทœํ™”ํ•˜๋˜ ๊ธฐ๋ณธ ํด๋ฆญ ๋™์ž‘์„ ๋ง‰์ง€ ์•Š๊ณ , ํ™”๋ฉด ์ „ํ™˜ ์งํ›„ ์Šคํฌ๋กค๋งŒ +0์œผ๋กœ ๋งž์ถ˜๋‹ค. ๋ชฉ๋ก์—์„œ ์ƒ์„ธ๋กœ ์ด๋™ํ•œ WSLยท์šด์˜ ๋ธŒ๋ผ์šฐ์ €์—์„œ ๊ฐ๊ฐ 20๊ฐœ ํ•ญ๋ชฉ์˜ +`DiscussionPage-list`๊ฐ€ ๋‹ค์‹œ ์ƒ์„ฑ๋˜๊ณ  `scrollY=0`์„ ํ™•์ธํ–ˆ๋‹ค. ์ƒ์„ธ ํ™”๋ฉด์„ ์ง์ ‘ +์ƒˆ๋กœ๊ณ ์นจํ•ด๋„ ์ง์ „์— ํ‘œ์‹œํ•œ ๋ชฉ๋ก Snapshot์œผ๋กœ ๋ ˆ์ด์–ด๋ฅผ ๋ณต์›ํ•˜๋ฉฐ ํ˜„์žฌ ํ† ๋ก  1๊ฑด์„ +ํ™œ์„ฑ ์ƒํƒœ๋กœ ํ‘œ์‹œํ•œ๋‹ค. + +์ž‘์„ฑ๊ธฐ ํ•˜๋‹จ์€ Flarum์˜ 300px Inline ๋†’์ด๋ณด๋‹ค ๋‚ด๋ถ€ ํŽธ์ง‘๊ธฐ์™€ Toolbar๊ฐ€ ๋” ๊ธธ์–ด +๊ฒŒ์‹œ ๋ฒ„ํŠผ์ด ํ™”๋ฉด ํ•˜๋‹จ์— ๊ฑธ๋ ธ๋‹ค. ๋ฐ์Šคํฌํ†ฑ ํ™œ์„ฑ ์ž‘์„ฑ๊ธฐ์˜ ์ตœ์†Œ ๋†’์ด๋ฅผ 336px๋กœ +ํ™•๋ณดํ•˜๊ณ  Toolbar๋ฅผ ์•ˆ์ชฝ์œผ๋กœ ๋ฐฐ์น˜ํ–ˆ๋‹ค. ํŽธ์ง‘๊ธฐ ํ•˜๋‹จ ์—ฌ๋ฐฑ๋„ ๋Š˜๋ ค ๋งˆ์ง€๋ง‰ ๋ฌธ์žฅ์ด +Toolbar ์•„๋ž˜์— ๊ฐ€๋ ค์ง€์ง€ ์•Š๋„๋ก ํ–ˆ๋‹ค. + +ํ† ๋ก  ๋ชฉ๋ก ๋ ˆ์ด์–ด๊ฐ€ ์—ด๋ฆฐ ์ƒํƒœ์—์„œ ์ƒˆ ํ† ๋ก ์ด๋‚˜ ๋‹ต๋ณ€ ์ž‘์„ฑ๊ธฐ๋ฅผ ์—ด๋ฉด ๋‘ ํ™”๋ฉด์ด +๊ฒน์ณค๋‹ค. ์ž‘์„ฑ๊ธฐ๊ฐ€ `visible`์ธ ๋™์•ˆ ๋ชฉ๋ก ๋ ˆ์ด์–ด๋งŒ ์ผ์‹œ์ ์œผ๋กœ ์ˆจ๊ธฐ๊ณ , ๊ณ ์ •๋œ Pane์ด +์ž‘์„ฑ๊ธฐ ํญ์„ ๋ฐ€์ง€ ์•Š๋„๋ก Composer Container์˜ ์™ผ์ชฝ ๊ฐ’์„ 0์œผ๋กœ ๋ณต์›ํ–ˆ๋‹ค. ์ž‘์„ฑ๊ธฐ๋ฅผ +๋‹ซ์œผ๋ฉด ๋ชฉ๋ก ๋ ˆ์ด์–ด์™€ ๊ณ ์ • ์ƒํƒœ๊ฐ€ ๋‹ค์‹œ ํ‘œ์‹œ๋œ๋‹ค. + +| ๋ธŒ๋ผ์šฐ์ € ๊ฒ€์ฆ ํ•ญ๋ชฉ | WSL | ์šด์˜ | +|---|---:|---:| +| ๋ณต์›๋œ ๋ชฉ๋ก ํญยทํ•ญ๋ชฉ ์ˆ˜ | 400px ยท 20๊ฑด | 400px ยท 20๊ฑด | +| ๋ชฉ๋ก ํด๋ฆญ ํ›„ ์ƒ์„ธ `scrollY` | 0 | 0 | +| ์ƒˆ๋กœ๊ณ ์นจ ํ›„ ํ˜„์žฌ ํ† ๋ก  ํ™œ์„ฑ ํ•ญ๋ชฉ | 1๊ฑด | 1๊ฑด | +| ํ™œ์„ฑ ์ž‘์„ฑ๊ธฐ ๋†’์ด | 336px | 336px | +| ๊ฒŒ์‹œ ๋ฒ„ํŠผ ํ•˜๋‹จ ์—ฌ๋ฐฑ | 22px | 22px | +| ์ž‘์„ฑ๊ธฐ Footer ํ•˜๋‹จ ์—ฌ๋ฐฑ | 8px | 8px | +| ์ž‘์„ฑ๊ธฐ์™€ ๋ชฉ๋ก ๊ฒน์นจ | ์—†์Œ | ์—†์Œ | + +ํ…Œ๋งˆ ๊ณ„์•ฝ ํ…Œ์ŠคํŠธ 13๊ฑด๊ณผ WSL `issue-73-composer-pane-v8` ์ ์šฉยท๋น„ํ™œ์„ฑํ™”ยท์žฌ์ ์šฉ +์ฃผ๊ธฐ๋ฅผ ํ†ต๊ณผํ–ˆ๋‹ค. ์šด์˜ ๋ฐฐํฌ ์ „ ๋ฐฑ์—…์€ +`/var/backups/techflow-flarum/theme-composer-pane-final-20260820T112831KST`์— +๋ณด์กดํ–ˆ๋‹ค. ์šด์˜ Source์™€ Vendor LESS SHA-256์€ +`3fc9dda37cc2e7e78024d470e122a1b883d023de658c973be9467e40359d0ca9`, JS SHA-256์€ +`a52c0bc148280a01f8cbce9a1ce79a28be6d7529bba7591ea1a6a9ed042dfe62`๋กœ ์ผ์น˜ํ•œ๋‹ค. +์™ธ๋ถ€ HTTPS 200๊ณผ NginxยทPHP-FPMยทMariaDBยทCommunity Monitor Timer `active`๋ฅผ +ํ™•์ธํ–ˆ์œผ๋ฉฐ DB Schema, ๊ฒŒ์‹œ๋ฌผ, ์ฒจ๋ถ€, TechFlow AI, GitHubโ†’Chat Webhook์€ ๋ณ€๊ฒฝํ•˜์ง€ +์•Š์•˜๋‹ค. + +### ์ขŒ์ธก ๋ชฉ๋ก ๋ฉ”๋‰ด์™€ ํ† ๋ก  ์ž‘์„ฑ๊ธฐ ๋ ˆ์ด์–ด ์ตœ์ข… ๊ต์ • + +์ข์€ 400px ํ† ๋ก  ๋ชฉ๋ก์˜ ๋”๋ณด๊ธฐ ๋ฉ”๋‰ด๊ฐ€ ์นด๋“œ ์œ„๋กœ ํŠ€์–ด๋‚˜์˜จ ์›์ธ์€ ๋ฐ์Šคํฌํ†ฑ ๋ชฉ๋ก์˜ +`top: 50%`์™€ `translateY(-50%)`๊ฐ€ ๊ทธ๋Œ€๋กœ ์ƒ์†๋œ ๊ฒƒ์ด์—ˆ๋‹ค. ์ข์€ ๋ชฉ๋ก์—์„œ๋Š” ๋ฉ”๋‰ด๋ฅผ +์นด๋“œ ์ƒ๋‹จ 10px, ์˜ค๋ฅธ์ชฝ 10px์— ๋ณ„๋„๋กœ ๊ณ ์ •ํ•˜๊ณ  ๋ฒ„ํŠผ ์˜์—ญ์„ 32ร—32px๋กœ ํ†ต์ผํ–ˆ๋‹ค. +WSL๊ณผ ์šด์˜ ๋ธŒ๋ผ์šฐ์ € ๋ชจ๋‘ ์นด๋“œ ์‹ค์ œ ๊ฒฝ๊ณ„์—์„œ ๋ฉ”๋‰ด ์ƒ๋‹จ๊นŒ์ง€ 11px์ด๋ฉฐ ๋ฒ„ํŠผ ์ „์ฒด๊ฐ€ +์นด๋“œ ์•ˆ์— ํฌํ•จ๋˜๊ณ  Transform์€ `none`์ด๋‹ค. + +ํ† ๋ก  ์ƒ์„ฑ ์ž‘์„ฑ๊ธฐ์—์„œ๋Š” ๊ธฐ์กด ๋ณด์™„ CSS๊ฐ€ ํ•˜๋‹จ ๋„๊ตฌ๋ง‰๋Œ€๋ฅผ 20px ์œ„๋กœ ์ด๋™์‹œ์ผœ ๋ณธ๋ฌธ +ํŽธ์ง‘ ์˜์—ญ๊ณผ ์‹ค์ œ๋กœ ๊ฒน์ณค๋‹ค. ์ž‘์„ฑ๊ธฐ ๋‚ด๋ถ€๋ฅผ ์„ธ๋กœ Flex ๊ตฌ์กฐ๋กœ ๋ฐ”๊พธ๊ณ  ํŽธ์ง‘ ์˜์—ญ๊ณผ +๋„๊ตฌ๋ง‰๋Œ€๋ฅผ ๊ฐ๊ฐ ๋…๋ฆฝ ํ–‰์œผ๋กœ ๋ฐฐ์น˜ํ–ˆ๋‹ค. ๋‘ ์˜์—ญ์˜ ๊ฒฝ๊ณ„ ๊ฐ„๊ฒฉ์€ 0px์ด์ง€๋งŒ ๊ต์ฐจ ์˜์—ญ์€ +์—†์œผ๋ฉฐ, 65.5px ๋„๊ตฌ๋ง‰๋Œ€ ์ „์ฒด๊ฐ€ ์ž‘์„ฑ๊ธฐ ํ•˜๋‹จ์—์„œ 15.5px ์•ˆ์ชฝ์— ๋‚จ๋Š”๋‹ค. ๊ฒŒ์‹œ ๋ฒ„ํŠผ์€ +์ž‘์„ฑ๊ธฐ ํ•˜๋‹จ์—์„œ 29.5px ๋–จ์–ด์ ธ ์žˆ๋‹ค. + +์ž‘์„ฑ๊ธฐ์™€ ๋ชฉ๋ก์ด ๊ฒน์น˜๋˜ ๋ฌธ์ œ๋Š” ๋‘ ๊ฒฝ๋กœ๋ฅผ ํ•จ๊ป˜ ์ฐจ๋‹จํ–ˆ๋‹ค. JavaScript๊ฐ€ ์ƒˆ ํ† ๋ก  ๋˜๋Š” +๋‹ต๋ณ€ ์ž‘์„ฑ๊ธฐ์˜ `visible` ์ƒํƒœ๋ฅผ `ablecloud-composer-open` ํด๋ž˜์Šค๋กœ ๋™๊ธฐํ™”ํ•˜๊ณ  ๊ธฐ์กด +Pane Controller๋ฅผ ์ฆ‰์‹œ ๋‹ซ๋Š”๋‹ค. ๋™์‹œ์— ์ž‘์„ฑ ์ค‘์ธ ํ™”๋ฉด์˜ ํ† ๋ก  ํ–‰์€ Hover ์ƒํƒœ์—ฌ๋„ +Z-index๋ฅผ 0์œผ๋กœ ์œ ์ง€ํ•ด ๋ฐฐ๊ฒฝ ๋ชฉ๋ก์ด ์ž‘์„ฑ๊ธฐ ์•ž์œผ๋กœ ์˜ฌ๋ผ์˜ค์ง€ ๋ชปํ•œ๋‹ค. ๋ชฉ๋ก์„ ๋จผ์ € +๊ณ ์ •ํ•ด ํ™”๋ฉด์— ํ‘œ์‹œํ•œ ๋’ค ๋‹ต๋ณ€ ์ž‘์„ฑ๊ธฐ๋ฅผ ์—ฐ ์šด์˜ ๋ธŒ๋ผ์šฐ์ € ๊ฒ€์ฆ์—์„œ๋„ Pane์€ +`display:none`, ์ž‘์„ฑ๊ธฐ์™€์˜ ๊ต์ฐจ๋Š” `false`์˜€๋‹ค. + +| ๋ธŒ๋ผ์šฐ์ € ๊ฒ€์ฆ ํ•ญ๋ชฉ | WSL | ์šด์˜ | +|---|---:|---:| +| ๋ชฉ๋ก ๋ฉ”๋‰ด ํฌ๊ธฐ | 32ร—32px | 32ร—32px | +| ์นด๋“œ ์ƒ๋‹จ ๊ธฐ์ค€ ๋ฉ”๋‰ด ์œ„์น˜ | 11px | 11px | +| ๋ฉ”๋‰ด๊ฐ€ ์นด๋“œ ๋‚ด๋ถ€์— ํฌํ•จ | ์˜ˆ | ์˜ˆ | +| ์ž‘์„ฑ๊ธฐ ๋†’์ด | 336px | 336px | +| ํŽธ์ง‘ ์˜์—ญ / ๋„๊ตฌ๋ง‰๋Œ€ ๋†’์ด | 180 / 65.5px | 180 / 65.5px | +| ํŽธ์ง‘ ์˜์—ญ๊ณผ ๋„๊ตฌ๋ง‰๋Œ€ ๊ฒน์นจ | ์—†์Œ | ์—†์Œ | +| ๋„๊ตฌ๋ง‰๋Œ€ ํ•˜๋‹จ ์—ฌ๋ฐฑ | 15.5px | 15.5px | +| ๋ชฉ๋ก์„ ๋จผ์ € ์—ฐ ๋’ค ์ž‘์„ฑ๊ธฐ ๊ฒน์นจ | ์—†์Œ | ์—†์Œ | + +๊ณ„์•ฝ ํ…Œ์ŠคํŠธ 13๊ฑด๊ณผ WSL +`issue-73-composer-layout-flex-20260820` ์ ์šฉยท๋น„ํ™œ์„ฑํ™”ยท์žฌ์ ์šฉ ์ฃผ๊ธฐ๋ฅผ ํ†ต๊ณผํ–ˆ๋‹ค. +์šด์˜ ๋ฐฐํฌ ์ „ ๋ฐฑ์—…์€ +`/var/backups/techflow-flarum/theme-composer-pane-layer-final-20260820T122747KST`์— +๋ณด์กดํ–ˆ๋‹ค. ์šด์˜ Source์™€ Vendor LESS SHA-256์€ +`b13615bd81ac3f794771969e2f97e7cf0bed1cdd02098266b120ac7019f62da1`, JS SHA-256์€ +`7636c2790502f03bf148422baec98b6f76c4d77519d5dcdf783469eff914c643`๋กœ ์ผ์น˜ํ•œ๋‹ค. +์™ธ๋ถ€ HTTPS ๋ธŒ๋ผ์šฐ์ € ์ ‘์†, NginxยทPHP-FPMยทMariaDBยทCommunity Monitor Timer +`active`๋ฅผ ํ™•์ธํ–ˆ์œผ๋ฉฐ ์›๊ฒฉ ์ž„์‹œ ๋ฐฐํฌ ์ž์‚ฐ์€ ์ œ๊ฑฐํ–ˆ๋‹ค. + +### ๋ฌดํ•œ ์Šคํฌ๋กคยทํ•œ๊ธ€ ์ด๋ชจ์ง€ยท์ƒˆ ํ† ๋ก  ์ค‘์•™ ๋Œ€ํ™”์ƒ์ž + +๋ชฉ๋ก ๋์˜ `๋” ๋ณด๊ธฐ` ๋ฒ„ํŠผ์„ ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ๋ˆ„๋ฅด๋˜ ํ๋ฆ„์„ ๋ณ€๊ฒฝํ–ˆ๋‹ค. ๋ชฉ๋ก ์Šคํฌ๋กค +์˜์—ญ์„ ๊ธฐ์ค€์œผ๋กœ ํ•˜๋‹จ 120px ์•ˆ์— ๊ธฐ์กด Flarum ๋กœ๋”ฉ ๋ฒ„ํŠผ์ด ๋“ค์–ด์˜ค๋ฉด +`IntersectionObserver`๊ฐ€ ๊ธฐ์กด ๋ฒ„ํŠผ ๋™์ž‘์„ ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰ํ•œ๋‹ค. Flarum์˜ ํŽ˜์ด์ง€๋„ค์ด์…˜๊ณผ +์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ๋Š” ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๋ฉฐ, `IntersectionObserver`๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋Š” ๋ธŒ๋ผ์šฐ์ €์—๋Š” +๊ธฐ์กด ๋ฒ„ํŠผ์„ ํด๋ฐฑ์œผ๋กœ ๋‚จ๊ฒผ๋‹ค. WSL๊ณผ ์šด์˜ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ˆ˜๋™ ๋ฒ„ํŠผ ํด๋ฆญ ์—†์ด ํ† ๋ก  +์ˆ˜๊ฐ€ ๊ฐ๊ฐ `20 โ†’ 40 โ†’ 60`์œผ๋กœ ์ฆ๊ฐ€ํ–ˆ๋‹ค. ๊ฐ™์€ DOM ์†์„ฑ์„ ๋ฐ˜๋ณต ๊ธฐ๋กํ•ด ํ™”๋ฉด์ด +์ง€์—ฐ๋˜๋˜ ์ดˆ๊ธฐ ๊ตฌํ˜„๋„ ์‹ค์ œ ์šด์˜ ๋ธŒ๋ผ์šฐ์ € ๊ฒ€์ฆ์—์„œ ๋ฐœ๊ฒฌํ•ด, ๊ฐ’์ด ๋ฐ”๋€” ๋•Œ๋งŒ ํ‘œ์‹์„ +๊ธฐ๋กํ•˜๋„๋ก ๊ต์ •ํ–ˆ๋‹ค. + +๊ธฐ์กด ์ด๋ชจ์ง€ ํ™•์žฅ์€ `regional_indicator_a` ๊ฐ™์€ ๋‚ด๋ถ€ ์˜๋ฌธ ์ด๋ฆ„์„ ๋จผ์ € ๋…ธ์ถœํ•˜๊ณ  +๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ์ฝœ๋ก ๋งŒ ์ž…๋ ฅํ•ด ์‚ฌ์šฉ๋ฒ•์„ ์•Œ๊ธฐ ์–ด๋ ค์› ๋‹ค. ์ƒˆ ์„ ํƒ๊ธฐ๋Š” ์ž์ฃผ ์“ฐ๋Š” Unicode +์ด๋ชจ์ง€ 24๊ฐœ๋ฅผ ํ•œ๊ธ€ ์ ‘๊ทผ์„ฑ ์ด๋ฆ„๊ณผ ํ•จ๊ป˜ ์ œ๊ณตํ•œ๋‹ค. ์šด์˜์—์„œ `๋ฏธ์†Œ` ํ•ญ๋ชฉ์„ ์„ ํƒํ•ด +ํŽธ์ง‘๊ธฐ์— `์ด๋ชจ์ง€ ์‚ฝ์ž… ํ™•์ธ ๐Ÿ˜Š `๊ฐ€ ๋“ค์–ด๊ฐ€๊ณ  ์„ ํƒ๊ธฐ๊ฐ€ ๋‹ซํžˆ๋Š” ๊ฒƒ๊นŒ์ง€ ํ™•์ธํ–ˆ๋‹ค. ๊ธฐ์กด +์˜๋ฌธ ๋‚ด๋ถ€ ์ด๋ฆ„๊ณผ ์›๋ž˜ Emoji Dropdown์€ ํ‘œ์‹œ๋˜์ง€ ์•Š์•˜๋‹ค. + +์ƒˆ ํ† ๋ก  ์ž‘์„ฑ๊ธฐ๋Š” ๊ธฐ์กด ํ™”๋ฉด ํ•˜๋‹จ ๋ ˆ์ด์–ด๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค. ์ƒˆ ํ† ๋ก ์—๋งŒ ์ „์ฒด ํ™”๋ฉด +๋ฐฐ๊ฒฝ ์ฐจ๋‹จ ๋ ˆ์ด์–ด์™€ ์ค‘์•™ ๋Œ€ํ™”์ƒ์ž๋ฅผ ์ ์šฉํ•˜๊ณ , ์ƒ์„ธ ํ™”๋ฉด์˜ ๋‹ต์žฅ ์ž‘์„ฑ๊ธฐ๋Š” ๊ธฐ์กด Flarum +ํ๋ฆ„์„ ์œ ์ง€ํ•œ๋‹ค. ์šด์˜ 1680ร—881 ํ™”๋ฉด์—์„œ ๋Œ€ํ™”์ƒ์ž๋Š” X 410.875, Y 206.5, +843.25ร—520px์ด๋ฉฐ ํŽธ์ง‘ ์˜์—ญ์€ 376.5px, ํ•˜๋‹จ ๋„๊ตฌ๋ง‰๋Œ€๋Š” 65.5px๋กœ ์„œ๋กœ ๊ฒน์น˜์ง€ +์•Š์•˜๋‹ค. ์ตœ์†Œํ™”์™€ ์ „์ฒด ํ™”๋ฉด ๋ฒ„ํŠผ์€ ์ˆจ๊ธฐ๊ณ  ๋‹ซ๊ธฐ ๋ฒ„ํŠผ๋งŒ ์œ ์ง€ํ–ˆ๋‹ค. Flarum์ด ๊ธฐ์กด +์ž‘์„ฑ๊ธฐ DOM์˜ Class๋ฅผ ๋Šฆ๊ฒŒ ๋ฐ”๊พธ๋Š” ๊ฒฝ์šฐ๋„ ์ตœ์ดˆ ํด๋ฆญ ํ›„ ์ž๋™์œผ๋กœ ์ƒํƒœ๋ฅผ ๋‹ค์‹œ +๋™๊ธฐํ™”ํ•˜๋„๋ก ๋ณด์™„ํ–ˆ๋‹ค. + +ํ…Œ๋งˆ ๊ณ„์•ฝ ํ…Œ์ŠคํŠธ 14๊ฑด๊ณผ WSL `issue-73-browser-final-20260820` ์ ์šฉยท๋น„ํ™œ์„ฑํ™”ยท +์žฌ์ ์šฉ ์ฃผ๊ธฐ๊ฐ€ ํ†ต๊ณผํ–ˆ๋‹ค. ์šด์˜ ์ตœ์ข… ๋ฐฑ์—…์€ +`/var/backups/techflow-flarum/theme-infinite-emoji-dialog-final-20260820T1430KST`์— +๋ณด์กดํ–ˆ๋‹ค. ์šด์˜ Vendor JS SHA-256์€ +`977a9bf06026ab4ff9ec8d5348fcf07edcaa58d38d7abaf94a11114d0a586779`, LESS๋Š” +`1aff8b1ec4afb8225f960e3cb71666f2707c527b8222239d064df36b9046672b`์ด๋ฉฐ WSL +Source์™€ ์ผ์น˜ํ•œ๋‹ค. NginxยทPHP-FPMยทMariaDB๋Š” ๋ชจ๋‘ `active`, ์„œ๋ฒ„ ๋กœ์ปฌ HTTP๋Š” +200์ด๋‹ค. DB Schema, ๊ฒŒ์‹œ๋ฌผ, ์ฒจ๋ถ€, TechFlow AI์™€ GitHubโ†’Chat Webhook์€ +๋ณ€๊ฒฝํ•˜์ง€ ์•Š์•˜์œผ๋ฉฐ ์ž„์‹œ ๋ฐฐํฌ ํŒŒ์ผ์€ ์ œ๊ฑฐํ–ˆ๋‹ค. + +### ๊ฒ€์ƒ‰ ์ž…๋ ฅ ์•„์ด์ฝ˜๊ณผ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ๊ฐ€๋…์„ฑ ๊ต์ • + +`v2k` ๊ฒ€์ƒ‰์„ ์šด์˜ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์žฌํ˜„ํ•œ ๊ฒฐ๊ณผ ๋ฐ์ดํ„ฐ ์†์ƒ์€ ์—†์—ˆ๋‹ค. ํ•ด๊ฒฐ๋œ ํ† ๋ก  +๊ฒ€์ƒ‰ ํ™•์žฅ ๊ธฐ๋Šฅ์ด ํƒœ๊ทธยท์ œ๋ชฉยท์งˆ๋ฌธ ์š”์•ฝยท์„ ํƒ ๋‹ต๋ณ€ ์š”์•ฝ์„ ๊ธฐ์กด 400px ๊ฒ€์ƒ‰ ๋ฉ”๋‰ด์—์„œ +๊ฐ€๋กœ Flex ํ–‰์œผ๋กœ ๋ฐฐ์น˜ํ•ด ๊ฐ ์˜์—ญ์ด 51~115px๋กœ ์ค„์–ด๋“  ๊ฒƒ์ด ๊ธ€์ž๊ฐ€ ํ•œ๋‘ ์ž์”ฉ +๋Š๊ธฐ๋˜ ์›์ธ์ด์—ˆ๋‹ค. ๊ฒ€์ƒ‰ ์ž…๋ ฅ์˜ ์ ‘๋‘ ์•„์ด์ฝ˜์€ ์šด์˜์ฒด์ œ ๊ธฐํ˜ธ ๊ธ€๊ผด์— ์˜์กดํ–ˆ๊ณ , +๊ฒ€์ƒ‰ ๋น„์šฐ๊ธฐ ๋ฒ„ํŠผ์˜ `fa-times-circle`์€ ํ…Œ๋งˆ SVG ๋ชฉ๋ก์— ์—†์–ด ์‚ฌ๊ฐํ˜•์œผ๋กœ ํ‘œ์‹œ๋๋‹ค. + +๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ํ•œ ๊ฑด์„ `ํƒœ๊ทธ โ†’ ์ œ๋ชฉ โ†’ ์งˆ๋ฌธ ์š”์•ฝ โ†’ ์„ ํƒ ๋‹ต๋ณ€ ์š”์•ฝ`์˜ ๋‹จ์ผ ์„ธ๋กœ ํ๋ฆ„์œผ๋กœ +์žฌ๊ตฌ์„ฑํ–ˆ๋‹ค. ํŒจ๋„์€ ๋ฐ์Šคํฌํ†ฑ ์ตœ๋Œ€ 560px, ๋ชจ๋ฐ”์ผ์€ ํ™”๋ฉด ์–‘์ชฝ 12px ์—ฌ๋ฐฑ์„ ์‚ฌ์šฉํ•˜๋ฉฐ, +์งˆ๋ฌธ๊ณผ ์„ ํƒ ๋‹ต๋ณ€ ์š”์•ฝ์€ ๊ฐ๊ฐ ๋‘ ์ค„๊นŒ์ง€๋งŒ ํ‘œ์‹œํ•œ๋‹ค. ๊ธด ํ•œ๊ธ€์€ ๋‹จ์–ด ๋‹จ์œ„๋กœ ์ค„๋ฐ”๊ฟˆํ•˜๊ณ  +๊ฒฐ๊ณผ ๋‚ด๋ถ€ ์š”์†Œ๋Š” ์„œ๋กœ ๊ฒน์น˜์ง€ ์•Š๋Š”๋‹ค. ๊ฒ€์ƒ‰๊ณผ ๋น„์šฐ๊ธฐ ์•„์ด์ฝ˜์€ ๋ชจ๋‘ ๋‚ด์žฅ SVG Mask๋กœ +๋ฐ”๊ฟ” ์™ธ๋ถ€ Font Awesome ๋˜๋Š” ์šด์˜์ฒด์ œ ๊ธฐํ˜ธ ๊ธ€๊ผด์— ์˜์กดํ•˜์ง€ ์•Š๋Š”๋‹ค. + +| ๋ธŒ๋ผ์šฐ์ € ๊ฒ€์ฆ ํ•ญ๋ชฉ | WSL | ์šด์˜ | +|---|---:|---:| +| ๊ฒ€์ƒ‰์–ด | `v2k` | `v2k` | +| ๊ฒฐ๊ณผ ํŒจ๋„ ํญ | 560px | 560px | +| ํ•ด๊ฒฐ๋œ ํ† ๋ก  ๊ฒฐ๊ณผ | 1๊ฑด | 2๊ฑด | +| ํƒœ๊ทธยท์ œ๋ชฉยท์š”์•ฝยท๋‹ต๋ณ€ ๊ฒน์นจ | ์—†์Œ | ์—†์Œ | +| ๊ฒ€์ƒ‰ SVG Mask | ์ •์ƒ | ์ •์ƒ | +| ๋น„์šฐ๊ธฐ SVG Mask | ์ •์ƒ | ์ •์ƒ | + +๊ณ„์•ฝ ํ…Œ์ŠคํŠธ 14๊ฑด๊ณผ WSL `search-ui-20260820-r3` ์ ์šฉยท๋น„ํ™œ์„ฑํ™”ยท์žฌ์ ์šฉ ์ฃผ๊ธฐ๋ฅผ +ํ†ต๊ณผํ–ˆ๋‹ค. ์šด์˜ ๋ฐฐํฌ ์ „ ๋ฐฑ์—…์€ +`/var/backups/techflow-flarum/theme-search-ui-20260820`์— ๋ณด์กดํ–ˆ๋‹ค. ์šด์˜ Source์™€ +Vendor LESS SHA-256์€ ๋ชจ๋‘ +`61598a1d4be2c48b21cf839546546f5c036c545c580b8d54f763f17127b662b5`๋กœ ์ผ์น˜ํ•˜๋ฉฐ, +NginxยทPHP-FPMยทMariaDB๋Š” `active`, ์„œ๋ฒ„ ๋กœ์ปฌ HTTP๋Š” 200์ด๋‹ค. DB Schema, ๊ฒŒ์‹œ๋ฌผ, +์ฒจ๋ถ€, TechFlow AI์™€ GitHubโ†’Chat Webhook์€ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š์•˜๊ณ  ์›๊ฒฉ ์ž„์‹œ ํŒŒ์ผ์€ +์ œ๊ฑฐํ–ˆ๋‹ค. + +### ํƒœ๊ทธยทํ”„๋กœํ•„ยท์„ค์ •ยท๋ณด์•ˆ ํ™”๋ฉด ํญ ํ†ต์ผ + +์šด์˜ ๋ธŒ๋ผ์šฐ์ €์˜ 1,280ร—720 ํ™”๋ฉด์—์„œ ํ† ๋ก  ๋ชฉ๋ก์€ Header์™€ Page๊ฐ€ 1,165px์ธ๋ฐ, +ํƒœ๊ทธยทํ”„๋กœํ•„ยท์„ค์ •ยท๋ณด์•ˆ ํ™”๋ฉด์€ Flarum ๊ธฐ๋ณธ 1,100px Container๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์—ˆ๋‹ค. +์ด ๋•Œ๋ฌธ์— ๋™์ผํ•œ Header๋ฅผ ์‚ฌ์šฉํ•ด๋„ ์ขŒ์šฐ ๋์ด ๊ฐ๊ฐ 32.5px ์•ˆ์ชฝ์œผ๋กœ ๋“ค์–ด๊ฐ€ ํ™”๋ฉด์„ +์ด๋™ํ•  ๋•Œ ํญ์ด ๋‹ฌ๋ผ ๋ณด์˜€๋‹ค. + +๊ณตํ†ต ๋ฐ์Šคํฌํ†ฑ ์…ธ์„ 1,165px๋กœ ์ •์˜ํ•˜๊ณ  Header, Welcome/Tag/User Hero, TagsPage, +UserPage์— ๋™์ผํ•˜๊ฒŒ ์ ์šฉํ–ˆ๋‹ค. ๋‹จ์ˆœํžˆ ๋‚ด๋ถ€ ์ฝ˜ํ…์ธ ๋ฅผ ๋„“ํžˆ์ง€ ์•Š๊ณ  ์˜ค๋ฅธ์ชฝ 80px์„ +๊ณ ์ • ํ€ต ๋งํฌ ์˜์—ญ์œผ๋กœ ์˜ˆ์•ฝํ•ด ๊ธฐ์กด 1,070px ๋‚ด๋ถ€ ํญ์„ ์œ ์ง€ํ–ˆ๋‹ค. ๊ทธ ๊ฒฐ๊ณผ ํƒœ๊ทธ์™€ +์‚ฌ์šฉ์ž ํ™”๋ฉด์˜ ์ฝ˜ํ…์ธ  ์˜ค๋ฅธ์ชฝ ๋์€ ๋ชฉ๋ก๊ณผ ๊ฐ™์€ X 1,135px์ด๊ณ , ํ€ต ๋งํฌ ์‹œ์ž‘์  +X 1,143.5px๋ณด๋‹ค ์™ผ์ชฝ์— ์žˆ์–ด ๊ฒน์น˜์ง€ ์•Š๋Š”๋‹ค. 768px ๋ฏธ๋งŒ ํ™”๋ฉด์—๋Š” ์ด ๊ทœ์น™์„ +์ ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค. + +| ์šด์˜ ๋ธŒ๋ผ์šฐ์ € ๊ฒ€์ฆ ํ™”๋ฉด | Header | Hero | Page | ์ฝ˜ํ…์ธ  ์˜ค๋ฅธ์ชฝ | +|---|---:|---:|---:|---:| +| ํ† ๋ก  ๋ชฉ๋ก ๊ธฐ์ค€ | 1,165px | ํ•ด๋‹น ์—†์Œ | 1,165px | 1,135px | +| ํƒœ๊ทธ | 1,165px | 1,165px | 1,165px | 1,135px | +| ์‚ฌ์šฉ์ž ํ”„๋กœํ•„ | 1,165px | 1,165px | 1,165px | 1,135px | +| ์„ค์ • | 1,165px | 1,165px | 1,165px | 1,135px | +| ๋ณด์•ˆ | 1,165px | 1,165px | 1,165px | 1,135px | + +ํ…Œ๋งˆ ๊ณ„์•ฝ ํ…Œ์ŠคํŠธ 14๊ฑด๊ณผ WSL +`issue73-shell-width-20260820` ์ ์šฉยท๋น„ํ™œ์„ฑํ™”ยท์žฌ์ ์šฉ ์ฃผ๊ธฐ๋ฅผ ํ†ต๊ณผํ–ˆ๋‹ค. ์šด์˜ ๋ฐฐํฌ +์ „ ๋ฐฑ์—…์€ `/var/backups/techflow-flarum/theme-shell-width-20260820T142154KST`์— +๋ณด์กดํ–ˆ๋‹ค. ์šด์˜ Source์™€ Vendor LESS SHA-256์€ ๋ชจ๋‘ +`6bff0d72cdee7420aa38cd195229787a39cc64a351b8b18eee61955235a48746`๋กœ ์ผ์น˜ํ•œ๋‹ค. +NginxยทPHP-FPMยทMariaDB๋Š” `active`, ์„œ๋ฒ„ ๋กœ์ปฌ HTTP์™€ ์™ธ๋ถ€ HTTPS๋Š” 200์ด๋‹ค. +DB Schema, ๊ฒŒ์‹œ๋ฌผ, ์ฒจ๋ถ€, TechFlow AI์™€ GitHubโ†’Chat Webhook์€ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š์•˜๋‹ค. + +### ๋ชจ๋ฐ”์ผ ํ•˜์œ„ ๋ฉ”๋‰ด์™€ ์ƒˆ ํ† ๋ก ยท๋Œ“๊ธ€ ๋‹ซ๊ธฐ ํ†ต์ผ + +๋ชจ๋ฐ”์ผ Drawer ์•ˆ์—์„œ `ํ•œ๊ตญ์–ด` ๊ฐ™์€ ํ•˜์œ„ ๋ฉ”๋‰ด๋ฅผ ํŽผ์น  ๋•Œ Flarum Core๊ฐ€ ํ„ฐ์น˜ +๊ธฐ๊ธฐ์šฉ ์ „์ฒด ํ™”๋ฉด `.dropdown-backdrop`์„ ๋ฉ”๋‰ด ๋‚ด๋ถ€์— ์ถ”๊ฐ€ํ•˜๊ณ  ์žˆ์—ˆ๋‹ค. ํ…Œ๋งˆ๊ฐ€ +ํ•˜์œ„ ๋ฉ”๋‰ด๋ฅผ Drawer ์•ˆ์˜ ์ •์  ๋ชฉ๋ก์œผ๋กœ ๋ฐ”๊พผ ๋’ค์—๋„ ์ด Backdrop์ด ๋‚จ์•„ Drawer์™€ +๋ณธ๋ฌธ์„ ํ•จ๊ป˜ ์–ด๋‘ก๊ฒŒ ๋ฎ๋Š” ๊ฒƒ์ด ํ™”๋ฉด ๊นจ์ง์˜ ์›์ธ์ด์—ˆ๋‹ค. Drawer์˜ LocaleยทSession +๋ฉ”๋‰ด ์•ˆ์—์„œ๋งŒ ์ด ํ„ฐ์น˜ Backdrop์„ ์ œ๊ฑฐํ•ด ๋‹ค๋ฅธ Modal์ด๋‚˜ ์ „์ฒด Drawer Backdrop์˜ +๋‹ซ๊ธฐ ๋™์ž‘์€ ์œ ์ง€ํ–ˆ๋‹ค. + +์ƒˆ ํ† ๋ก ์˜ ๋‹ซ๊ธฐ ๋ฒ„ํŠผ์€ 44ร—44px๋กœ ์กด์žฌํ–ˆ์ง€๋งŒ Composer์˜ `::before` ๋ ˆ์ด์–ด๋ณด๋‹ค +๋‚ฎ์€ ์ธต์— ์žˆ์–ด ํ™”๋ฉด์— ๊ฐ€๋ ค์งˆ ์ˆ˜ ์žˆ์—ˆ๋‹ค. Composer ๋ฐฐ๊ฒฝ ๋ ˆ์ด์–ด๋Š” ์ž…๋ ฅ์„ ๋ฐ›์ง€ ์•Š๋Š” +์ตœํ•˜์œ„ ์ธต์œผ๋กœ ๋‚ด๋ฆฌ๊ณ , ๋‹ซ๊ธฐ ์ œ์–ด๋Š” `z-index: 1002`์— ๋ฐฐ์น˜ํ–ˆ๋‹ค. ๊ฐ™์€ ๊ทœ์น™์„ ์ƒˆ ํ† ๋ก ๋ฟ +์•„๋‹ˆ๋ผ ๋Œ“๊ธ€ ์ž‘์„ฑ๊ธฐ์—๋„ ์ ์šฉํ•ด ๋ชจ๋ฐ”์ผ ๋Œ“๊ธ€์—์„œ ์ตœ์†Œํ™”ยท์ „์ฒด ํ™”๋ฉด ๋ฒ„ํŠผ์„ ์ˆจ๊ธฐ๊ณ  +๋ช…์‹œ์ ์ธ `ร—` ๋‹ซ๊ธฐ ๋ฒ„ํŠผ์„ ์ œ๊ณตํ•œ๋‹ค. ๋‹ซ๊ธฐ ์ฒ˜๋ฆฌ๋Š” ๋นˆ ์ž‘์„ฑ๊ธฐ์™€ ์ž‘์„ฑ ์ค‘์ธ ์ž‘์„ฑ๊ธฐ ๋ชจ๋‘ +๊ณตํ†ต ๊ฒฝ๋กœ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉฐ, ์ž‘์„ฑ ์ค‘์ด๋ฉด ๊ธฐ์กด ์ทจ์†Œ ํ™•์ธ์„ ๊ฑฐ์นœ๋‹ค. + +| 390ร—844 ๋ธŒ๋ผ์šฐ์ € ๊ฒ€์ฆ | WSL | ์šด์˜ | +|---|---:|---:| +| Locale ํ•˜์œ„ ๋ฉ”๋‰ด | 222ร—131.1875px | 222ร—131.1875px | +| ์–ธ์–ด ์ „ํ™˜ | `ko โ†’ en โ†’ ko` | `ko โ†’ en โ†’ ko` | +| Drawer ๋‹ซ๊ธฐ | ์ •์ƒ | ์ •์ƒ | +| ์ƒˆ ํ† ๋ก  ๋‹ซ๊ธฐ | 44ร—44pxยทHit ์ •์ƒ | 44ร—44pxยทHit ์ •์ƒ | +| ๋Œ“๊ธ€ ๋‹ซ๊ธฐ | 44ร—44pxยทHit ์ •์ƒ | 44ร—44pxยทHit ์ •์ƒ | +| ๋Œ“๊ธ€ ์ตœ์†Œํ™”ยท์ „์ฒด ํ™”๋ฉด | ์ˆจ๊น€ | ์ˆจ๊น€ | +| ๋‹ซ๊ธฐ ํ›„ Composer | ์—†์Œ | ์—†์Œ | + +ํ…Œ๋งˆ ๊ณ„์•ฝ ํ…Œ์ŠคํŠธ 15๊ฑด๊ณผ WSL +`issue73-mobile-submenu-reply-final-20260820` ํ™œ์„ฑํ™”ยท๋น„ํ™œ์„ฑํ™” ๋กค๋ฐฑยท์žฌํ™œ์„ฑํ™” +์ฃผ๊ธฐ๋ฅผ ํ†ต๊ณผํ–ˆ๋‹ค. ์‚ฌ์šฉ์ž 39๋ช…, ํ† ๋ก  117๊ฑด, ๊ฒŒ์‹œ๋ฌผ 305๊ฑด๊ณผ ์ฝ˜ํ…์ธ ยท์—…๋กœ๋“œ ํ•ด์‹œ๋Š” +์ „ ๊ณผ์ •์—์„œ ๋™์ผํ–ˆ๋‹ค. ์šด์˜ ๋ฐฑ์—…์€ +`/var/backups/techflow-flarum/theme-mobile-submenu-reply-20260820T151526KST`์— +๋ณด์กดํ–ˆ๋‹ค. Source์™€ Vendor LESS SHA-256์€ +`1424f73c1a8b4416e569d7bb32d6fa29a9694fcb9013c2f672030e8138915ca6`, JS๋Š” +`5ad7bb5e00dda6c36c5e97c194d09aefc137c9834cdf9059916f5f0bf956e8cb`๋กœ ๊ฐ๊ฐ +์ผ์น˜ํ•œ๋‹ค. NginxยทPHP-FPMยทMariaDB๋Š” `active`, ์™ธ๋ถ€ HTTPS๋Š” 200์ด๋ฉฐ DB Schema, +๊ฒŒ์‹œ๋ฌผ, ์ฒจ๋ถ€, TechFlow AI์™€ GitHubโ†’Chat Webhook์€ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š์•˜๋‹ค. + +### ๋ชจ๋ฐ”์ผ ๋ฉ”์ธยท์ขŒ์ธก ๋ฉ”๋‰ดยท๋Œ€ํ™”์ƒ์ž ์ธํ„ฐํŽ˜์ด์Šค ๊ต์ • + +390ร—844 ๋ชจ๋ฐ”์ผ ํ™”๋ฉด์—์„œ ๋ฉ”์ธ Header ์•„๋ž˜์— ๋น„์–ด ์žˆ๋Š” ๋‘ฅ๊ทผ ์‚ฌ๊ฐํ˜•์ด ๋‚จ๋˜ ์›์ธ์€ +๋ฐ์Šคํฌํ†ฑ ์ขŒ์ธก ๋ฉ”๋‰ด์˜ `.IndexPage-nav > ul` ๋ฐฐ๊ฒฝยทํ…Œ๋‘๋ฆฌยท๋†’์ด๊ฐ€ ๋ชจ๋ฐ”์ผ์—๋„ +์ ์šฉ๋๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๋ชจ๋ฐ”์ผ์—์„œ๋Š” ํ˜„์žฌ ํ•„ํ„ฐ์™€ ์ƒˆ ํ† ๋ก  ๋ฒ„ํŠผ๋งŒ Header์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ +์žˆ๋„๋ก ๋ฉ”๋‰ด ์ปจํ…Œ์ด๋„ˆ ์ž์ฒด๋Š” 0ร—0 ํˆฌ๋ช… ์˜์—ญ์œผ๋กœ ๋งŒ๋“ค๊ณ  ๋‘ ๋ฒ„ํŠผ์˜ ํฌ์ธํ„ฐ ์ž…๋ ฅ๋งŒ +์œ ์ง€ํ–ˆ๋‹ค. WSL๊ณผ ์šด์˜์—์„œ Header ์•„๋ž˜ ๋นˆ ์‚ฌ๊ฐํ˜•์ด ์—†์–ด์กŒ๊ณ  `๋ชจ๋“  ํ† ์˜`, ์ƒˆ ํ† ๋ก , +์ •๋ ฌยท์ƒˆ๋กœ๊ณ ์นจ ๊ธฐ๋Šฅ์€ ๊ทธ๋Œ€๋กœ ๋™์ž‘ํ•œ๋‹ค. + +์ขŒ์ธก ๋ฉ”๋‰ด๊ฐ€ ๊ฐ€๋กœ๋กœ ๊นจ์ง„ ์›์ธ์€ ๋ฐ์Šคํฌํ†ฑ `.Header-secondary > ul` Flex ํ–‰๊ณผ ์ˆจ๊ฒจ์ง„ +LocaleยทSession Dropdown์ด ๋ชจ๋ฐ”์ผ Drawer์—์„œ๋„ ๋†’์ด๋ฅผ ์ฐจ์ง€ํ•œ ๋ฐ ์žˆ์—ˆ๋‹ค. Drawer๋ฅผ +270px ๋‹จ์ผ ์—ด๋กœ ์žฌ๊ตฌ์„ฑํ•˜๊ณ  ๊ฒ€์ƒ‰ยท์–ธ์–ดยท์‹ ๊ณ ยท์•Œ๋ฆผยท๊ณ„์ •์„ 46px ํ–‰์œผ๋กœ ์ •๋ ฌํ–ˆ๋‹ค. +๋‹ซํžŒ Dropdown์€ `display:none`, ํŽผ์นœ Dropdown๋งŒ ์ •์  ํ•˜์œ„ ๋ชฉ๋ก์œผ๋กœ ํ‘œ์‹œํ•œ๋‹ค. +44ร—44px ๋‹ซ๊ธฐ ๋ฒ„ํŠผ๊ณผ ๋ฐฐ๊ฒฝ ํด๋ฆญยทESC ๋‹ซ๊ธฐ๋ฅผ ์ œ๊ณตํ•˜๊ณ , Flarum ์ „ํ™˜ ํ›„ ๋‚จ๋˜ Backdrop๋„ +์ œ๊ฑฐํ•œ๋‹ค. ์šด์˜์—์„œ ์—ด๋ฆผ Backdrop 1๊ฐœ, ๋‹ซํž˜ ๋’ค 0๊ฐœ๋ฅผ ํ™•์ธํ–ˆ๋‹ค. + +ํ† ๋ก  ์ž‘์„ฑ๊ธฐ์˜ ๋‹ซ๊ธฐ ๋ฒ„ํŠผ์€ DOM์— ์žˆ์—ˆ์ง€๋งŒ Flarum ๋ชจ๋ฐ”์ผ ์ž‘์„ฑ๊ธฐ์˜ `::before` +๋ ˆ์ด์–ด๊ฐ€ ํ„ฐ์น˜ ์ž…๋ ฅ์„ ๊ฐ€๋กœ์ฑ„๊ณ  ์žˆ์—ˆ๋‹ค. ์ด ๋ ˆ์ด์–ด์˜ Pointer Event๋ฅผ ์ œ๊ฑฐํ•˜๊ณ  +44ร—44px `ร—` ๋ฒ„ํŠผ์„ ๊ณ ์ •ํ–ˆ๋‹ค. ์ž‘์„ฑ ๋‚ด์šฉ์ด ์žˆ์œผ๋ฉด ์ทจ์†Œ ํ™•์ธ ํ›„ ๋‹ซ๊ณ , ๋‚ด์šฉ์ด ์—†์œผ๋ฉด +์ฆ‰์‹œ ๋‹ซ๋Š”๋‹ค. ํƒœ๊ทธ ์„ ํƒ์„ ํฌํ•จํ•œ Flarum Modal๋„ ๊ฐ™์€ 44ร—44px `ร—`, ๊ณ ์ • Header, +์Šคํฌ๋กค ๊ฐ€๋Šฅํ•œ Body ๊ทœ์น™์„ ์‚ฌ์šฉํ•œ๋‹ค. + +| ๋ชจ๋ฐ”์ผ ๋ธŒ๋ผ์šฐ์ € ๊ฒ€์ฆ ํ•ญ๋ชฉ | WSL | ์šด์˜ | +|---|---:|---:| +| ๋นˆ Navigation ์˜์—ญ | 0ร—0pxยทํˆฌ๋ช… | 0ร—0pxยทํˆฌ๋ช… | +| Drawer | 270ร—844px | 270ร—844px | +| Drawer ๋‹ซํž˜ ๋’ค Backdrop | 0๊ฑด | 0๊ฑด | +| ํ† ๋ก  ์ž‘์„ฑ ๋‹ซ๊ธฐ ๋ฒ„ํŠผ | 44ร—44pxยท๋™์ž‘ | 44ร—44pxยท๋™์ž‘ | +| ํƒœ๊ทธ Modal ๋‹ซ๊ธฐ ๋ฒ„ํŠผ | 44ร—44pxยท๋™์ž‘ | 44ร—44pxยท๋™์ž‘ | +| Modal Body ์Šคํฌ๋กค | `auto` | `auto` | + +ํ…Œ๋งˆ ๊ณ„์•ฝ ํ…Œ์ŠคํŠธ 15๊ฑด๊ณผ WSL +`issue73-mobile-final-20260820` ์ ์šฉยท๋น„ํ™œ์„ฑํ™”ยท์žฌ์ ์šฉ ์ฃผ๊ธฐ๋ฅผ ํ†ต๊ณผํ–ˆ๋‹ค. ์‚ฌ์šฉ์ž 39๋ช…, +ํ† ๋ก  117๊ฑด, ๊ฒŒ์‹œ๋ฌผ 305๊ฑด๊ณผ ์ฝ˜ํ…์ธ ยท์—…๋กœ๋“œ ํ•ด์‹œ๋Š” ์ „ ๊ณผ์ •์—์„œ ๋™์ผํ–ˆ๋‹ค. ์šด์˜ ๋ฐฐํฌ +์ „ ๋ฐฑ์—…์€ `/var/backups/techflow-flarum/theme-mobile-ui-20260820T145819KST`์— +๋ณด์กดํ–ˆ๋‹ค. Source์™€ Vendor LESS SHA-256์€ ๋ชจ๋‘ +`35dc0b6a285f2242b66a2be3253f71de432ec92412c53206173d99c2482547ee`๋กœ ์ผ์น˜ํ•˜๊ณ , +NginxยทPHP-FPMยทMariaDB๋Š” `active`, ์„œ๋ฒ„ ๋กœ์ปฌ HTTP์™€ ์™ธ๋ถ€ ๋ชฉ๋กยท์ƒ์„ธ HTTPS๋Š” 200์ด๋‹ค. +DB Schema, ๊ฒŒ์‹œ๋ฌผ, ์ฒจ๋ถ€, TechFlow AI์™€ GitHubโ†’Chat Webhook์€ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š์•˜๋‹ค. diff --git a/docs/runbooks/community-theme-rollout-rollback.md b/docs/runbooks/community-theme-rollout-rollback.md index 168e101..faed702 100644 --- a/docs/runbooks/community-theme-rollout-rollback.md +++ b/docs/runbooks/community-theme-rollout-rollback.md @@ -54,12 +54,128 @@ sudo -u www-data env TECHFLOW_ALLOWED_FLARUM_ROOT=/var/www/html \ sudo systemctl restart php8.3-fpm nginx ``` +์ด๋ฏธ ์„ค์น˜๋œ ํ…Œ๋งˆ์˜ CSSยทLocaleยทJS๋งŒ ๊ฐฑ์‹ ํ•  ๋•Œ๋Š” ๋ณ€๊ฒฝ ์ž์‚ฐ์„ +`/tmp/ablecloud-community-theme-`์— ์ „์†กํ•œ ๋’ค ๊ฒฝ๋กœ ์ œํ•œ ๋ฐฐํฌ๊ธฐ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. +๋ฐฐํฌ๊ธฐ๋Š” ํ˜„์žฌ ExtensionยทVendorยทComposerยท์ปดํŒŒ์ผ ์ž์‚ฐยทLocale์„ +`/var/backups/techflow-flarum/theme-`์— ๋จผ์ € ๋ฐฑ์—…ํ•˜๊ณ , ์ ์šฉ ํ›„ ์„œ๋น„์Šค์™€ +์ปดํŒŒ์ผ CSS๋ฅผ ๊ฒ€์ฆํ•œ๋‹ค. + +```bash +sudo bash deploy/flarum/apply-community-theme-update.sh \ + /tmp/ablecloud-community-theme- \ + theme- +``` + ## 5. ์ ์šฉ ํ›„ ๊ฒ€์ฆ - ์™ธ๋ถ€ HTTPS์™€ ์„œ๋ฒ„ ๋กœ์ปฌ Host/HTTPS ์ „๋‹ฌ ํ—ค๋” ์ ๊ฒ€์ด ๋ชจ๋‘ 200์ด์–ด์•ผ ํ•œ๋‹ค. - `public/assets/forum.css`์— `--ablecloud-brand-primary`๊ฐ€ ์žˆ์–ด์•ผ ํ•œ๋‹ค. - `public/assets/forum-ko.js`๊ฐ€ ๋น„์–ด ์žˆ์ง€ ์•Š๊ณ  `core.forum.header.search_placeholder`๋ฅผ ํฌํ•จํ•ด์•ผ ํ•œ๋‹ค. - ๋ฐ์Šคํฌํ†ฑ๊ณผ ๋ชจ๋ฐ”์ผ์—์„œ ํ™ˆ, ๋ชฉ๋ก, ์ƒ์„ธ, ๊ฒ€์ƒ‰, ๋กœ๊ทธ์ธ, ๊ธ€์“ฐ๊ธฐ, ์ฒจ๋ถ€๋ฅผ ํ™•์ธํ•œ๋‹ค. +- ๋ฐ์Šคํฌํ†ฑ ์ฒซ ํ™”๋ฉด์—์„œ ๋ฌธ์„œ ์ „์ฒด์˜ `scrollY`๊ฐ€ `0`์œผ๋กœ ์œ ์ง€๋˜๋Š”์ง€ ํ™•์ธํ•œ๋‹ค. + Welcome Hero๋Š” ํ‘œ์‹œํ•˜์ง€ ์•Š๊ณ  ์ขŒ์ธก ๋ฉ”๋‰ด๋Š” ๊ณ ์ •ํ•œ๋‹ค. `.IndexPage-results` ์ „์ฒด๊ฐ€ ์Šคํฌ๋กค๋˜์–ด์•ผ + ํ•œ๋‹ค. `์ตœ์‹ ` ์„ ํƒ ์ฝค๋ณด๋ฅผ ํฌํ•จํ•œ `.IndexPage-toolbar`์™€ ํ† ๋ก  ํ–‰์ด ํ•จ๊ป˜ + ์ด๋™ํ•ด์•ผ ํ•˜๋ฉฐ `.DiscussionList` ์ž์ฒด์˜ `scrollTop`์€ `0`์„ ์œ ์ง€ํ•ด์•ผ ํ•œ๋‹ค. + ์šฐ์ธก ์˜์—ญ๋„ `scrollbar-width: none`๊ณผ WebKit ๊ทœ์น™์œผ๋กœ ์Šคํฌ๋กค๋ฐ”๋ฅผ ํ‘œ์‹œํ•˜์ง€ + ์•Š๋Š”๋‹ค. ํ™”๋ฉด ํ•˜๋‹จ์—๋Š” Footer๊ฐ€ ์—†์–ด์•ผ ํ•œ๋‹ค. +- ๊ธฐ์กด Footer์˜ `ABLECLOUD Home`, `Blog`, `Online Docs` ๋งํฌ๋Š” ์ฝ˜ํ…์ธ  ์˜ค๋ฅธ์ชฝ์˜ + ์„ธ๋กœํ˜• ์›ํ˜• ํ€ต ๋งํฌ๋กœ ๋ณด์—ฌ์•ผ ํ•œ๋‹ค. ๋ฐ์Šคํฌํ†ฑ์—์„œ๋Š” 48px ํญ, ๋ชจ๋ฐ”์ผ์—์„œ๋Š” + 44px ํญ์ด๋ฉฐ ํ† ๋ก  ๋ชฉ๋ก์„ ์Šคํฌ๋กคํ•ด๋„ ํ™”๋ฉด์ƒ Y ์ขŒํ‘œ๊ฐ€ ๋ณ€ํ•˜์ง€ ์•Š์•„์•ผ ํ•œ๋‹ค. +- ๋ฐ์Šคํฌํ†ฑ ์ฒซ ํ™”๋ฉด์€ ๊ธฐ์กด 1100px ์ฝ˜ํ…์ธ  ํญ์„ ์œ ์ง€ํ•˜๊ณ , ์˜ค๋ฅธ์ชฝ์— ํ€ต ๋งํฌ + ๋ฐฐ์น˜์šฉ 65px๋งŒ ์ถ”๊ฐ€ํ•œ ์ตœ๋Œ€ 1165px ์…ธ์„ ์‚ฌ์šฉํ•œ๋‹ค. ํ† ๋ก  ๋ชฉ๋ก ์˜ค๋ฅธ์ชฝ๊ณผ 48px + ํ€ต ๋งํฌ ์‚ฌ์ด์—๋Š” ์•ฝ 16px ๊ฐ„๊ฒฉ์ด ์žˆ์–ด์•ผ ํ•˜๋ฉฐ ํ™”๋ฉด ์˜ค๋ฅธ์ชฝ ๋์œผ๋กœ ๋ถ„๋ฆฌ๋˜๋ฉด + ์•ˆ ๋œ๋‹ค. +- ๋ฐ์Šคํฌํ†ฑ ์ฒซ ํ™”๋ฉด์˜ ์ƒ๋‹จ ํ—ค๋”์™€ ๋ณธ๋ฌธ ์…ธ์€ ๋ชจ๋‘ ์ตœ๋Œ€ 1165px๋กœ ๋™์ผํ•ด์•ผ ํ•œ๋‹ค. + `scrollbar-gutter: stable`์„ ์ ์šฉํ•œ 1680px Viewport์—์„œ๋Š” ๋‘ ์˜์—ญ์˜ X๊ฐ€ 250px, + ์˜ค๋ฅธ์ชฝ ๋์ด 1415px์ธ์ง€ ํ™•์ธํ•œ๋‹ค. + ํƒœ๊ทธยทํƒœ๊ทธ๋ณ„ ๋ชฉ๋กยท์‚ฌ์šฉ์ž ํ”„๋กœํ•„ยท์„ค์ •ยท๋ณด์•ˆ ํ™”๋ฉด์˜ HeaderยทHeroยท๋ณธ๋ฌธ๋„ ๊ฐ™์€ + 1165px ์™ธ๊ณฝ์„ ์„ ์‚ฌ์šฉํ•œ๋‹ค. ๋ณธ๋ฌธ๊ณผ Hero์˜ ์˜ค๋ฅธ์ชฝ 80px์€ ๊ณ ์ • ํ€ต ๋งํฌ ์˜์—ญ์œผ๋กœ + ์˜ˆ์•ฝํ•ด ๊ธฐ์กด 1070px ๋‚ด๋ถ€ ์ฝ˜ํ…์ธ  ํญ์„ ์œ ์ง€ํ•˜๊ณ  ๊ฒน์นจ์„ ๋ฐฉ์ง€ํ•œ๋‹ค. ๋ชจ๋ฐ”์ผ์—๋Š” ์ด + ๋ฐ์Šคํฌํ†ฑ ํญ์„ ๊ฐ•์ œํ•˜์ง€ ์•Š๋Š”๋‹ค. +- ์ขŒ์ธก ๋ฉ”๋‰ด๋Š” ์‚ฌ์šฉ์ž ์„ค์ • ํ™”๋ฉด๊ณผ ๊ฐ™์ด ๊ณ ์ •ํ•œ๋‹ค. ๋†’์ด๊ฐ€ ์งง์•„ ํƒœ๊ทธ๊ฐ€ ๋ชจ๋‘ ๋ณด์ด์ง€ + ์•Š์„ ๋•Œ ๋‚ด๋ถ€ ์Šคํฌ๋กค์€ ํ—ˆ์šฉํ•˜์ง€๋งŒ `scrollbar-width: none`๊ณผ WebKit ๊ทœ์น™์œผ๋กœ + ์Šคํฌ๋กค๋ฐ”๋Š” ํ‘œ์‹œํ•˜์ง€ ์•Š๋Š”๋‹ค. +- ํ† ๋ก  ๋ชฉ๋ก ๋ฐ”๊นฅ Container์™€ ๊ฐœ๋ณ„ ํ–‰์— Card BorderยทBackgroundยทShadow๋ฅผ + ๋งŒ๋“ค์ง€ ์•Š๋Š”๋‹ค. ํˆฌ๋ช…ํ•œ ํ‰๋ฉด ํ”ผ๋“œ์—์„œ 1px ๊ตฌ๋ถ„์„ ์œผ๋กœ๋งŒ ํ–‰์„ ๋‚˜๋ˆ„๊ณ , + ์ž‘์„ฑ ์ •๋ณดยท์ œ๋ชฉยท์ฒซ ๊ธ€ ์š”์•ฝยทํƒœ๊ทธยท๋Œ“๊ธ€ ์ˆ˜ยท๋”๋ณด๊ธฐ ๊ธฐ๋Šฅ๊ณผ ํŒŒ๋ž€์ƒ‰ Hover/Focus + ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•œ๋‹ค. +- ํ•ด๊ฒฐ ๋ฐฐ์ง€์™€ ๋Œ€ํ‘œ ์นดํ…Œ๊ณ ๋ฆฌยท์ž‘์„ฑ์žยท์ž‘์„ฑ ์‹œ๊ฐ์€ ๊ฐ™์€ ํ—ค๋” ํ–‰์—์„œ ์„œ๋กœ ๊ฒน์น˜์ง€ + ์•Š์•„์•ผ ํ•œ๋‹ค. ์‚ฌ์šฉ์ž ์•„๋ฐ”ํƒ€๋Š” ํ•ด๊ฒฐ ์—ฌ๋ถ€์™€ ๊ด€๊ณ„์—†์ด ํ–‰ ์ขŒ์ธก ์ƒ๋‹จ์˜ ๋ณธ๋ฌธ ๊ฒฝ๊ณ„ + ์•ˆ์— ์žˆ์–ด์•ผ ํ•œ๋‹ค. +- ์ฒซ ๊ฒŒ์‹œ๋ฌผ์— ์ด๋ฏธ์ง€๊ฐ€ ์žˆ์œผ๋ฉด ์ฒซ ์ด๋ฏธ์ง€๋ฅผ ๋ฐ์Šคํฌํ†ฑ 124ร—92px, ๋ชจ๋ฐ”์ผ 88ร—72px + ์šฐ์ธก ์ธ๋„ค์ผ๋กœ ํ‘œ์‹œํ•œ๋‹ค. ์ด๋ฏธ์ง€๊ฐ€ ์—†๋Š” ๊ธ€์—๋Š” ๋นˆ ์ธ๋„ค์ผ ์˜์—ญ์„ ๋งŒ๋“ค์ง€ ์•Š์œผ๋ฉฐ, + ์ธ๋„ค์ผ์€ ๊ธฐ์กด Store ๋ฐ์ดํ„ฐ์—์„œ ์ƒ์„ฑํ•˜๊ณ  ์ง€์—ฐ ๋กœ๋”ฉํ•ด์•ผ ํ•œ๋‹ค. +- ํ† ๋ก  ์ƒ์„ธ Hero์™€ ๋ณธ๋ฌธ ์…ธ์€ ์‹œ์ž‘ ํŽ˜์ด์ง€์™€ ๊ฐ™์€ 1165px ์™ธ๊ณฝ์„ ์„ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค. + ์ƒ์„ธ Header๋„ ๊ฐ™์€ 1165px ์™ธ๊ณฝ์„ ์„ ์‚ฌ์šฉํ•ด HeaderยทHeroยท๋ณธ๋ฌธ์˜ ์ขŒ์šฐ ๋์ด ์ •ํ™•ํžˆ + ์ผ์น˜ํ•ด์•ผ ํ•œ๋‹ค. + ๊ฒŒ์‹œ๋ฌผ Stream์€ 845px, ๊ธฐ์กด Flarum ์„ธ๋กœ ํƒ์ƒ‰์€ 150px์„ ์œ ์ง€ํ•˜๊ณ  ๊ทธ ์‚ฌ์ด์—๋Š” + 75px ๊ฐ„๊ฒฉ์„ ๋‘”๋‹ค. ์„ธ๋กœ ํƒ์ƒ‰์—๋Š” ๋‹ต์žฅยทํŒ”๋กœ์šฐยท์›๋ณธ/์ตœ์‹  ๊ฒŒ์‹œ๋ฌผยทํ˜„์žฌ ์œ„์น˜ยท์ฝ์ง€ + ์•Š์Œ ์ •๋ณด๊ฐ€ ํ‘œ์‹œ๋˜์–ด์•ผ ํ•œ๋‹ค. +- ์ƒ์„ธ ์…ธ ์˜ค๋ฅธ์ชฝ 80px์€ ํ€ต ๋งํฌ ์˜์—ญ์œผ๋กœ ์˜ˆ์•ฝํ•˜๋ฉฐ ์„ธ๋กœ ํƒ์ƒ‰๊ณผ ํ€ต ๋งํฌ์˜ ๊ฒน์นจ์€ + 0px์ด์–ด์•ผ ํ•œ๋‹ค. +- ์ƒ์„ธ ์ œ๋ชฉ์€ Welcome Hero์™€ ๊ฐ™์€ ๋ฐฐ๋„ˆ ์™ธํ˜•์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค. ํฐ ๋ฐฐ๊ฒฝ์˜ ์ฝ˜ํ…์ธ  + ์ œ๋ชฉ ์˜์—ญ์—์„œ ํ•ด๊ฒฐ ์ƒํƒœยทํƒœ๊ทธยท์ œ๋ชฉยท์ž‘์„ฑ์žยท์ž‘์„ฑ ์‹œ๊ฐ์„ ์™ผ์ชฝ ์ •๋ ฌ๋กœ ํ‘œ์‹œํ•œ๋‹ค. +- ๋ฐ์Šคํฌํ†ฑ ์ƒ์„ธ Hero๋Š” ๊ณ ์ • Header ์•„๋ž˜์—์„œ 16px ๋‚ด๋ ค ๋ฐฐ์น˜ํ•˜๊ณ  Header ํ•˜๋‹จ๊ณผ ์ฒซ + ํƒœ๊ทธ ์‚ฌ์ด์— ์•ฝ 18px์˜ ์‹œ๊ฐ์  ๊ฐ„๊ฒฉ์ด ์žˆ์–ด์•ผ ํ•œ๋‹ค. +- ์ผ๋ฐ˜ ๋‹ต๋ณ€์€ ์นด๋“œ ์ค‘์ฒฉ ์—†์ด 1px ๊ตฌ๋ถ„์„ ์œผ๋กœ ๋‚˜๋ˆ„๊ณ  AI ๊ธฐ์ˆ ์ง€์›ยท์ถ”๊ฐ€ ํ™•์ธ ํ•„์š”ยท + ์ตœ์ข… ํ•ด๊ฒฐ ๊ฐ€์ด๋“œ๋งŒ ์˜๋ฏธ ์ƒ‰์ƒ ์นด๋“œ๋กœ ๊ตฌ๋ถ„ํ•œ๋‹ค. +- ์ฒซ ๊ฒŒ์‹œ๋ฌผ ์•ˆ์˜ ์„ ํƒ ๋‹ต๋ณ€ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋Š” ์ˆจ๊ธฐ๊ณ  `ํ•ด๊ฒฐ ๋‹ต๋ณ€ ๋ณด๊ธฐ`๊ฐ€ ์‹ค์ œ ์„ ํƒ ๋‹ต๋ณ€์œผ๋กœ + ์ด๋™ํ•ด์•ผ ํ•œ๋‹ค. ์ด๋™ ํ›„ ์„ ํƒ ๋‹ต๋ณ€ ์ƒ๋‹จ์€ ๊ณ ์ • Header ์•„๋ž˜์—์„œ ๋ณด์—ฌ์•ผ ํ•œ๋‹ค. +- ์งˆ๋ฌธ ๋‹ค์Œ์—๋Š” `๋‹ต๋ณ€์„ ์ž‘์„ฑํ•ด ์ฃผ์„ธ์š”` ๋ฒ„ํŠผ์ด ํ‘œ์‹œ๋˜์–ด ๊ธฐ์กด Flarum ๋‹ต์žฅ ์ž‘์„ฑ๊ธฐ๋ฅผ + ์—ด์–ด์•ผ ํ•œ๋‹ค. 480px๋ณด๋‹ค ๊ธด `pre` ๋กœ๊ทธ์—๋Š” `์ „์ฒด ๋กœ๊ทธ ๋ณด๊ธฐ`์™€ `๋กœ๊ทธ ์ ‘๊ธฐ`๊ฐ€ ์ œ๊ณต๋˜์–ด์•ผ ํ•œ๋‹ค. +- ์™ผ์ชฝ ํ† ๋ก  ๋ชฉ๋ก์€ 400px ํญ์„ ์œ ์ง€ํ•˜๋˜ ๋ฉ”ํƒ€ ์ •๋ณด, ๋‘ ์ค„ ์ œ๋ชฉ, ๋‘ ์ค„ ์š”์•ฝ์„ ์„ธ๋กœ๋กœ + ๋ฐฐ์น˜ํ•ด์•ผ ํ•œ๋‹ค. ์ธ๋„ค์ผ์€ 72ร—54px ์˜ค๋ฅธ์ชฝ ์ „์šฉ ์—ด, ๋Œ“๊ธ€ ์ˆ˜๋Š” ์˜ค๋ฅธ์ชฝ ์•„๋ž˜์— ๋‘๊ณ  + ์ œ๋ชฉยท์š”์•ฝ๊ณผ ๊ฒน์น˜์ง€ ์•Š์•„์•ผ ํ•œ๋‹ค. +- ์ž‘์„ฑ๊ธฐ์˜ ๋ฏธ๋ฆฌ๋ณด๊ธฐยท๊ตต๊ฒŒยท๊ธฐ์šธ์ž„ยท์ฝ”๋“œยท์ธ์šฉยท๋งํฌยท์ด๋ฏธ์ง€ยท๋ชฉ๋กยท์ถ”๊ฐ€ ์„œ์‹ ์•„์ด์ฝ˜์€ + SVG Mask๋กœ ํ‘œ์‹œ๋˜์–ด์•ผ ํ•˜๋ฉฐ `P`๋‚˜ ๊ฒ€์€ ์‚ฌ๊ฐํ˜• ๋Œ€์ฒด ๋ฌธ์ž๊ฐ€ ์—†์–ด์•ผ ํ•œ๋‹ค. +- ๋ชฉ๋ก์—์„œ ์ƒ์„ธ ํ™”๋ฉด์œผ๋กœ ์ด๋™ํ–ˆ์„ ๋•Œ ์ „์ฒด ํŽ˜์ด์ง€๋ฅผ ๋‹ค์‹œ ์—ด์ง€ ์•Š๊ณ  Flarum SPA + ์ „ํ™˜์„ ์œ ์ง€ํ•ด์•ผ ํ•œ๋‹ค. ์ƒ์„ธ ํ™”๋ฉด์˜ `scrollY`๋Š” 0์ด๊ณ  ์™ผ์ชฝ ๊ฐ€์žฅ์ž๋ฆฌ Hover ์‹œ + 400px ํ† ๋ก  ๋ชฉ๋ก์ด ํ‘œ์‹œ๋˜์–ด์•ผ ํ•œ๋‹ค. ์ƒ์„ธ ์ƒˆ๋กœ๊ณ ์นจ ๋’ค์—๋„ ์ง์ „ ๋ชฉ๋ก Snapshot์„ + ์‚ฌ์šฉํ•ด ๋ชฉ๋ก์„ ๋ณต์›ํ•ด์•ผ ํ•œ๋‹ค. +- ๋ฐ์Šคํฌํ†ฑ ํ™œ์„ฑ ์ž‘์„ฑ๊ธฐ๋Š” ์ตœ์†Œ 336px ๋†’์ด๋ฅผ ๊ฐ€์ ธ์•ผ ํ•˜๋ฉฐ ๊ฒŒ์‹œ ๋ฒ„ํŠผ์€ ํ™”๋ฉด ํ•˜๋‹จ์—์„œ + 20px ์ด์ƒ ๋–จ์–ด์ ธ์•ผ ํ•œ๋‹ค. ์ž‘์„ฑ๊ธฐ๊ฐ€ ํ‘œ์‹œ๋˜๋Š” ๋™์•ˆ ์™ผ์ชฝ ํ† ๋ก  ๋ชฉ๋ก์€ ์ˆจ๊ฒจ์ ธ ์„œ๋กœ + ๊ฒน์น˜์ง€ ์•Š์•„์•ผ ํ•˜๊ณ , ์ž‘์„ฑ๊ธฐ๋ฅผ ๋‹ซ์œผ๋ฉด ๋ชฉ๋ก์ด ๋‹ค์‹œ ํ‘œ์‹œ๋˜์–ด์•ผ ํ•œ๋‹ค. +- ์ž‘์„ฑ๊ธฐ ๋ณธ๋ฌธ ํŽธ์ง‘ ์˜์—ญ๊ณผ ํ•˜๋‹จ ๋„๊ตฌ๋ง‰๋Œ€๋Š” ๋…๋ฆฝ๋œ ์„ธ๋กœ Flex ํ–‰์ด์–ด์•ผ ํ•œ๋‹ค. ๋‘ ์˜์—ญ์˜ + ๊ฒฝ๊ณ„๋Š” ๋งž๋‹ฟ์„ ์ˆ˜ ์žˆ์ง€๋งŒ ๊ต์ฐจํ•˜๋ฉด ์•ˆ ๋˜๋ฉฐ ๋„๊ตฌ๋ง‰๋Œ€ ํ•˜๋‹จ ์ „์ฒด๊ฐ€ ์ž‘์„ฑ๊ธฐ ๊ฒฝ๊ณ„ ์•ˆ์— + ์žˆ์–ด์•ผ ํ•œ๋‹ค. ์ž‘์„ฑ ์ค‘์ธ ๋ฐฐ๊ฒฝ ํ† ๋ก  ํ–‰์€ Hover ์ƒํƒœ์—์„œ๋„ ์ž‘์„ฑ๊ธฐ๋ณด๋‹ค ์•ž์œผ๋กœ ๋‚˜์˜ค๋ฉด + ์•ˆ ๋œ๋‹ค. +- 400px ํ† ๋ก  ๋ชฉ๋ก์˜ ๋”๋ณด๊ธฐ ๋ฉ”๋‰ด๋Š” 32ร—32px์ด๊ณ  ์นด๋“œ ์ƒ๋‹จ๊ณผ ์˜ค๋ฅธ์ชฝ์—์„œ ์•ฝ 10px + ์•ˆ์ชฝ์— ์žˆ์–ด์•ผ ํ•œ๋‹ค. `translateY`๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์œผ๋ฉฐ ๋ฒ„ํŠผ ์ „์ฒด๊ฐ€ ์นด๋“œ ๊ฒฝ๊ณ„ ์•ˆ์— + ํฌํ•จ๋˜์–ด์•ผ ํ•œ๋‹ค. +- ์‹œ์ž‘ ํŽ˜์ด์ง€์˜ ๋ชฉ๋ก ๋๊นŒ์ง€ ์Šคํฌ๋กคํ•˜๋ฉด ๊ธฐ์กด `๋” ๋ณด๊ธฐ` ๋ฒ„ํŠผ์„ ์ง์ ‘ ๋ˆ„๋ฅด์ง€ ์•Š์•„๋„ + ๋‹ค์Œ 20๊ฑด์ด ์ž๋™์œผ๋กœ ๋กœ๋“œ๋˜์–ด์•ผ ํ•œ๋‹ค. ์—ฐ์† ๋‘ ๋ฒˆ ์Šคํฌ๋กคํ•ด 20โ†’40โ†’60๊ฑด์„ + ํ™•์ธํ•˜๊ณ , ๊ฐ™์€ ๋กœ๋”ฉ ํ‘œ์‹ ๋•Œ๋ฌธ์— DOM ๋ณ€๊ฒฝ์ด ๋ฐ˜๋ณต๋˜์ง€ ์•Š๋Š”์ง€ ํ™”๋ฉด ์กฐ์ž‘ ์ง€์—ฐ๋„ + ํ•จ๊ป˜ ํ™•์ธํ•œ๋‹ค. ๋ธŒ๋ผ์šฐ์ €๊ฐ€ `IntersectionObserver`๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ์—๋Š” ๊ธฐ์กด + ๋ฒ„ํŠผ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค. +- ์ƒˆ ํ† ๋ก ์˜ `์ด๋ชจ์ง€ ์‚ฝ์ž…` ๋ฒ„ํŠผ์€ ํ•œ๊ธ€ ์•ˆ๋‚ด๊ฐ€ ์žˆ๋Š” ๊ณตํ†ต ์ด๋ชจ์ง€ 24๊ฐœ ์„ ํƒ๊ธฐ๋ฅผ + ํ‘œ์‹œํ•ด์•ผ ํ•œ๋‹ค. `regional_indicator_a` ๊ฐ™์€ ๋‚ด๋ถ€ ์˜๋ฌธ ์ด๋ฆ„์ด๋‚˜ ์›๋ž˜ Dropdown์ด + ํ•จ๊ป˜ ํ‘œ์‹œ๋˜๋ฉด ์•ˆ ๋˜๋ฉฐ, ํ•ญ๋ชฉ ์„ ํƒ ํ›„ ์‹ค์ œ ํŽธ์ง‘๊ธฐ ๋ณธ๋ฌธ์— Unicode ์ด๋ชจ์ง€๊ฐ€ + ๋“ค์–ด๊ฐ€๋Š”์ง€ ํ™•์ธํ•œ๋‹ค. +- ์ƒˆ ํ† ๋ก  ์ž‘์„ฑ๊ธฐ๋Š” ํ™”๋ฉด ํ•˜๋‹จ ๋ ˆ์ด์–ด๊ฐ€ ์•„๋‹ˆ๋ผ ์ „์ฒด ํ™”๋ฉด ๋ฐฐ๊ฒฝ ์ฐจ๋‹จ ๋ ˆ์ด์–ด ์œ„์˜ ์ค‘์•™ + ๋Œ€ํ™”์ƒ์ž๋กœ ํ‘œ์‹œ๋˜์–ด์•ผ ํ•œ๋‹ค. ์ตœ์†Œํ™”ยท์ „์ฒด ํ™”๋ฉด์€ ์ˆจ๊ธฐ๊ณ  ๋‹ซ๊ธฐ๋Š” ์œ ์ง€ํ•œ๋‹ค. ๋ณธ๋ฌธ + ํŽธ์ง‘ ์˜์—ญ๊ณผ ๋„๊ตฌ๋ง‰๋Œ€๊ฐ€ ๋…๋ฆฝ ํ–‰์œผ๋กœ ๋Œ€ํ™”์ƒ์ž ์•ˆ์— ๋ชจ๋‘ ๋“ค์–ด๊ฐ€๋ฉฐ, ์ฒซ ํด๋ฆญ๋งŒ์œผ๋กœ + ๋Œ€ํ™”์ƒ์ž ์ƒํƒœ๊ฐ€ ์ ์šฉ๋˜์–ด์•ผ ํ•œ๋‹ค. ๋‹ต์žฅ ์ž‘์„ฑ๊ธฐ๋Š” ๊ธฐ์กด ํ๋ฆ„์„ ์œ ์ง€ํ•œ๋‹ค. +- 390ร—844 ๋ชจ๋ฐ”์ผ ์‹œ์ž‘ ํ™”๋ฉด์—์„œ Header ์•„๋ž˜ `.IndexPage-nav > ul`์€ ๋นˆ ๋‘ฅ๊ทผ + ์‚ฌ๊ฐํ˜•์„ ๋งŒ๋“ค์ง€ ์•Š์•„์•ผ ํ•œ๋‹ค. ํ˜„์žฌ ํ•„ํ„ฐ์™€ ์ƒˆ ํ† ๋ก  ๋ฒ„ํŠผ์€ Header์—์„œ ๊ณ„์† + ๋™์ž‘ํ•ด์•ผ ํ•œ๋‹ค. +- ๋ชจ๋ฐ”์ผ ์ขŒ์ธก Drawer๋Š” 270px ๋‹จ์ผ ์—ด๋กœ ํ‘œ์‹œํ•˜๊ณ  ๊ฒ€์ƒ‰ยท์–ธ์–ดยท์‹ ๊ณ ยท์•Œ๋ฆผยท๊ณ„์ •์„ + ์„ธ๋กœ ํ–‰์œผ๋กœ ๋ฐฐ์น˜ํ•œ๋‹ค. ๋‹ซํžŒ LocaleยทSession Dropdown์€ ๊ณต๊ฐ„์„ ์ฐจ์ง€ํ•˜์ง€ ์•Š์•„์•ผ + ํ•˜๋ฉฐ 44ร—44px ๋‹ซ๊ธฐ ๋ฒ„ํŠผ, ๋ฐฐ๊ฒฝ ํด๋ฆญ, ESC๋กœ ๋‹ซ์€ ๋’ค `.drawer-backdrop`์€ 0๊ฑด์ด์–ด์•ผ ํ•œ๋‹ค. +- ๋ชจ๋ฐ”์ผ ํ† ๋ก  ์ž‘์„ฑ๊ธฐ์™€ ๋ชจ๋“  Flarum Modal์—๋Š” ํ•ญ์ƒ ๋ณด์ด๋Š” 44ร—44px `ร—` ๋‹ซ๊ธฐ + ๋ฒ„ํŠผ์„ ์ œ๊ณตํ•œ๋‹ค. ๋‹ซ๊ธฐ ๋ฒ„ํŠผ ์ค‘์‹ฌ์˜ ์‹ค์ œ Hit Target์ด ๋ฒ„ํŠผ ์ž์‹ ์ธ์ง€ ํ™•์ธํ•˜๊ณ , + ํƒœ๊ทธ Modal์€ ๊ณ ์ • Header์™€ ๋…๋ฆฝ์ ์œผ๋กœ ์Šคํฌ๋กค๋˜๋Š” Body๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค. +- ๋ชจ๋ฐ”์ผ Drawer์—์„œ ์–ธ์–ดยท๊ณ„์ • ํ•˜์œ„ ๋ฉ”๋‰ด๋ฅผ ํŽผ์น  ๋•Œ Flarum์˜ ํ„ฐ์น˜ ์ „์šฉ + `.dropdown-backdrop`์ด Drawer๋ฅผ ๋ฎ๊ฑฐ๋‚˜ ์ƒ‰์„ ์ž…ํžˆ๋ฉด ์•ˆ ๋œ๋‹ค. `ํ•œ๊ตญ์–ด โ†’ English โ†’ + ํ•œ๊ตญ์–ด` ์ „ํ™˜ ํ›„ Drawer๊ฐ€ ์ •์ƒ ๋‹ซํžˆ๊ณ  ์ตœ์ข… ์–ธ์–ด๊ฐ€ `ko`์ธ์ง€ ํ™•์ธํ•œ๋‹ค. +- ๋ชจ๋ฐ”์ผ ์ƒˆ ํ† ๋ก ๊ณผ ๋Œ“๊ธ€ ์ž‘์„ฑ๊ธฐ๋Š” ๋ชจ๋‘ 44ร—44px `ร—` ๋‹ซ๊ธฐ๋ฅผ ์ œ๊ณตํ•œ๋‹ค. ๋Œ“๊ธ€ ์ž‘์„ฑ๊ธฐ์—๋Š” + ์ตœ์†Œํ™”ยท์ „์ฒด ํ™”๋ฉด ๋ฒ„ํŠผ์„ ํ‘œ์‹œํ•˜์ง€ ์•Š์œผ๋ฉฐ, ๋‹ซ๊ธฐ ํ›„ `.ablecloud-composer-open` ์ƒํƒœ์™€ + ๋ณด์ด๋Š” Composer๊ฐ€ ๋ชจ๋‘ ์ œ๊ฑฐ๋˜์–ด์•ผ ํ•œ๋‹ค. +- ํ—ค๋” ๊ฒ€์ƒ‰์— `v2k`์ฒ˜๋Ÿผ ํ•ด๊ฒฐ ๋‹ต๋ณ€๊ณผ ์ผ๋ฐ˜ ํ† ๋ก ์ด ํ•จ๊ป˜ ์กฐํšŒ๋˜๋Š” ๊ฒ€์ƒ‰์–ด๋ฅผ ์ž…๋ ฅํ•œ๋‹ค. + ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ํ•œ ๊ฑด์€ ํƒœ๊ทธยท์ œ๋ชฉยท์งˆ๋ฌธ ์š”์•ฝยท์„ ํƒ ๋‹ต๋ณ€ ์š”์•ฝ์ด ์œ„์—์„œ ์•„๋ž˜๋กœ ํ‘œ์‹œ๋˜์–ด์•ผ + ํ•˜๋ฉฐ, ๋„ค ์˜์—ญ์ด ๊ฐ€๋กœ ์—ด๋กœ ์••์ถ•๋˜๊ฑฐ๋‚˜ ๊ธ€์ž๊ฐ€ ํ•œ๋‘ ์ž์”ฉ ๋Š๊ธฐ๋ฉด ์•ˆ ๋œ๋‹ค. ๊ฒ€์ƒ‰ ์ ‘๋‘ + ์•„์ด์ฝ˜๊ณผ ๋น„์šฐ๊ธฐ ์•„์ด์ฝ˜์€ ์‚ฌ๊ฐํ˜• ๋Œ€์ฒด ๋ฌธ์ž๊ฐ€ ์•„๋‹Œ SVG Mask๋กœ ๋ณด์—ฌ์•ผ ํ•œ๋‹ค. +- ์‚ฌ์šฉ์ž ํ”„๋กœํ•„ยท์„ค์ • ํ™”๋ฉด์„ 1,150px ํญ์—์„œ ์Šคํฌ๋กคํ–ˆ์„ ๋•Œ ๊ณ ์ • ๋ฉ”๋‰ด ํญ์ด + 280px๋กœ ์œ ์ง€๋˜๊ณ  ๋ฉ”๋‰ด ์˜ค๋ฅธ์ชฝ ๋์ด ๋ณธ๋ฌธ ์™ผ์ชฝ๋ณด๋‹ค ์ž‘๊ฑฐ๋‚˜ ๊ฐ™์€์ง€ ํ™•์ธํ•œ๋‹ค. - `Answered`, `Best Answer`, `Select Best Answer` ์›๋ฌธ ๋…ธ์ถœ์ด ์—†์–ด์•ผ ํ•œ๋‹ค. - ์‚ฌ์šฉ์žยทํ† ์˜ยท๊ฒŒ์‹œ๋ฌผ ์ˆ˜์™€ ์ฒจ๋ถ€ ํ•ด์‹œ๊ฐ€ ์ ์šฉ ์ „๊ณผ ๊ฐ™์•„์•ผ ํ•œ๋‹ค. @@ -82,4 +198,13 @@ sudo systemctl restart php8.3-fpm nginx - GO: HTTP 200, ํ•œ๊ธ€ ์›๋ฌธ ํ‚ค 0๊ฑด, ํ•ต์‹ฌ ๊ธฐ๋Šฅ ์ •์ƒ, ์ฝ˜ํ…์ธ ยท์ฒจ๋ถ€ ๋ถˆ๋ณ€. - ROLLBACK: ํ™”๋ฉดยท๋ฒˆ์—ญยท๋กœ๊ทธ์ธยท์ž‘์„ฑยท์ฒจ๋ถ€ ์ค‘ ํ•˜๋‚˜๋ผ๋„ ์‹คํŒจ. -- ์šด์˜ ์ ์šฉ ์ „ ํ˜„์žฌ ์ƒํƒœ: Conditional GO, ์Šน์ธ ๋Œ€๊ธฐ. +- 2026-08-20 ์šด์˜ ์ ์šฉ ์™„๋ฃŒ: ์‚ฌ์šฉ์ž ๋ฉ”๋‰ด ๊ฒน์นจ ๋ณด์™„๊ณผ Redditํ˜• ํ‰๋ฉด ํ”ผ๋“œยท๊ฒŒ์‹œ๋ฌผ + ์ •๋ณด ๊ณ„์ธตยท์ด๋ฏธ์ง€ ์ธ๋„ค์ผ, ํ•˜๋‹จ Footer ์ œ๊ฑฐ์™€ ์šฐ์ธก ๊ณ ์ • ํ€ต ๋งํฌ, ๋ฉ”์ธ ํ™˜์˜ ๋ฐฐ๋„ˆ + ์ œ๊ฑฐ, Flarum ์„ธ๋กœ ํƒ์ƒ‰ ๋ณต์›๊ณผ 1165px ์ƒ์„ธ ์…ธ ์ •๋ ฌ์ด WSL ๋ฐ ์šด์˜์— ๋ฐ˜์˜๋˜์—ˆ๋‹ค. + ์ตœ์‹  ์šด์˜ ๋ฐฑ์—…์€ + `/var/backups/techflow-flarum/theme-infinite-emoji-dialog-final-20260820T1430KST`์— + ๋ณด์กดํ•œ๋‹ค. ๋ชฉ๋ก ์ž๋™ ๋กœ๋”ฉ, ํ•œ๊ธ€ ์ด๋ชจ์ง€ ์‚ฝ์ž…, ์ƒˆ ํ† ๋ก  ์ค‘์•™ ๋Œ€ํ™”์ƒ์ž๊นŒ์ง€ ์šด์˜ + ๋ธŒ๋ผ์šฐ์ €์—์„œ ํ™•์ธํ–ˆ๋‹ค. ๋ชจ๋ฐ”์ผ ๋ฉ”์ธยทDrawerยท๋Œ€ํ™”์ƒ์ž ๋ณด์™„์˜ ์ตœ์‹  ๋ฐฑ์—…์€ + `/var/backups/techflow-flarum/theme-mobile-submenu-reply-20260820T151526KST`์ด๋ฉฐ, + 390ร—844 ์šด์˜ ๋ธŒ๋ผ์šฐ์ €์—์„œ Drawer ํ•˜์œ„ ๋ฉ”๋‰ด ์ „ํ™˜๊ณผ ์ƒˆ ํ† ๋ก ยท๋Œ“๊ธ€ ์ž‘์„ฑ๊ธฐ ๋‹ซ๊ธฐ๋ฅผ + ํ™•์ธํ–ˆ๋‹ค.