@korioinc/next-core 2.0.15 → 2.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -18,8 +18,12 @@ export declare function getPathname({ locale, href }: {
18
18
  }): string;
19
19
  export declare function getLanguageAlternates(pathname?: string): Record<string, string>;
20
20
  export declare function getCurrentPathname(): Promise<string>;
21
- /**
22
- * pathname과 params를 조합하여 전체 URL을 생성하는 함수
23
- */
24
- export declare function getFullUrl(pathname: string, params?: Record<string, string | string[] | undefined> | ReadonlyURLSearchParams | undefined): string;
21
+ export declare function getCurrentSearchParams(): Promise<URLSearchParams>;
22
+ export declare function getDefaultPathname(): Promise<string>;
23
+ export declare function getCurrentLanguageAlternates(): Promise<Record<string, string>>;
24
+ type FullUrlParams = Record<string, string | string[] | undefined> | URLSearchParams | ReadonlyURLSearchParams | undefined;
25
+ export declare function getFullUrlByPathname(pathname: string, params?: FullUrlParams): string;
26
+ export declare function getFullUrl(params?: FullUrlParams): Promise<string>;
27
+ export declare function getFullUrlWithCurrentParams(): Promise<string>;
28
+ export {};
25
29
  //# sourceMappingURL=routing.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../../src/i18n/routing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EAAE,KAAK,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AA6B7D;;GAEG;AACH,wBAAgB,UAAU,IAAI,MAAM,EAAE,CAErC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AA2DD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,WAAW,yBA2EvD;AAED,wBAAgB,gBAAgB,CAAC,cAAc,EAAE,OAAO,GAAG,MAAM,CAShE;AAED,wBAAgB,eAAe,CAAC,cAAc,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAkB9F;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAoBvG;AAED,wBAAgB,WAAW,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,UAY7E;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,GAAE,MAAY,0BAmC3D;AAED,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC,CAK1D;AAED;;GAEG;AACH,wBAAgB,UAAU,CACxB,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,GAAG,uBAAuB,GAAG,SAAS,GAC3F,MAAM,CAkCR"}
1
+ {"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../../src/i18n/routing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EAAE,KAAK,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAsC7D;;GAEG;AACH,wBAAgB,UAAU,IAAI,MAAM,EAAE,CAErC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAyDD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,WAAW,yBAyEvD;AAED,wBAAgB,gBAAgB,CAAC,cAAc,EAAE,OAAO,GAAG,MAAM,CAShE;AAED,wBAAgB,eAAe,CAAC,cAAc,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAkB9F;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAoBvG;AAED,wBAAgB,WAAW,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,UAY7E;AAsBD,wBAAgB,qBAAqB,CAAC,QAAQ,GAAE,MAAY,0BAwB3D;AAED,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC,CAK1D;AAED,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,eAAe,CAAC,CAMvE;AAED,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC,CAU1D;AAED,wBAAsB,4BAA4B,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAIpF;AAED,KAAK,aAAa,GACd,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,GAC7C,eAAe,GACf,uBAAuB,GACvB,SAAS,CAAC;AAiDd,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,MAAM,CAErF;AAED,wBAAsB,UAAU,CAAC,MAAM,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAIxE;AAED,wBAAsB,2BAA2B,IAAI,OAAO,CAAC,MAAM,CAAC,CAKnE"}
@@ -18,6 +18,13 @@ const DEFAULT_FALLBACK = () => {
18
18
  };
19
19
  const LOCALE_COOKIE_NAME = 'NEXT_LOCALE';
20
20
  const LOCALE_PREFIX = 'as-needed';
21
+ const PATHNAME_HEADER_NAME = 'x-pathname';
22
+ const SEARCH_PARAMS_HEADER_NAME = 'x-search-params';
23
+ const setRoutingHeaders = (response, pathname, search) => {
24
+ response.headers.set(PATHNAME_HEADER_NAME, pathname);
25
+ response.headers.set(SEARCH_PARAMS_HEADER_NAME, search.replace(/^\?/, ''));
26
+ return response;
27
+ };
21
28
  /**
22
29
  * Get all available locales
23
30
  */
@@ -54,8 +61,7 @@ const redirectWithoutLocale = (request, pathname, locale, shouldSetCookie = fals
54
61
  const url = new URL(newPathname, request.url);
55
62
  url.search = request.nextUrl.search;
56
63
  url.hash = request.nextUrl.hash;
57
- const response = NextResponse.redirect(url, 302);
58
- response.headers.set('x-pathname', newPathname);
64
+ const response = setRoutingHeaders(NextResponse.redirect(url, 302), newPathname, request.nextUrl.search);
59
65
  if (shouldSetCookie) {
60
66
  setCookie(response, locale);
61
67
  }
@@ -68,8 +74,7 @@ const rewriteWithLocale = (request, pathname, locale) => {
68
74
  const internalUrl = new URL(`/${locale}${pathname}`, request.url);
69
75
  internalUrl.search = request.nextUrl.search;
70
76
  internalUrl.hash = request.nextUrl.hash;
71
- const response = NextResponse.rewrite(internalUrl);
72
- response.headers.set('x-pathname', pathname);
77
+ const response = setRoutingHeaders(NextResponse.rewrite(internalUrl), pathname, request.nextUrl.search);
73
78
  return response;
74
79
  };
75
80
  export function handleLocaleRouting(request) {
@@ -89,8 +94,7 @@ export function handleLocaleRouting(request) {
89
94
  // 2. URL에 locale이 포함된 경우
90
95
  if (pathnameHasLocale) {
91
96
  // 이미 path에 locale이 있을 때
92
- const response = NextResponse.next();
93
- response.headers.set('x-pathname', pathname);
97
+ const response = setRoutingHeaders(NextResponse.next(), pathname, request.nextUrl.search);
94
98
  // 쿠키 값이 path locale과 다를 경우
95
99
  if (localeCookie?.value !== pathLocale) {
96
100
  // 기본 locale인 경우 URL에서 제거
@@ -125,8 +129,7 @@ export function handleLocaleRouting(request) {
125
129
  // 다른 locale은 URL에 표시
126
130
  const newPathname = getChangeLocalePath(pathname, defaultFallback, locale);
127
131
  request.nextUrl.pathname = newPathname;
128
- const response = NextResponse.redirect(request.nextUrl);
129
- response.headers.set('x-pathname', newPathname);
132
+ const response = setRoutingHeaders(NextResponse.redirect(request.nextUrl), newPathname, request.nextUrl.search);
130
133
  // 쿠키가 없는 경우 설정
131
134
  if (localeCookie?.value === undefined) {
132
135
  setCookie(response, locale);
@@ -189,18 +192,24 @@ export function getPathname({ locale, href }) {
189
192
  }
190
193
  return href.startsWith('/') ? `/${locale}${href}` : `/${locale}/${href}`;
191
194
  }
192
- export function getLanguageAlternates(pathname = '/') {
193
- const defaultFallback = DEFAULT_FALLBACK();
194
- // Remove any existing locale from the pathname to get the base path
195
- let basePath = pathname;
195
+ const getBasePathWithoutLocale = (pathname = '/') => {
196
+ let basePath = pathname || '/';
196
197
  locales.forEach((locale) => {
197
198
  const localePattern = new RegExp(`^/${locale}(/|$)`);
198
199
  if (localePattern.test(basePath)) {
199
200
  basePath = basePath.replace(localePattern, '/');
200
201
  }
201
202
  });
202
- // Normalize path
203
+ // Normalize path and ensure root fallback
203
204
  basePath = basePath.replace(/\/+/g, '/');
205
+ if (!basePath.startsWith('/')) {
206
+ basePath = `/${basePath}`;
207
+ }
208
+ return basePath || '/';
209
+ };
210
+ export function getLanguageAlternates(pathname = '/') {
211
+ const defaultFallback = DEFAULT_FALLBACK();
212
+ const basePath = getBasePathWithoutLocale(pathname);
204
213
  const alternates = {};
205
214
  // Set x-default based on LOCALE_PREFIX and defaultFallback
206
215
  if (LOCALE_PREFIX === 'as-needed') {
@@ -224,40 +233,73 @@ export function getLanguageAlternates(pathname = '/') {
224
233
  export async function getCurrentPathname() {
225
234
  const { headers } = await import('next/headers');
226
235
  const headersList = await headers();
227
- return headersList.get('x-pathname') || '';
236
+ return headersList.get(PATHNAME_HEADER_NAME) || '';
228
237
  }
229
- /**
230
- * pathname과 params를 조합하여 전체 URL을 생성하는 함수
231
- */
232
- export function getFullUrl(pathname, params) {
233
- const baseUrl = process.env.NEXT_PUBLIC_APP_URL || '';
234
- if (!params) {
235
- return `${baseUrl}${pathname}`;
238
+ export async function getCurrentSearchParams() {
239
+ const { headers } = await import('next/headers');
240
+ const headersList = await headers();
241
+ const rawSearchParams = headersList.get(SEARCH_PARAMS_HEADER_NAME) || '';
242
+ return new URLSearchParams(rawSearchParams);
243
+ }
244
+ export async function getDefaultPathname() {
245
+ const pathname = await getCurrentPathname();
246
+ const basePath = getBasePathWithoutLocale(pathname);
247
+ const defaultFallback = DEFAULT_FALLBACK();
248
+ if (LOCALE_PREFIX === 'as-needed') {
249
+ return basePath;
236
250
  }
251
+ return `/${defaultFallback}${basePath === '/' ? '' : basePath}`;
252
+ }
253
+ export async function getCurrentLanguageAlternates() {
254
+ const pathname = await getCurrentPathname();
255
+ return getLanguageAlternates(pathname);
256
+ }
257
+ const isSearchParamsLike = (params) => {
258
+ return typeof params.get === 'function' && typeof params.forEach === 'function';
259
+ };
260
+ const toSearchParams = (params) => {
237
261
  const searchParams = new URLSearchParams();
238
- // ReadonlyURLSearchParams인 경우
239
- if (params instanceof URLSearchParams) {
262
+ if (!params) {
263
+ return searchParams;
264
+ }
265
+ if (isSearchParamsLike(params)) {
240
266
  params.forEach((value, key) => {
241
- // locale 파라미터는 제외
242
267
  if (key !== 'locale') {
243
268
  searchParams.append(key, value);
244
269
  }
245
270
  });
271
+ return searchParams;
246
272
  }
247
- else {
248
- // Record<string, string | string[] | undefined>인 경우
249
- Object.entries(params).forEach(([key, value]) => {
250
- // undefined 값은 건너뛰기
251
- if (value === undefined)
252
- return;
253
- if (Array.isArray(value)) {
254
- value.forEach((v) => searchParams.append(key, v));
255
- }
256
- else {
257
- searchParams.append(key, value);
258
- }
259
- });
260
- }
273
+ Object.entries(params).forEach(([key, value]) => {
274
+ if (value === undefined)
275
+ return;
276
+ if (Array.isArray(value)) {
277
+ value.forEach((v) => searchParams.append(key, v));
278
+ }
279
+ else {
280
+ searchParams.append(key, value);
281
+ }
282
+ });
283
+ return searchParams;
284
+ };
285
+ /**
286
+ * pathname과 params를 조합하여 전체 URL을 생성하는 함수
287
+ */
288
+ const buildFullUrl = (pathname, params) => {
289
+ const baseUrl = process.env.NEXT_PUBLIC_APP_URL || '';
290
+ const searchParams = toSearchParams(params);
261
291
  const queryString = searchParams.toString();
262
292
  return `${baseUrl}${pathname}${queryString ? `?${queryString}` : ''}`;
293
+ };
294
+ export function getFullUrlByPathname(pathname, params) {
295
+ return buildFullUrl(pathname, params);
296
+ }
297
+ export async function getFullUrl(params) {
298
+ const pathname = await getCurrentPathname();
299
+ return buildFullUrl(pathname, params);
300
+ }
301
+ export async function getFullUrlWithCurrentParams() {
302
+ const pathname = await getCurrentPathname();
303
+ const currentParams = await getCurrentSearchParams();
304
+ return buildFullUrl(pathname, currentParams);
263
305
  }
@@ -0,0 +1,183 @@
1
+ # Glass 토큰/유틸 역할 정리 (modal 사용 기준 + globals.css 기반)
2
+
3
+ 이 문서는 **모달에서 실제 사용한 조합**과 `packages/core/src/styles/globals.css`의 정의를 확인한 뒤, glass 관련 토큰/유틸리티의 역할과 사용 기준을 정리한 것입니다.
4
+
5
+ ## 1) 모달 사용 기준 (참조: `/apps/web/src/app/[locale]/modal/_components/modal.tsx`)
6
+ - **오버레이**: `glass` (배경을 얇게 덮고 유리감만 부여)
7
+ - **모달 패널**: `glass-tinted glass-backdrop border-glass-strong`
8
+ - **모달 내부 컨테이너**: `border-glass-strong` + `bg-white/60`(다크는 `dark:bg-slate-900/80`) + `backdrop-blur` 계열
9
+ - **입력/버튼**: `glass border-glass-strong` + hover 시 `hover:bg-glass-tinted`
10
+
11
+ ## 2) globals.css 정의 기반 역할 요약
12
+
13
+ ### 2.1 토큰(커스텀 프로퍼티)
14
+ `--glass*` 계열은 라이트/다크 각각 별도 값이 있으며, 공통적으로 다음 역할을 가집니다.
15
+ - `--glass`: 유리 레이어 기본 색 (투명도 포함)
16
+ - `--glass-tinted`: 유리의 색 농도 강화 버전
17
+ - `--glass-glow`: 유리의 발광 느낌을 위한 색
18
+ - `--glass-surface`: 실제 표면 색(배경 그라데이션 밑바탕)
19
+ - `--glass-surface-tinted`: 표면 색의 틴트 버전
20
+ - `--glass-border`: 유리 테두리 강도
21
+ - `--glass-border-subtle`: 유리 테두리 약한 버전
22
+ - `--glass-shine`: 유리 하이라이트
23
+ - `--glass-shine-subtle`: 유리 하이라이트 약한 버전
24
+ - `--glass-shadow-color`: 유리 그림자 컬러
25
+ - `--glass-shadow-ambient`: 유리 앰비언트 그림자
26
+ - `--glass-blur`: 백드롭 블러 강도 (기본 8px)
27
+
28
+ ### 2.2 유틸리티(클래스)
29
+ globals.css 내 `@utility` 정의 기준
30
+
31
+ #### `glass`
32
+ - **역할**: 기본 유리 표면. 그라데이션 + 그림자 + 내부 하이라이트 포함.
33
+ - **대상**: 카드, 패널, 버튼, 인풋 등 “표면” 요소.
34
+
35
+ #### `glass-tinted`
36
+ - **역할**: `glass`와 동일 효과 + 틴트된 표면(`--glass-surface-tinted`).
37
+ - **대상**: 모달 패널, 강조 카드.
38
+
39
+ #### `glass-glow`
40
+ - **역할**: `glass`와 동일 효과 + 발광 표면(`--glass-surface-glow`).
41
+ - **대상**: 강조 배지, 강조 카드.
42
+
43
+ #### `glass-backdrop`
44
+ - **역할**: 배경 블러 + 채도 증가.
45
+ - **대상**: 모달 패널, 오버레이 같이 **뒤 배경이 비쳐야 하는 영역**.
46
+
47
+ #### `glass-overlay`
48
+ - **역할**: 기존 `bg-*` 색상을 유지한 채 유리 질감만 얹는다.
49
+ - **대상**: 색상 버튼/배지/태그 같이 `bg-*`가 있는 요소.
50
+
51
+ #### `border-glass-soft`
52
+ - **역할**: 은은한 유리 테두리 + 내부 얕은 하이라이트.
53
+ - **대상**: 보조 카드, 내부 블록.
54
+
55
+ #### `border-glass-strong`
56
+ - **역할**: 더 선명한 유리 테두리 + 내부 하이라이트.
57
+ - **대상**: 모달 패널, 버튼, 입력처럼 **경계가 분명해야 하는 요소**.
58
+
59
+ ## 3) 사용 규칙 (짧은 체크리스트)
60
+ - 유리 표면에는 `border-glass-*`를 **필요할 때만** 붙여 경계를 잡는다.
61
+ - 블러가 필요한 곳에만 `glass-backdrop`을 붙인다.
62
+ - 강조 패널에는 `glass-tinted`, 일반 카드에는 `glass`가 기본.
63
+ - 다크 모드에서는 텍스트/placeholder 대비를 추가로 보정한다 (`dark:*`).
64
+ - **색상 버튼/배지**는 `bg-*` + `glass-overlay` 조합을 기본으로 사용한다.
65
+
66
+ ## 4) 적용 예시 (모달/입력/버튼)
67
+
68
+ ### 4.1 모달 오버레이
69
+ ```tsx
70
+ <div className="glass fixed inset-0" />
71
+ ```
72
+ - **의도**: 배경을 얇게 덮고 유리감만 부여.
73
+
74
+ ### 4.2 모달 패널
75
+ ```tsx
76
+ <Dialog.Panel className="glass-tinted glass-backdrop border-glass-strong rounded-2xl border p-8 shadow-2xl">
77
+ ...
78
+ </Dialog.Panel>
79
+ ```
80
+ - **의도**: 패널 자체는 틴트 유리 + 배경 블러 + 강한 경계.
81
+
82
+ ### 4.3 모달 내부 컨테이너(본문 영역)
83
+ ```tsx
84
+ <div className="rounded-2xl border border-white/40 bg-white/60 p-6 backdrop-blur-xl dark:border-slate-700/70 dark:bg-slate-900/80">
85
+ ...
86
+ </div>
87
+ ```
88
+ - **의도**: 내부 콘텐츠는 더 밝게, 다크 모드 대비 보정.
89
+
90
+ ### 4.4 입력 필드
91
+ ```tsx
92
+ <input
93
+ className="glass border-glass-strong rounded-lg border px-3 py-2 text-sm shadow-sm
94
+ focus-visible:ring-2 focus-visible:ring-slate-900/20
95
+ dark:placeholder:text-slate-400 dark:focus-visible:ring-white/20"
96
+ />
97
+ ```
98
+ - **의도**: 유리 표면 + 강한 경계 + 포커스 링 보정.
99
+
100
+ ### 4.5 버튼
101
+ ```tsx
102
+ <button className="glass border-glass-strong hover:bg-glass-tinted rounded-lg px-5 py-2 text-sm">
103
+ ...
104
+ </button>
105
+ ```
106
+ - **의도**: 기본은 glass, hover에서 tinted로 강조.
107
+
108
+ ### 4.6 색상 버튼 (glass-overlay)
109
+ ```tsx
110
+ <button className="glass-overlay bg-emerald-400 text-slate-900 rounded-lg px-5 py-2 text-sm">
111
+ ...
112
+ </button>
113
+ ```
114
+ - **의도**: `bg-*`를 유지하면서 glass 질감만 얹는다.
115
+
116
+ ### 4.7 탭 선택 상태 (glass-overlay)
117
+ ```tsx
118
+ <div className="flex gap-2">
119
+ <button className="glass border-glass-strong text-foreground rounded-full px-4 py-2 text-sm">
120
+ 콘텐츠 작성
121
+ </button>
122
+ <button className="glass-overlay bg-slate-900 text-white rounded-full px-4 py-2 text-sm shadow-md">
123
+ 렌더링 미리보기 (선택됨)
124
+ </button>
125
+ </div>
126
+ ```
127
+ - **의도**: 탭 선택 상태에서 `bg-*`를 유지한 채 glass 질감을 추가한다.
128
+
129
+ ### 4.8 기본 Solid 버튼 (non-glass)
130
+ ```tsx
131
+ <button className="bg-primary hover:bg-primary text-primary-foreground rounded-lg px-5 py-2 text-sm font-semibold shadow-lg transition">
132
+ ...
133
+ </button>
134
+ ```
135
+ - **의도**: 가장 기본적인 CTA 버튼. glass가 필요 없을 때 사용.
136
+
137
+ ### 4.9 알림/에러 메시지 (glass + 컬러 배경)
138
+ ```tsx
139
+ <div className="glass border-glass-strong rounded-2xl border border-rose-200/80 bg-rose-50/70 px-4 py-3 text-sm text-rose-600 dark:border-rose-500/30 dark:bg-rose-500/10 dark:text-rose-200">
140
+ ...
141
+ </div>
142
+ ```
143
+ - **의도**: 유리 표면 위에 컬러 배경을 얹어 메시지 톤을 유지한다.
144
+
145
+ ## 5) 적용 순서 가이드
146
+ 1. **표면 선택**: `glass` 또는 `glass-tinted`
147
+ 2. **경계 부여**: `border-glass-strong` 또는 `border-glass-soft`
148
+ 3. **배경 블러 필요 여부**: 필요하면 `glass-backdrop`
149
+ 4. **다크 보정**: `dark:*`로 텍스트/placeholder 대비 조정
150
+
151
+ ## 6) 추가 예시 (카드/리스트/배지)
152
+
153
+ ### 6.1 기본 카드
154
+ ```tsx
155
+ <div className="glass border-glass-soft rounded-2xl p-6 shadow-md">
156
+ ...
157
+ </div>
158
+ ```
159
+ - **의도**: 보조 영역에 은은한 유리감.
160
+
161
+ ### 6.2 강조 카드 (CTA)
162
+ ```tsx
163
+ <div className="glass-tinted border-glass-strong rounded-2xl p-6 shadow-xl">
164
+ ...
165
+ </div>
166
+ ```
167
+ - **의도**: 시선이 가야 하는 블록에 강한 대비.
168
+
169
+ ### 6.3 리스트 아이템
170
+ ```tsx
171
+ <li className="glass border-glass-strong rounded-lg p-3 shadow">
172
+ ...
173
+ </li>
174
+ ```
175
+ - **의도**: 반복 항목에 동일한 유리 표면 제공.
176
+
177
+ ### 6.4 배지/태그
178
+ ```tsx
179
+ <span className="glass border-glass-strong rounded-full px-3 py-1 text-xs font-semibold">
180
+ NEW
181
+ </span>
182
+ ```
183
+ - **의도**: 작은 요소에도 유리감 유지.
@@ -296,6 +296,57 @@
296
296
  inset 0 1px 0 var(--glass-shine-subtle);
297
297
  }
298
298
 
299
+ @utility glass-overlay {
300
+ backdrop-filter: blur(var(--glass-blur)) saturate(1.15);
301
+ background-image:
302
+ linear-gradient(
303
+ 135deg,
304
+ color-mix(in oklab, white 30%, transparent) 0%,
305
+ color-mix(in oklab, white 18%, transparent) 30%,
306
+ color-mix(in oklab, white 8%, transparent) 70%,
307
+ color-mix(in oklab, white 22%, transparent) 100%
308
+ ),
309
+ radial-gradient(
310
+ 120% 120% at 18% 0%,
311
+ color-mix(in oklab, white 26%, transparent) 0%,
312
+ transparent 48%
313
+ );
314
+ box-shadow:
315
+ 0 1px 2px color-mix(in oklab, var(--glass-shadow-ambient) 60%, transparent),
316
+ 0 6px 12px color-mix(in oklab, var(--glass-shadow-color) 55%, transparent),
317
+ inset 0 1px 0 color-mix(in oklab, white 38%, transparent),
318
+ inset 0 -1px 0 color-mix(in oklab, black 10%, transparent);
319
+ filter: saturate(1.04) brightness(1.03);
320
+
321
+ .dark & {
322
+ background-image:
323
+ linear-gradient(
324
+ 180deg,
325
+ color-mix(in oklab, black 22%, transparent) 0%,
326
+ color-mix(in oklab, black 16%, transparent) 100%
327
+ ),
328
+ linear-gradient(
329
+ 135deg,
330
+ color-mix(in oklab, white 28%, transparent) 0%,
331
+ color-mix(in oklab, white 16%, transparent) 30%,
332
+ color-mix(in oklab, white 7%, transparent) 70%,
333
+ color-mix(in oklab, white 20%, transparent) 100%
334
+ ),
335
+ radial-gradient(
336
+ 120% 120% at 18% 0%,
337
+ color-mix(in oklab, white 22%, transparent) 0%,
338
+ transparent 44%
339
+ );
340
+ background-blend-mode: multiply, normal, normal;
341
+ box-shadow:
342
+ 0 2px 6px color-mix(in oklab, var(--glass-shadow-ambient) 65%, transparent),
343
+ 0 10px 18px color-mix(in oklab, var(--glass-shadow-color) 65%, transparent),
344
+ inset 0 1px 0 color-mix(in oklab, white 24%, transparent),
345
+ inset 0 -1px 0 color-mix(in oklab, black 18%, transparent);
346
+ filter: saturate(1.04) brightness(1.03);
347
+ }
348
+ }
349
+
299
350
  @utility glass-tinted {
300
351
  --glass-surface-slot: var(--glass-surface-tinted);
301
352
  @apply glass;
@@ -325,6 +376,11 @@
325
376
  border-radius: inherit;
326
377
  pointer-events: none;
327
378
  box-shadow: inset 0 1px 0 var(--glass-shine-subtle);
379
+ transform: translateY(-0.3px);
380
+ }
381
+
382
+ .dark &::after {
383
+ transform: translateY(0);
328
384
  }
329
385
  }
330
386
 
@@ -343,6 +399,11 @@
343
399
  border-radius: inherit;
344
400
  pointer-events: none;
345
401
  box-shadow: inset 0 1px 0 var(--glass-shine-subtle);
402
+ transform: translateY(-0.3px);
403
+ }
404
+
405
+ .dark &::after {
406
+ transform: translateY(0);
346
407
  }
347
408
  }
348
409
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@korioinc/next-core",
3
- "version": "2.0.15",
3
+ "version": "2.0.17",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./ads": {
@@ -68,8 +68,8 @@
68
68
  "@lingui/react": "^5.9.0",
69
69
  "@tailwindcss/typography": "^0.5.19",
70
70
  "@types/negotiator": "^0.6.4",
71
- "@types/node": "^25.0.10",
72
- "@types/react": "^19.2.10",
71
+ "@types/node": "^25.2.1",
72
+ "@types/react": "^19.2.13",
73
73
  "@types/react-dom": "^19.2.3",
74
74
  "eslint": "^9.39.2",
75
75
  "next-themes": "^0.4.6",
@@ -79,11 +79,11 @@
79
79
  "tsc-alias": "^1.8.16",
80
80
  "tw-animate-css": "^1.4.0",
81
81
  "typescript": "^5.9.3",
82
- "@korioinc/next-configs": "2.0.15"
82
+ "@korioinc/next-configs": "2.0.17"
83
83
  },
84
84
  "dependencies": {
85
85
  "@floating-ui/react": "^0.27.17",
86
- "@formatjs/intl-localematcher": "^0.7.5",
86
+ "@formatjs/intl-localematcher": "^0.8.1",
87
87
  "clsx": "^2.1.1",
88
88
  "cookies-next": "^6.1.1",
89
89
  "cosmiconfig": "^9.0.0",
@@ -94,7 +94,7 @@
94
94
  "schema-dts": "^1.1.5",
95
95
  "tailwind-merge": "^3.4.0",
96
96
  "valtio": "^2.3.0",
97
- "@korioinc/next-conf": "2.0.15"
97
+ "@korioinc/next-conf": "2.0.17"
98
98
  },
99
99
  "publishConfig": {
100
100
  "access": "public",