미디어위키:MainPage.css: 두 판 사이의 차이

(새 문서: →‎========================================= COASTLINE: BLACK ICE - MainPage 대문 전용 스타일 =========================================: /* 설계 목적 ----------------------------------------- 이 파일은 대문 전용 화면 구성만 담당한다. 이전 테스트 단계에서는 MediaWiki:Test.css 안에 대문 레이아웃, 이미지 피드, 방명록 더미, 스타일 매뉴얼, 카테고리 네비 실험 코드가 모두 섞여 있었다. 그 상태로 실...)
 
편집 요약 없음
16번째 줄: 16번째 줄:


1. 어느 코드가 대문 전용인지, 어느 코드가 공통 컴포넌트인지 구분하기 어렵다.
1. 어느 코드가 대문 전용인지, 어느 코드가 공통 컴포넌트인지 구분하기 어렵다.
2. 나중에 일반 문서 네비바나 사이드바를 고칠 때 대문 코드가 같이 영향을 받을 수 있다.
2. 일반 문서 네비바나 사이드바를 고칠 때 대문 코드가 같이 영향을 받을 수 있다.
3. 테스트용 더미와 실제 운영 UI가 같은 파일에 남아 유지보수가 어려워진다.
3. 테스트용 더미와 실제 운영 UI가 같은 파일에 남아 유지보수가 어려워진다.


94번째 줄: 94번째 줄:


따라서 본문에는 비어 있는 mount만 두고,
따라서 본문에는 비어 있는 mount만 두고,
CategoryNav.js가 실제 <a class="..."> 요소들을 생성한다.
CategoryNav.js가 실제 네비 DOM을 생성한다.
 
이렇게 하면 하나의 <a>가 다음 역할을 동시에 담당한다.
 
1. 보이는 항목
2. 클릭되는 링크
3. 호버되는 면
4. clip-path로 잘린 사선 버튼
 
이 구조가 이번 문제의 핵심 해결책이다.
*/
*/


113번째 줄: 104번째 줄:


/* -----------------------------------------
/* -----------------------------------------
2. Category navigation
2. Category navigation - SVG version
----------------------------------------- */
----------------------------------------- */


/*
/*
카테고리 바의 시각 규칙
왜 SVG로 바꿨는가
-----------------------------------------
- 전체는 하나의 긴 바처럼 보여야 한다.
- 각 항목은 독립 버튼처럼 외장을 가지면 안 된다.
- 부모 .portal-category-nav가 표면, 테두리, 상하 입체감을 담당한다.
- 각 .portal-cat-link는 클릭 영역과 호버 영역만 담당한다.
- 왼쪽 3개 항목은 \ 방향으로 잘린다.
- 가운데 프로젝트는 \ PROJECT / 형태다.
- 오른쪽 3개 항목은 / 방향으로 잘린다.
- 검은 획은 항목 외장이 아니라 구분선이다.
 
이전 실패 기록
-----------------------------------------
-----------------------------------------
1. span 안에 [[위키링크]]를 넣고 span에 clip-path를 주는 방식:
이전 CSS 박스 방식은 계속 같은 문제가 생겼다.
  MediaWiki가 만든 <a>와 span이 분리되어 클릭/호버/시각 범위가 계속 엇갈렸다.


2. ::after로 사선 획을 각 버튼 내부에 넣는 방식:
1. clip-path를 span에 걸면 MediaWiki가 생성한 <a>와
  clip-path가 ::after까지 잘라먹어서 왼쪽 획이 사라지거나 위치가 어긋났다.
  시각적 버튼이 분리된다.
2. ::after로 검은 획을 만들면 clip-path가 획까지 잘라먹는다.
3. 획과 호버를 별도 overlay로 만들면 좌표가 다시 어긋난다.
4. 음수 margin으로 버튼을 겹치면 호버가 옆 칸으로 넘어간다.


3. 버튼끼리 음수 margin으로 겹치는 방식:
SVG 방식에서는 각 항목이 실제 polygon이다.
  빈 사선 영역은 메워졌지만 호버 배경이 옆 칸으로 넘어갔다.


4. 호버와 획을 별도 overlay로 분리하는 방식:
즉,
  JS 없이 좌표를 수동 계산해야 해서 프로젝트 폭이 바뀔 때 유지보수가 어려웠다.
- 보이는 면
- 호버되는 면
- 클릭되는 면
- 검은 구분 획


이번 구조
이 모두가 같은 SVG 좌표계 안에서 처리된다.
-----------------------------------------
JS가 실제 <a class="portal-cat-link ...">를 생성한다.
각 링크 자체에 clip-path와 :hover를 적용한다.
따라서 보이는 모양, 클릭 범위, 호버 범위가 같은 요소에 묶인다.
*/
*/


.portal-category-nav {
.portal-category-nav {
--cat-cell-w:163px;
--cat-cut:10px;
position:relative;
position:relative;
display:grid;
grid-template-columns:
var(--cat-cell-w)
var(--cat-cell-w)
var(--cat-cell-w)
minmax(180px,1fr)
var(--cat-cell-w)
var(--cat-cell-w)
var(--cat-cell-w);
width:100%;
width:100%;
height:var(--nav-h);
height:var(--nav-h);
182번째 줄: 150번째 줄:


/*
/*
각 링크는 실제 버튼이다.
SVG는 JS가 현재 nav 폭을 읽어서 viewBox를 다시 계산한다.
 
따라서 CSS에서 임의로 좌표를 계산하지 않는다.
여기서는 개별 버튼 외장을 만들지 않는다.
background는 평소 transparent이고, hover 때만 아주 약하게 밝아진다.
개별 border, 개별 box-shadow를 넣으면 7개의 독립 버튼처럼 보이므로 금지한다.
*/
*/


.portal-cat-link {
.portal-category-svg {
position:relative;
display:block;
display:flex !important;
width:100%;
align-items:center !important;
height:100%;
justify-content:center !important;
height:var(--nav-h);
min-width:0;
padding:0 10px !important;
box-sizing:border-box !important;
 
background:transparent;
border:none;
box-shadow:none;
 
color:var(--text-main) !important;
font-size:10px !important;
font-weight:700 !important;
line-height:1.2 !important;
text-align:center !important;
text-decoration:none !important;
white-space:nowrap;
overflow:hidden;
text-overflow:ellipsis;
 
text-shadow:
1px 1px 0 #000,
0 0 2px rgba(0,0,0,0.85) !important;
}
}


/*
/*
중앙 프로젝트는 의미상 허브이므로 글자만 살짝 키운다.
각 항목의 polygon은 기본적으로 투명하다.
표면이나 색으로 강조하지 않는다.
표면은 부모 .portal-category-nav가 담당한다.
*/


.portal-cat-project {
호버 시에만 아주 약한 밝기 레이어를 polygon에 입힌다.
font-size:11px !important;
이렇게 해야 독립 버튼 외장처럼 보이지 않는다.
letter-spacing:0.2px;
}
 
/*
사선 형태
-----------------------------------------
왼쪽군:
- 오른쪽 경계가 \ 방향으로 깎인다.
 
프로젝트:
- 왼쪽 경계는 \ 쪽과 맞물린다.
- 오른쪽 경계는 / 쪽과 맞물린다.
 
오른쪽군:
- 왼쪽 경계가 / 방향으로 깎인다.
 
첫 항목과 마지막 항목은 바깥 프레임과 맞닿으므로
바깥쪽은 직각으로 유지한다.
*/
*/


.portal-cat-left-start {
.portal-cat-shape {
clip-path:polygon(
fill:transparent;
0 0,
stroke:none;
calc(100% - var(--cat-cut)) 0,
pointer-events:all;
100% 100%,
0 100%
);
}
}


.portal-cat-left-mid,
.portal-cat-anchor:hover .portal-cat-shape {
.portal-cat-left-end {
fill:rgba(255,255,255,0.035);
clip-path:polygon(
0 0,
calc(100% - var(--cat-cut)) 0,
100% 100%,
var(--cat-cut) 100%
);
}
}


.portal-cat-project {
.portal-cat-anchor:active .portal-cat-shape {
clip-path:polygon(
fill:rgba(0,0,0,0.20);
0 0,
100% 0,
calc(100% - var(--cat-cut)) 100%,
var(--cat-cut) 100%
);
}
}


.portal-cat-right-start,
/*
.portal-cat-right-mid {
텍스트는 polygon 위에 얹힌다.
clip-path:polygon(
pointer-events를 끄는 이유는 클릭/호버 판정을 polygon이 담당하게 하기 위해서다.
var(--cat-cut) 0,
텍스트가 별도 판정면이 되면 작은 위치 차이로 호버가 끊기는 현상이 생길 수 있다.
100% 0,
*/
calc(100% - var(--cat-cut)) 100%,
0 100%
);
}


.portal-cat-right-end {
.portal-cat-label {
clip-path:polygon(
fill:var(--text-main);
var(--cat-cut) 0,
font-size:10px;
100% 0,
font-weight:700;
100% 100%,
font-family:inherit;
0 100%
text-anchor:middle;
);
dominant-baseline:middle;
pointer-events:none;
text-shadow:
1px 1px 0 #000,
0 0 2px rgba(0,0,0,0.85);
}
}


/*
.portal-cat-anchor:hover .portal-cat-label {
호버
fill:#ffffff;
-----------------------------------------
호버는 각 <a>에 직접 적용한다.
이전처럼 별도 overlay를 좌표로 맞추는 방식은 쓰지 않는다.
그래야 화면 크기와 프로젝트 폭이 바뀌어도 호버 범위가 유지된다.
*/
 
.portal-cat-link:hover {
background:rgba(255,255,255,0.035);
color:#ffffff !important;
text-decoration:none !important;
}
}


.portal-cat-link:active {
.portal-cat-project-label {
background:rgba(0,0,0,0.20);
font-size:11px;
padding-top:1px !important;
letter-spacing:0.2px;
}
}


/*
/*
검은 구분 획
검은 획은 SVG line이다.
-----------------------------------------
polygon 안에 넣지 않고, 같은 svg 좌표계 안의 별도 line으로 그린다.
획은 각 링크 안에서 그린다.


이전에는 clip-path가 ::after를 잘라먹어서
이전 CSS 방식의 실패:
왼쪽 항목의 획이 사라지는 문제가 있었다.
- 버튼 내부 ::after는 clip-path에 잘렸다.
이번 구조에서는 <a> 자체가 링크이므로 시각과 호버가 일치하고,
- absolute overlay는 CSS grid 좌표와 프로젝트 가변폭 때문에 어긋났다.
획도 버튼 내부에서 같은 clip-path 기준으로 움직인다.


다만 획이 지나치게 두꺼우면 '틈'처럼 보이므로 1px로 둔다.
SVG line은 polygon 좌표와 같은 값으로 생성되므로,
사선 면과 획이 같은 기준을 사용한다.
*/
*/


.portal-cat-link::after {
.portal-cat-divider {
content:"";
stroke:#050505;
position:absolute;
stroke-width:1;
top:-5px;
shape-rendering:crispEdges;
bottom:-5px;
vector-effect:non-scaling-stroke;
right:calc(var(--cat-cut) - 1px);
width:1px;
z-index:3;
pointer-events:none;
pointer-events:none;
background:#050505;
opacity:0.96;
display:none;
transform-origin:top center;
}
.portal-cat-left-start::after,
.portal-cat-left-mid::after,
.portal-cat-left-end::after {
display:block;
transform:skewX(18.5deg);
}
.portal-cat-project::after,
.portal-cat-right-start::after,
.portal-cat-right-mid::after {
display:block;
right:0;
transform:skewX(-18.5deg);
}
}


/*
/*
반응형
좁은 화면에서는 SVG 사선 네비가 과밀해질 수 있다.
-----------------------------------------
일단 높이는 유지하고, JS가 폭에 따라 각 칸을 줄인다.
좁은 화면에서는 사선 구조를 유지하면 읽기가 어려워진다.
나중에 모바일 전용 대문이 필요하면 여기서 별도 compact nav로 분기한다.
대문용 큰 네비의 축소판을 억지로 유지하지 않고,
한 줄씩 쌓이는 일반 버튼 목록으로 전환한다.
*/
*/
@media screen and (max-width:1180px) {
.portal-category-nav {
height:auto;
grid-template-columns:minmax(0,1fr);
}
.portal-cat-link,
.portal-cat-project {
height:var(--nav-h);
clip-path:none;
}
.portal-cat-link::after {
display:none !important;
}
}

2026년 5월 19일 (화) 07:45 판

/* =========================================
COASTLINE: BLACK ICE - MainPage
대문 전용 스타일
========================================= */

/*
설계 목적
-----------------------------------------
이 파일은 대문 전용 화면 구성만 담당한다.

이전 테스트 단계에서는 MediaWiki:Test.css 안에
대문 레이아웃, 이미지 피드, 방명록 더미, 스타일 매뉴얼,
카테고리 네비 실험 코드가 모두 섞여 있었다.

그 상태로 실사용에 넣으면 다음 문제가 생긴다.

1. 어느 코드가 대문 전용인지, 어느 코드가 공통 컴포넌트인지 구분하기 어렵다.
2. 일반 문서 네비바나 사이드바를 고칠 때 대문 코드가 같이 영향을 받을 수 있다.
3. 테스트용 더미와 실제 운영 UI가 같은 파일에 남아 유지보수가 어려워진다.

따라서 이 파일은 대문 본문 안에서 쓰는
.main-portal 스코프 내부만 담당한다.

주의:
- body, .liberty-content, .content-wrapper 같은 전역 레이아웃은 건드리지 않는다.
- 상단 공통 네비바, 좌우 사이드바, 관리자 패널은 이 파일의 책임이 아니다.
- 카테고리 네비는 JS가 DOM을 생성하고, 이 파일은 그 결과물의 모양만 담당한다.
*/

/* -----------------------------------------
0. Main portal scope
----------------------------------------- */

.main-portal {
--surface-page:#070707;
--surface-frame:#101010;
--surface-frame-soft:#141414;
--surface-title:#1d1d1d;
--surface-title-hover:#252525;
--surface-well:#080808;

--edge-top:#555555;
--edge-side:#2b2b2b;
--edge-bottom:#050505;
--edge-soft:#202020;

--text-main:#e2e2e2;
--text-soft:#c8c8c8;
--text-dim:#8a8a8a;
--text-faint:#626262;

--space-block:8px;
--space-inner:6px;
--pad-panel:8px;

--nav-h:30px;

width:100%;
margin:0 auto;
padding:var(--space-block);
box-sizing:border-box;
background:var(--surface-page);
color:var(--text-main);
font-size:11px;
line-height:1.5;
}

.main-portal *,
.main-portal *::before,
.main-portal *::after {
box-sizing:border-box;
}

.main-portal a {
color:var(--text-main) !important;
text-decoration:none !important;
}

.main-portal a:hover {
color:#ffffff !important;
text-decoration:none !important;
}

/* -----------------------------------------
1. Category nav mount
----------------------------------------- */

/*
[data-component="category-nav"]는 본문에 남겨두는 자리 표시자다.

본문에 위키문법으로 링크를 직접 조립하면,
MediaWiki가 생성한 <a>와 바깥 span이 분리된다.
그 상태에서는 실제 클릭 영역, 호버 영역, 사선 모양이 서로 어긋난다.

따라서 본문에는 비어 있는 mount만 두고,
CategoryNav.js가 실제 네비 DOM을 생성한다.
*/

.main-portal [data-component="category-nav"] {
display:block;
width:100%;
margin:0 0 var(--space-block);
}

/* -----------------------------------------
2. Category navigation - SVG version
----------------------------------------- */

/*
왜 SVG로 바꿨는가
-----------------------------------------
이전 CSS 박스 방식은 계속 같은 문제가 생겼다.

1. clip-path를 span에 걸면 MediaWiki가 생성한 <a>와
   시각적 버튼이 분리된다.
2. ::after로 검은 획을 만들면 clip-path가 획까지 잘라먹는다.
3. 획과 호버를 별도 overlay로 만들면 좌표가 다시 어긋난다.
4. 음수 margin으로 버튼을 겹치면 호버가 옆 칸으로 넘어간다.

SVG 방식에서는 각 항목이 실제 polygon이다.

즉,
- 보이는 면
- 호버되는 면
- 클릭되는 면
- 검은 구분 획

이 모두가 같은 SVG 좌표계 안에서 처리된다.
*/

.portal-category-nav {
position:relative;
width:100%;
height:var(--nav-h);
margin:0;
padding:0;
overflow:hidden;

background:var(--surface-frame-soft);
border:1px solid;
border-color:
var(--edge-top)
var(--edge-side)
var(--edge-bottom)
var(--edge-side);
box-shadow:
inset 0 1px 0 rgba(255,255,255,0.055),
inset 0 -1px 0 rgba(0,0,0,0.72);
}

/*
SVG는 JS가 현재 nav 폭을 읽어서 viewBox를 다시 계산한다.
따라서 CSS에서 임의로 좌표를 계산하지 않는다.
*/

.portal-category-svg {
display:block;
width:100%;
height:100%;
}

/*
각 항목의 polygon은 기본적으로 투명하다.
표면은 부모 .portal-category-nav가 담당한다.

호버 시에만 아주 약한 밝기 레이어를 polygon에 입힌다.
이렇게 해야 독립 버튼 외장처럼 보이지 않는다.
*/

.portal-cat-shape {
fill:transparent;
stroke:none;
pointer-events:all;
}

.portal-cat-anchor:hover .portal-cat-shape {
fill:rgba(255,255,255,0.035);
}

.portal-cat-anchor:active .portal-cat-shape {
fill:rgba(0,0,0,0.20);
}

/*
텍스트는 polygon 위에 얹힌다.
pointer-events를 끄는 이유는 클릭/호버 판정을 polygon이 담당하게 하기 위해서다.
텍스트가 별도 판정면이 되면 작은 위치 차이로 호버가 끊기는 현상이 생길 수 있다.
*/

.portal-cat-label {
fill:var(--text-main);
font-size:10px;
font-weight:700;
font-family:inherit;
text-anchor:middle;
dominant-baseline:middle;
pointer-events:none;
text-shadow:
1px 1px 0 #000,
0 0 2px rgba(0,0,0,0.85);
}

.portal-cat-anchor:hover .portal-cat-label {
fill:#ffffff;
}

.portal-cat-project-label {
font-size:11px;
letter-spacing:0.2px;
}

/*
검은 획은 SVG line이다.
polygon 안에 넣지 않고, 같은 svg 좌표계 안의 별도 line으로 그린다.

이전 CSS 방식의 실패:
- 버튼 내부 ::after는 clip-path에 잘렸다.
- absolute overlay는 CSS grid 좌표와 프로젝트 가변폭 때문에 어긋났다.

SVG line은 polygon 좌표와 같은 값으로 생성되므로,
사선 면과 획이 같은 기준을 사용한다.
*/

.portal-cat-divider {
stroke:#050505;
stroke-width:1;
shape-rendering:crispEdges;
vector-effect:non-scaling-stroke;
pointer-events:none;
}

/*
좁은 화면에서는 SVG 사선 네비가 과밀해질 수 있다.
일단 높이는 유지하고, JS가 폭에 따라 각 칸을 줄인다.
나중에 모바일 전용 대문이 필요하면 여기서 별도 compact nav로 분기한다.
*/