un2266 commited on
Commit
ceffa8f
·
1 Parent(s): 75fea30

排版重構:徹底清除頂部列/底部連結周圍殘留留白

Browse files

上一個 commit(9b8d839)只解決了「捲動時被拖走」的問題,千卉重新整理
後回報「上下還是有空白」並附截圖標出頂部/底部兩塊實際留白區域,這次
才是真正把空白清零。

逐層用 devtools 量 computed style 揪出三層獨立成因,缺一個都還會留下
可見空白:

1. `.app`/`.gradio-container` 本身還有上下 padding(16px/24px):
之前為了「左右貼齊邊界」只清過左右方向,這次一併清掉上下方向;
`.app-header` 自己的 margin-top(1rem) 也拿掉。

2. 手機漢堡選單的兩個橋接元件(mobile_nav_toggle_html/
nav_backdrop_html):內層內容雖然 display:none,但 Gradio 自動包的
wrapper(帶 "padded" class)本身有約 20px 的空氣行高,即使內容是
display:none 也不會消失,兩個相加 40px。第一輪嘗試用
elem_classes+display:contents 處理,結果沒用——排查後發現這版
Gradio 的 elem_classes 會同時蓋在外層 .block 跟內層 .prose 兩層
div 上(本檔 8-B 就記過這個坑,這次加新元件時忘了套用),兩層
display:contents 疊加沒有真的清零。改用 elem_id 直接鎖定 wrapper、
寫死 display:none,JS 對應方式也從切內層元素改成切 wrapper 本身。

3. 光 display:none 還不夠:wrapper 被 JS 切成 display:block 讓手機
斷點能用時,即使自己 height:0,只要還是「排版流程內」的 flex 子項,
外層 column 的 gap:16px 照樣在它前後插入間距(CSS flexbox 的 gap
只認 display:none/不算 box 高度)——這是全新踩坑類別,跟先前任何
一次 flex-wrap/min-width 的坑都不同源。用 devtools 逐一
display:none 排查(不是用 display:contents 或 height:0 這種治標
猜測)才鎖定真因。修法:桌面寬度(>900px)用 media query +
!important 強制這兩個 wrapper 永遠 display:none,蓋過 JS 可能設的
inline display:block,只有手機寬度才放行讓 JS 判斷生效。

4. 最後 10px 殘留來自 Gradio 幫每個 gr.HTML() 元件包的 .html-container
通用 padding,用 `.html-container:has(.app-header)` 精準只清掉包住
頂部列的那一個,不影響 canvas-col 裡其他 gr.HTML()(如
computing_html)。

已用 Playwright 驗證:headerTop 與 footerBottom 現在精確等於 0/
viewport 高度,桌面/手機皆確認;手機漢堡按鈕、抽屜開關、開始新分析
reset 流程全部重測一輪皆正常;console 0 error。

Files changed (1) hide show
  1. app_agent.py +117 -14
app_agent.py CHANGED
@@ -1842,7 +1842,7 @@ _CUSTOM_CSS = """
1842
  width: 100% !important;
1843
  max-width: 100% !important;
1844
  margin: 0 !important;
1845
- padding: 0 0 1.5rem !important;
1846
  }
1847
  /* .gradio-container 內部還包了一層 Gradio 自己的 .app(class="main fillable app")
1848
  wrapper。原本以為是它的左右 padding 造成貼不到邊界,歸零後留白仍在;
@@ -1851,10 +1851,13 @@ _CUSTOM_CSS = """
1851
  設了 `max-width:1280px` 搭配 auto 置中的 margin-left(量到 77.5px =
1852
  (1435-1280)/2,抓包抓得很準),是 margin 不是 padding。真正修法是拿掉
1853
  max-width 限制、margin 歸零,padding 那條規則留著無害但其實沒解決過
1854
- 問題。 */
 
 
 
 
1855
  .app {
1856
- padding-left: 0 !important;
1857
- padding-right: 0 !important;
1858
  max-width: 100% !important;
1859
  margin-left: 0 !important;
1860
  margin-right: 0 !important;
@@ -1903,6 +1906,29 @@ gradio-app {
1903
  footer {
1904
  flex-shrink: 0 !important;
1905
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1906
  *, *::before, *::after { box-sizing: border-box; }
1907
  /* 全域安全網(陷阱 13 第 3 點):Gradio 的 markdown 渲染統一包在
1908
  <span class="md ...">,預設是 inline 卻常常裝 block 層級的 <p>/<ul> 等,
@@ -1920,7 +1946,7 @@ footer {
1920
  text-align: left;
1921
  padding: .85rem 1.25rem;
1922
  position: relative;
1923
- margin: 1rem 0 1.25rem;
1924
  border: 1px solid var(--c-border);
1925
  border-radius: 16px;
1926
  background: linear-gradient(180deg, rgba(var(--c-accent-rgb),.07) 0%, rgba(var(--c-bg-rgb),.45) 100%);
@@ -2359,7 +2385,7 @@ div[role="log"][aria-label="chatbot conversation"],
2359
  display: flex !important;
2360
  flex-direction: column !important;
2361
  gap: .6rem !important;
2362
- height: calc(100vh - 150px) !important;
2363
  overflow-y: auto !important;
2364
  overflow-x: hidden !important;
2365
  }
@@ -2374,7 +2400,7 @@ div[role="log"][aria-label="chatbot conversation"],
2374
  flex-direction: column !important;
2375
  flex-wrap: nowrap !important;
2376
  gap: 1rem !important;
2377
- height: calc(100vh - 150px) !important;
2378
  overflow: hidden !important;
2379
  min-width: 0 !important;
2380
  }
@@ -2404,13 +2430,21 @@ div[role="log"][aria-label="chatbot conversation"],
2404
  flex-direction: column !important;
2405
  flex-wrap: nowrap !important;
2406
  gap: 0 !important;
2407
- height: calc(100vh - 150px) !important;
2408
  overflow: hidden !important;
2409
  min-width: 0 !important;
2410
  }
2411
  .canvas-col .app-header {
2412
  flex-shrink: 0 !important;
2413
  }
 
 
 
 
 
 
 
 
2414
  .canvas-scroll {
2415
  display: flex !important;
2416
  flex-direction: column !important;
@@ -2480,8 +2514,46 @@ div[role="log"][aria-label="chatbot conversation"],
2480
  }
2481
  /* ── 5d. HOMEPAGE(Phase 8:首頁 4 張情境卡片)──────────────────── */
2482
  /* 隱藏版情境���鈕:純作 JS 橋接目標(見 _APP_HEAD_SCRIPT),
2483
- 點卡片=模擬點這裡的按鈕,Python 端 .click() 綁定完全不動 */
 
 
 
2484
  .sc-bridge-btn { display: none !important; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2485
 
2486
  .homepage-col { max-width: 50rem; margin: 2.5rem auto 3rem; padding: 0 1rem; }
2487
  .homepage-hero { text-align: center; margin-bottom: 2.4rem; }
@@ -3574,9 +3646,16 @@ _APP_HEAD_SCRIPT = """
3574
  當下)nav-col 根本還不存在,套不上。都改成搭這裡既有的
3575
  setInterval 一起輪詢,不額外開新計時器。 */
3576
  function _syncMobileHamburger() {
3577
- var hamburger = document.getElementById('mobile_nav_toggle');
3578
- if (!hamburger) return;
3579
- hamburger.style.display = document.querySelector('.nav-col') ? '' : 'none';
 
 
 
 
 
 
 
3580
  }
3581
  function _initNavCollapseIfNeeded() {
3582
  var navCol = document.querySelector('.nav-col');
@@ -3709,11 +3788,35 @@ def create_app():
3709
  # display:none,只在手機寬度且 workspace 啟用時由 JS 顯示(見
3710
  # _APP_HEAD_SCRIPT 的 _syncMobileHamburger()),桌面寬度/首頁
3711
  # 狀態下完全不佔版面也不可點。
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3712
  mobile_nav_toggle_html = gr.HTML(
3713
  f'<button type="button" id="mobile_nav_toggle" class="mobile-nav-toggle" '
3714
- f'aria-label="開啟導覽選單" aria-expanded="false">{_icon("bars-3", 22)}</button>'
 
 
 
 
 
3715
  )
3716
- nav_backdrop_html = gr.HTML('<div id="nav_backdrop" class="nav-backdrop"></div>')
3717
 
3718
  # Phase 8-B:三欄式工作區——nav-col(窄,導覽/重置)+chat-col
3719
  # (對話視窗+輸入框,獨立捲動)+canvas-col(步驟列+上傳/表單/
 
1842
  width: 100% !important;
1843
  max-width: 100% !important;
1844
  margin: 0 !important;
1845
+ padding: 0 !important;
1846
  }
1847
  /* .gradio-container 內部還包了一層 Gradio 自己的 .app(class="main fillable app")
1848
  wrapper。原本以為是它的左右 padding 造成貼不到邊界,歸零後留白仍在;
 
1851
  設了 `max-width:1280px` 搭配 auto 置中的 margin-left(量到 77.5px =
1852
  (1435-1280)/2,抓包抓得很準),是 margin 不是 padding。真正修法是拿掉
1853
  max-width 限制、margin 歸零,padding 那條規則留著無害但其實沒解決過
1854
+ 問題。
1855
+ 後續(同日):千卉截圖標出頂部/底部還有大片空白,實測 .app 本身還有
1856
+ `padding:16px 0px`(上下各16px,之前只清過左右)、.gradio-container
1857
+ 也還有 `padding:0 0 24px`(底部24px)——這兩個之前為了「左右貼齊」
1858
+ 只處理過左右方向,上下方向從沒動過,這次一併歸零。 */
1859
  .app {
1860
+ padding: 0 !important;
 
1861
  max-width: 100% !important;
1862
  margin-left: 0 !important;
1863
  margin-right: 0 !important;
 
1906
  footer {
1907
  flex-shrink: 0 !important;
1908
  }
1909
+ /* 千卉截圖回報頂部列還是沒有真正貼齊頂端,量到 main-layout-row 距離
1910
+ .contain 頂端還有 72px 空隙——追出來是 .nav-col/.chat-col/.canvas-col
1911
+ 原本寫死的 `calc(100vh - 150px)` 這個 magic number,是照舊版「頁面
1912
+ 周圍還有一堆 padding/margin」的年代校準出來的高度預算。這次把
1913
+ .app/.gradio-container 的上下 padding 都清空、footer 也鎖定
1914
+ flex-shrink:0 之後,實際可用空間比 150px 這個預留值還多,多出來的
1915
+ 空間沒地方去,就變成三欄跟外層容器之間看得到的空隙。
1916
+ 修法:不要再猜一個固定 px 預算,改成整條鏈都用 flex 100% 往下傳,
1917
+ 有多少空間就填多少,不會再跟實際版面脫節。.contain 底下只有一個
1918
+ 直屬子元素(Gradio Blocks 的根 Column),這裡讓它 flex 撐滿
1919
+ .contain,裡面的 main-layout-row 再 flex 撐滿它,nav-col/chat-col/
1920
+ canvas-col 三欄改成 height:100%(相對 main-layout-row 的高度),
1921
+ 不再各自寫死 calc(100vh - 150px)。 */
1922
+ .contain > .column {
1923
+ display: flex !important;
1924
+ flex-direction: column !important;
1925
+ height: 100% !important;
1926
+ min-height: 0 !important;
1927
+ }
1928
+ .main-layout-row {
1929
+ flex: 1 1 auto !important;
1930
+ min-height: 0 !important;
1931
+ }
1932
  *, *::before, *::after { box-sizing: border-box; }
1933
  /* 全域安全網(陷阱 13 第 3 點):Gradio 的 markdown 渲染統一包在
1934
  <span class="md ...">,預設是 inline 卻常常裝 block 層級的 <p>/<ul> 等,
 
1946
  text-align: left;
1947
  padding: .85rem 1.25rem;
1948
  position: relative;
1949
+ margin: 0 0 1.25rem;
1950
  border: 1px solid var(--c-border);
1951
  border-radius: 16px;
1952
  background: linear-gradient(180deg, rgba(var(--c-accent-rgb),.07) 0%, rgba(var(--c-bg-rgb),.45) 100%);
 
2385
  display: flex !important;
2386
  flex-direction: column !important;
2387
  gap: .6rem !important;
2388
+ height: 100% !important;
2389
  overflow-y: auto !important;
2390
  overflow-x: hidden !important;
2391
  }
 
2400
  flex-direction: column !important;
2401
  flex-wrap: nowrap !important;
2402
  gap: 1rem !important;
2403
+ height: 100% !important;
2404
  overflow: hidden !important;
2405
  min-width: 0 !important;
2406
  }
 
2430
  flex-direction: column !important;
2431
  flex-wrap: nowrap !important;
2432
  gap: 0 !important;
2433
+ height: 100% !important;
2434
  overflow: hidden !important;
2435
  min-width: 0 !important;
2436
  }
2437
  .canvas-col .app-header {
2438
  flex-shrink: 0 !important;
2439
  }
2440
+ /* 最後一段殘餘留白(10px):Gradio 幫所有 gr.HTML() 元件的內容包了一層
2441
+ .html-container,本身有 padding:10px 12px,這是全站每個 gr.HTML()
2442
+ 共用的預設樣式(首頁卡片、運算中提示等都靠它),不能整個歸零,只精準
2443
+ 鎖定「包住 .app-header 的那一個」html-container,不影響其他 gr.HTML()
2444
+ 元件(例如同樣在 canvas-col 裡的 computing_html)。 */
2445
+ .html-container:has(.app-header) {
2446
+ padding: 0 !important;
2447
+ }
2448
  .canvas-scroll {
2449
  display: flex !important;
2450
  flex-direction: column !important;
 
2514
  }
2515
  /* ── 5d. HOMEPAGE(Phase 8:首頁 4 張情境卡片)──────────────────── */
2516
  /* 隱藏版情境���鈕:純作 JS 橋接目標(見 _APP_HEAD_SCRIPT),
2517
+ 點卡片=模擬點這裡的按鈕,Python 端 .click() 綁定完全不動。
2518
+ gr.Button 的 elem_classes 直接掛在 <button> 標籤本身(不像
2519
+ gr.HTML() 會多一層 wrapper),所以這裡單純 display:none 就完全
2520
+ 不佔版面,沒有下面 .nav-html-bridge 那個問題。 */
2521
  .sc-bridge-btn { display: none !important; }
2522
+ /* 2026-07-23 踩坑:gr.HTML() 元件的實際內容跟 Gradio 自動產生的外層
2523
+ wrapper(.block)是兩個不同的 DOM 節點——只把內層內容(例如
2524
+ #mobile_nav_toggle/#nav_backdrop)設 display:none,wrapper 本身
2525
+ 還是會依自己的預設 min-height/padding 佔用版面高度,疊加上跟相鄰
2526
+ 元件的 flex gap,變成看得到但摸不著原因的空白區塊(千卉截圖回報
2527
+ 頂部/底部大片空白時發現)。第一輪試過 display:contents 沒有真的
2528
+ 清零(懷疑跟 elem_classes 同時疊在 .block 與 .prose 兩層有關,
2529
+ detail 見 create_app() 裡兩個 gr.HTML() 呼叫旁的完整記錄),改用
2530
+ 最直接可靠的做法:wrapper 直接用 elem_id 鎖定、預設寫死
2531
+ display:none——一般 block 元素包一個 display:none 的子元素天生
2532
+ 零高度,不用依賴 display:contents 這種較冷門的屬性。JS 對應改成
2533
+ 切換這兩個 wrapper 本身的 display(見 _APP_HEAD_SCRIPT 的
2534
+ _syncMobileHamburger()),內層 #mobile_nav_toggle/#nav_backdrop
2535
+ 自己原本的桌面/手機斷點 CSS 完全不用動。 */
2536
+ /* 兩層踩坑疊在一起:①這個 wrapper 帶 Gradio 自己的 "padded" class,
2537
+ 就算內層內容 display:none,wrapper 本身還是會因為 line-height留下
2538
+ 約20px空氣行高②就算加 height:0 讓 wrapper 自己不佔高度,JS 為了
2539
+ 手機斷點把它切成 display:block 時,它仍然是「有在排版流程裡」的
2540
+ flex 子項,外層 column 的 gap:16px 還是會在它前後插入間距——CSS
2541
+ flexbox 的 gap 只認「這個子項是不是 display:none」,不管它自己的
2542
+ box 高度是不是 0。用 devtools 逐一 display:none 排查才抓到這一層,
2543
+ 單靠 height:0 治標不治本。
2544
+ 真正修法:桌面寬度(>900px)這兩個 wrapper 必須是貨真價實的
2545
+ display:none(徹底退出 flex 排版,不只是零高度),用 !important
2546
+ 蓋掉 JS 可能設的 inline display:block;手機寬度(<=900px)才放行讓
2547
+ JS 的 block/none 判斷生效。這樣桌面寬度不管 nav-col 存不存在,這兩個
2548
+ wrapper 永遠不佔用任何 flex 空間。 */
2549
+ #mobile_nav_toggle_wrap, #nav_backdrop_wrap {
2550
+ display: none;
2551
+ }
2552
+ @media (min-width: 901px) {
2553
+ #mobile_nav_toggle_wrap, #nav_backdrop_wrap {
2554
+ display: none !important;
2555
+ }
2556
+ }
2557
 
2558
  .homepage-col { max-width: 50rem; margin: 2.5rem auto 3rem; padding: 0 1rem; }
2559
  .homepage-hero { text-align: center; margin-bottom: 2.4rem; }
 
3646
  當下)nav-col 根本還不存在,套不上。都改成搭這裡既有的
3647
  setInterval 一起輪詢,不額外開新計時器。 */
3648
  function _syncMobileHamburger() {
3649
+ /* 2026-07-23:改成切換 wrapper(#mobile_nav_toggle_wrap/
3650
+ #nav_backdrop_wrap)本身的 display,不是內層按鈕/遮罩自己
3651
+ 的 style——wrapper 預設 display:none 才能真正零高度(見
3652
+ CSS 對應註解),內層元素桌面/手機斷點的顯示邏輯完全不變,
3653
+ 這裡只負責「nav-col 存不存在」這個外層開關。 */
3654
+ var navColExists = !!document.querySelector('.nav-col');
3655
+ var toggleWrap = document.getElementById('mobile_nav_toggle_wrap');
3656
+ var backdropWrap = document.getElementById('nav_backdrop_wrap');
3657
+ if (toggleWrap) toggleWrap.style.display = navColExists ? 'block' : 'none';
3658
+ if (backdropWrap) backdropWrap.style.display = navColExists ? 'block' : 'none';
3659
  }
3660
  function _initNavCollapseIfNeeded() {
3661
  var navCol = document.querySelector('.nav-col');
 
3788
  # display:none,只在手機寬度且 workspace 啟用時由 JS 顯示(見
3789
  # _APP_HEAD_SCRIPT 的 _syncMobileHamburger()),桌面寬度/首頁
3790
  # 狀態下完全不佔版面也不可點。
3791
+ # 2026-07-23 踩坑(分兩輪才修對):這兩個元件的實際內容
3792
+ # (#mobile_nav_toggle/#nav_backdrop)本身雖然預設 display:none,
3793
+ # 但那只是隱藏「內層」元素——Gradio 自動包在 gr.HTML() 外面的
3794
+ # wrapper(.block)沒有被一併處理,仍然佔用版面高度+跟相鄰元件的
3795
+ # flex gap,變成看得到但摸不著原因的空白區塊(千卉截圖回報頂部/
3796
+ # 底部有大片空白才發現)。
3797
+ # 第一輪嘗試:elem_classes 掛 nav-html-bridge,CSS 設
3798
+ # `display:contents`,理論上該讓 wrapper 不產生盒模型——實測
3799
+ # 沒用,逐一 display:none 排查後才發現:這版 Gradio 的
3800
+ # elem_classes 會同時蓋在外層 .block wrapper 跟內層 .prose
3801
+ # wrapper 兩層 div 上(這個踩坑本檔 8-B 就記過一次「.block.<class>
3802
+ # 跟.prose.<class>」的雙重疊加,這次加新元件時忘了套用同一個
3803
+ # 教訓),兩層 display:contents 疊加後不知為何沒有真的清零
3804
+ # flex gap 佔用的空間。
3805
+ # 第二輪改用最直接可靠的做法:wrapper 用 elem_id 直接鎖定,
3806
+ # 預設寫死 display:none(一般 block 元素包一個 display:none 的
3807
+ # 子元素,天生就是零高度,不用依賴 display:contents 這種比較
3808
+ # 少人踩過的屬性),JS 改成切換這個 wrapper 本身的 display,
3809
+ # 內層 #mobile_nav_toggle/#nav_backdrop 自己的 CSS(桌面/手機
3810
+ # 斷點判斷)完全不用動,兩層互不干擾。
3811
  mobile_nav_toggle_html = gr.HTML(
3812
  f'<button type="button" id="mobile_nav_toggle" class="mobile-nav-toggle" '
3813
+ f'aria-label="開啟導覽選單" aria-expanded="false">{_icon("bars-3", 22)}</button>',
3814
+ elem_id="mobile_nav_toggle_wrap",
3815
+ )
3816
+ nav_backdrop_html = gr.HTML(
3817
+ '<div id="nav_backdrop" class="nav-backdrop"></div>',
3818
+ elem_id="nav_backdrop_wrap",
3819
  )
 
3820
 
3821
  # Phase 8-B:三欄式工作區——nav-col(窄,導覽/重置)+chat-col
3822
  # (對話視窗+輸入框,獨立捲動)+canvas-col(步驟列+上傳/表單/