feat(site): 쇼케이스를 별도 페이지로 분리하고 본문을 존댓말로 바꾼다
All checks were successful
ci / build (push) Successful in 18s
All checks were successful
ci / build (push) Successful in 18s
홈에 있던 쇼케이스 섹션은 스크린샷 두 장을 카드에 넣고 각도를 준 것이었고,
눌러도 갈 곳이 없었다. 그림만 보여주고 끝나는 자리는 증거가 아니라 장식이다.
- /showcase 를 별도 페이지로 만들었다. 모자이크 그리드에 썸네일을 놓고
누르면 실제로 도는 사이트(/work/<slug>/)가 열린다.
- 사례 데이터를 src/data/work.ts 한 곳에 모았다. 항목 하나만 추가하면
그리드에 들어간다 — 페이지 코드는 건드리지 않는다.
- 첫 사례로 목요일의 화원(플로럴 스튜디오)을 넣고 실제 파일을
public/work/thursday-flowers/ 에 함께 싣는다.
- 홈에서 예전 섹션을 제거하고 내비를 /showcase 로 돌렸다.
본문 말투:
사이트 문장이 전부 반말이었고 일부는 내부 감사 로그처럼 읽혔다.
스킬 문서의 단정한 반말은 의도지만, 방문자를 향한 화면은 다르다.
제목은 단정문이 카피로서 힘을 가지므로 그대로 두고, 설명하는 문장과
명령형("~해라")을 전부 존댓말로 바꿨다. 실측 결과 홈·쇼케이스 모두 0건.
구현에서 걸린 것:
Base 레이아웃이 이미 <main class="page"> 를 제공하는데 새 페이지에서
또 감쌌다. 바깥 page 의 본문 칼럼(561px) 안에 안쪽 page 가 들어가
전 페이지가 좁아졌다 — 모자이크가 561px, 썸네일이 240px 이었다.
중첩을 걷어내자 1180px / 596px 로 돌아왔다.
This commit is contained in:
parent
b92853bf2b
commit
30dc6687dd
16 changed files with 1555 additions and 75 deletions
BIN
apps/site/src/assets/shots/work-thursday-flowers-2.png
Normal file
BIN
apps/site/src/assets/shots/work-thursday-flowers-2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 684 KiB |
BIN
apps/site/src/assets/shots/work-thursday-flowers.png
Normal file
BIN
apps/site/src/assets/shots/work-thursday-flowers.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 330 KiB |
55
apps/site/src/data/work.ts
Normal file
55
apps/site/src/data/work.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
* 쇼케이스 목록.
|
||||
*
|
||||
* 사례를 추가할 때 **여기 한 항목만 넣으면** 모자이크 그리드에 자동으로 들어간다.
|
||||
* 페이지 코드는 건드리지 않는다.
|
||||
*
|
||||
* 순서가 곧 표시 순서다(최근 것을 위에). `size` 는 타일 크기이고,
|
||||
* 전부 같은 크기로 두지 마라 — 균일한 격자는 이 스킬이 금지하는 형태다.
|
||||
*/
|
||||
import type { ImageMetadata } from "astro";
|
||||
import thursdayFlowers from "../assets/shots/work-thursday-flowers.png";
|
||||
import thursdayFlowers2 from "../assets/shots/work-thursday-flowers-2.png";
|
||||
|
||||
export interface WorkItem {
|
||||
slug: string;
|
||||
/** 사이트 이름. 실제 클라이언트가 아니라 데모면 그렇다고 적는다 */
|
||||
name: string;
|
||||
/** 무엇을 만든 것인가 — 한 줄 */
|
||||
kind: string;
|
||||
/** 2단계에서 정한 한 문장 컨셉 */
|
||||
concept: string;
|
||||
/** 감수한 리스크 하나. 이 스킬의 결과물을 다른 것과 구별하는 지점이다 */
|
||||
risk: string;
|
||||
preset: string;
|
||||
/** YYYY-MM */
|
||||
date: string;
|
||||
/** 자랑이 아니라 잰 값. 없으면 비워 둔다 */
|
||||
metrics: string[];
|
||||
/** 실제로 열리는 주소. 목업이 아니라 동작하는 사이트여야 한다 */
|
||||
href: string;
|
||||
shot: ImageMetadata;
|
||||
/** 호버할 때 바뀌는 두 번째 컷. 없어도 된다 */
|
||||
shotAlt?: ImageMetadata;
|
||||
alt: string;
|
||||
/** 모자이크 타일 크기 */
|
||||
size: "lg" | "md" | "sm";
|
||||
}
|
||||
|
||||
export const WORK: WorkItem[] = [
|
||||
{
|
||||
slug: "thursday-flowers",
|
||||
name: "목요일의 화원",
|
||||
kind: "플로럴 스튜디오 · 한 장",
|
||||
concept: "꽃을 파는 가게가 아니라, 꽃으로 작업하는 사람의 기록",
|
||||
risk: "첫 화면에 큰 사진을 깔지 않았습니다. 크기가 제각각인 사진 셋을 정렬선 없이 흩뿌립니다",
|
||||
preset: "editorial",
|
||||
date: "2026-08",
|
||||
metrics: ["이미지 603KB", "JS 0.6KB", "본문 대비 13.75:1", "하드 게이트 12/12"],
|
||||
href: "/work/thursday-flowers/",
|
||||
shot: thursdayFlowers,
|
||||
shotAlt: thursdayFlowers2,
|
||||
alt: "목요일의 화원 첫 화면 — 크림색 배경에 꽃 사진 셋이 서로 다른 위치에 놓여 있습니다",
|
||||
size: "lg",
|
||||
},
|
||||
];
|
||||
|
|
@ -108,34 +108,34 @@ export interface Content {
|
|||
const STAGES_KO: Stage[] = [
|
||||
{
|
||||
name: "브리프 게이트",
|
||||
does: "무엇을, 누구에게, 어떤 제약인지 세 줄로 압축한다. 규모를 판정해 경로를 고른다.",
|
||||
becomes: "누구에게: 홈카페 3년차 이상. 산미와 로스팅 프로파일을 구분한다",
|
||||
does: "무엇을, 누구에게, 어떤 제약인지 세 줄로 압축합니다. 규모를 판정해 경로를 고릅니다.",
|
||||
becomes: "누구에게: 홈카페 3년차 이상. 산미와 로스팅 프로파일을 구분합니다",
|
||||
},
|
||||
{
|
||||
name: "레퍼런스 조사",
|
||||
does: "실제로 존재하는 사이트 3개를 서로 다른 층위에서 고른다. 구조, 톤, 디테일.",
|
||||
becomes: "R2 = 독립 문학 출판사. 커피 업계 밖에서 톤을 가져온다",
|
||||
does: "실제로 존재하는 사이트 3개를 서로 다른 층위에서 고릅니다. 구조, 톤, 디테일.",
|
||||
becomes: "R2 = 독립 문학 출판사. 커피 업계 밖에서 톤을 가져옵니다",
|
||||
},
|
||||
{
|
||||
name: "방향 결정",
|
||||
does: "레퍼런스를 하나의 축으로 합성한다. 감수할 리스크를 하나 고른다.",
|
||||
does: "레퍼런스를 하나의 축으로 합성합니다. 감수할 리스크를 하나 고릅니다.",
|
||||
becomes: "감상은 이름에만, 화면에는 수치만",
|
||||
},
|
||||
{
|
||||
name: "디자인 토큰",
|
||||
does: "코드를 쓰기 전에 숫자를 정한다. 성능 예산도 여기서 확정한다.",
|
||||
does: "코드를 쓰기 전에 숫자를 정합니다. 성능 예산도 여기서 확정합니다.",
|
||||
becomes: "--measure: 33em / --leading-normal: 1.7 / JS 예산 0KB",
|
||||
mono: true,
|
||||
},
|
||||
{
|
||||
name: "구현",
|
||||
does: "레이아웃, 재질, 입체, 모션 순서로. 이펙트를 다 꺼도 완성돼 있어야 한다.",
|
||||
does: "레이아웃, 재질, 입체, 모션 순서로. 이펙트를 다 꺼도 완성돼 있어야 합니다.",
|
||||
becomes: "<h1>산미 8.5 · 바디 6.0 · 로스팅 라이트</h1>",
|
||||
mono: true,
|
||||
},
|
||||
{
|
||||
name: "프리플라이트",
|
||||
does: "자기 결과물을 남의 것처럼 본다. 게이트 12개는 전부 '아니오'여야 한다.",
|
||||
does: "자기 결과물을 남의 것처럼 봅니다. 게이트 12개는 전부 '아니오'여야 합니다.",
|
||||
becomes: "게이트 12/12 통과 · 슬롭 지문 0건 · 320px 오버플로 31px 수정",
|
||||
mono: true,
|
||||
},
|
||||
|
|
@ -181,17 +181,17 @@ export const ko: Content = {
|
|||
meta: {
|
||||
title: "designpaca · 웹 디자인 파이프라인 스킬",
|
||||
description:
|
||||
"AI 코딩 에이전트에게 웹 디자인을 순서대로 하게 만드는 스킬. 레퍼런스 조사부터 셀프 감사까지 여섯 단계. Claude Code, Codex, Cursor, Windsurf 에 한 줄로 설치한다.",
|
||||
"AI 코딩 에이전트에게 웹 디자인을 순서대로 하게 만드는 스킬. 레퍼런스 조사부터 셀프 감사까지 여섯 단계. Claude Code, Codex, Cursor, Windsurf 에 한 줄로 설치합니다.",
|
||||
},
|
||||
hero: {
|
||||
h1: "결정하지 않으면 기본값이 나온다",
|
||||
lead: "AI가 만든 티는 못 만들어서가 아니라 결정을 안 해서 난다. 그 결정을 여섯 단계로 강제하는 스킬.",
|
||||
lead: "AI가 만든 티는 못 만들어서가 아니라 결정을 안 해서 납니다. 그 결정을 여섯 단계로 강제하는 스킬.",
|
||||
install: "npx designpaca",
|
||||
installNote: "Node 20.11+ · 설치 대상을 감지해 골라준다",
|
||||
installNote: "Node 20.11+ · 설치 대상을 감지해 골라줍니다",
|
||||
links: [{ label: "npm", href: "https://www.npmjs.com/package/designpaca" }],
|
||||
stage: {
|
||||
alt: "유리판 여섯 장이 깊이 방향으로 나란히 서 있다. 스크롤하면 카메라가 판을 하나씩 통과하고, 통과한 판은 뒤로 물러난다.",
|
||||
caption: "판 하나가 단계 하나다. 통과 조건을 채우지 못하면 다음 판으로 가지 않는다.",
|
||||
alt: "유리판 여섯 장이 깊이 방향으로 나란히 서 있습니다. 스크롤하면 카메라가 판을 하나씩 통과하고, 통과한 판은 뒤로 물러납니다.",
|
||||
caption: "판 하나가 단계 하나다. 통과 조건을 채우지 못하면 다음 판으로 가지 않습니다.",
|
||||
gate: "게이트",
|
||||
pass: "통과",
|
||||
},
|
||||
|
|
@ -200,7 +200,7 @@ export const ko: Content = {
|
|||
eyebrow: "여섯 단계",
|
||||
h2: "브리프 한 줄이 검증된 페이지가 될 때까지",
|
||||
lead:
|
||||
"각 단계에는 통과 조건이 있다. 통과하지 못하면 다음으로 가지 않는다. 아래는 같은 브리프가 단계마다 무엇으로 변했는지다.",
|
||||
"각 단계에는 통과 조건이 있습니다. 통과하지 못하면 다음으로 가지 않습니다. 아래는 같은 브리프가 단계마다 무엇으로 변했는지다.",
|
||||
briefLabel: "들어온 브리프",
|
||||
brief: "수제 커피 로스터리의 원두 정기구독 랜딩 만들어줘",
|
||||
stages: STAGES_KO,
|
||||
|
|
@ -208,17 +208,17 @@ export const ko: Content = {
|
|||
showcase: {
|
||||
h2: "결과물은 서로 닮지 않는다",
|
||||
lead:
|
||||
"같은 파이프라인을 두 브리프에 돌린 결과다. 1단계에서 톤을 다른 업종에서 가져오게 강제하기 때문에 같은 도구를 써도 같은 화면이 나오지 않는다. 둘 다 수치 자리가 비어 있는 것도 우연이 아니다.",
|
||||
"같은 파이프라인을 두 브리프에 돌린 결과다. 1단계에서 톤을 다른 업종에서 가져오게 강제하기 때문에 같은 도구를 써도 같은 화면이 나오지 않습니다. 둘 다 수치 자리가 비어 있는 것도 우연이 아닙니다.",
|
||||
items: [
|
||||
{
|
||||
alt: "무월 원두 구독 랜딩. 오프화이트 배경에 굵은 한글 헤드라인, 오른쪽에 원두 3종의 산미와 바디 표가 있다.",
|
||||
alt: "무월 원두 구독 랜딩. 오프화이트 배경에 굵은 한글 헤드라인, 오른쪽에 원두 3종의 산미와 바디 표가 있습니다.",
|
||||
caption: "수제 커피 로스터리 · 구독 전환",
|
||||
note: "프리셋 quiet-commerce. 사진이 없다는 제약을 수치표로 뒤집었다. 표의 값은 아직 비어 있다. 로스터에게 받기 전까지 채우지 않는다.",
|
||||
note: "프리셋 quiet-commerce. 사진이 없습니다는 제약을 수치표로 뒤집었습니다. 표의 값은 아직 비어 있습니다. 로스터에게 받기 전까지 채우지 않습니다.",
|
||||
},
|
||||
{
|
||||
alt: "Pulsegate 모니터링 SaaS 히어로. 밝은 배경에 큰 세리프 헤드라인과 무료 체험 버튼이 있다.",
|
||||
alt: "Pulsegate 모니터링 SaaS 히어로. 밝은 배경에 큰 세리프 헤드라인과 무료 체험 버튼이 있습니다.",
|
||||
caption: "API 모니터링 SaaS · 무료 체험 가입",
|
||||
note: "개발자 도구라고 자동으로 다크로 가지 않았다. 브리프에 근거가 없었기 때문이다. 리전 수는 아직 중괄호 자리로 남아 있다.",
|
||||
note: "개발자 도구라고 자동으로 다크로 가지 않았습니다. 브리프에 근거가 없었기 때문입니다. 리전 수는 아직 중괄호 자리로 남아 있습니다.",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
@ -226,43 +226,43 @@ export const ko: Content = {
|
|||
eyebrow: "SVG 필터",
|
||||
h2: "이미지 편집기를 열지 않고 고치는 네 가지",
|
||||
lead:
|
||||
"질감을 만드는 필터 데모는 많다. 손이 실제로 줄어드는 자리는 따로 있다. 아래 넷은 왼쪽이 매번 손으로 맞추던 것, 오른쪽이 같은 소재에 선언 하나만 얹은 것이다. 아래 수치는 이 화면을 그대로 캡처해 잰 값이다.",
|
||||
"질감을 만드는 필터 데모는 많다. 손이 실제로 줄어드는 자리는 따로 있습니다. 아래 넷은 왼쪽이 매번 손으로 맞추던 것, 오른쪽이 같은 소재에 선언 하나만 얹은 것입니다. 아래 수치는 이 화면을 그대로 캡처해 잰 값이다.",
|
||||
demos: [
|
||||
{
|
||||
id: "scrim",
|
||||
name: "글자 모양대로 생기는 스크림",
|
||||
problem: "사진 위 캡션이 대비를 못 넘긴다. 사진마다 밝기가 달라 반투명 박스를 손으로 맞추게 된다.",
|
||||
problem: "사진 위 캡션이 대비를 못 넘깁니다. 사진마다 밝기가 달라 반투명 박스를 손으로 맞추게 됩니다.",
|
||||
beforeLabel: "그냥 얹음",
|
||||
afterLabel: "filter: url(#scrim)",
|
||||
how: "글자 알파를 부풀려 그 모양대로만 색을 채운다. 박스가 아니라 글자를 따라가므로 사진을 덜 가린다.",
|
||||
how: "글자 알파를 부풀려 그 모양대로만 색을 채웁니다. 박스가 아니라 글자를 따라가므로 사진을 덜 가립니다.",
|
||||
verdict: "흰 글자 대비 1.70:1 → 4.79:1",
|
||||
},
|
||||
{
|
||||
id: "cvd",
|
||||
name: "색각 이상 시뮬레이션",
|
||||
problem: "상태를 색으로만 구분하면 남성 12명 중 1명에게 같은 배지가 된다. 내 화면만 봐서는 못 잡는다.",
|
||||
problem: "상태를 색으로만 구분하면 남성 12명 중 1명에게 같은 배지가 됩니다. 내 화면만 봐서는 못 잡는다.",
|
||||
beforeLabel: "내가 보는 화면",
|
||||
afterLabel: "적록색약(2형)",
|
||||
how: "feColorMatrix 한 줄. 이것만은 linearRGB 로 둔다 — 색각 변환은 물리적인 빛의 혼합이다.",
|
||||
how: "feColorMatrix 한 줄. 이것만은 linearRGB 로 둡니다 — 색각 변환은 물리적인 빛의 혼합이다.",
|
||||
verdict: "배지 셋의 최소 색거리 82 → 2 · 주의와 실패가 같은 색",
|
||||
},
|
||||
{
|
||||
id: "goo",
|
||||
name: "떨어진 것이 하나로 뭉치기",
|
||||
problem: "조각이 녹아 붙는 표현에 캔버스나 물리 라이브러리를 켠다. 로더와 메뉴 전환에서 반복된다.",
|
||||
problem: "조각이 녹아 붙는 표현에 캔버스나 물리 라이브러리를 켜게 됩니다. 로더와 메뉴 전환에서 반복됩니다.",
|
||||
beforeLabel: "필터 없음 · 원 3개",
|
||||
afterLabel: "filter: url(#goo)",
|
||||
how: "블러한 뒤 알파만 세워 잘라낸다. 붙을지 말지는 취향이 아니라 간격과 블러 반경의 비율로 정해진다.",
|
||||
how: "블러한 뒤 알파만 세워 잘라냅니다. 붙을지 말지는 취향이 아니라 간격과 블러 반경의 비율로 정해진다.",
|
||||
verdict: "덩어리 3개 → 1개 · JS 0줄",
|
||||
},
|
||||
{
|
||||
id: "glass",
|
||||
name: "배경을 휘게 하는 오버레이",
|
||||
problem: "반투명 패널은 뒤를 흐리게만 만든다. 실제 유리는 빛을 휜다.",
|
||||
problem: "반투명 패널은 뒤를 흐리게만 만듭니다. 실제 유리는 빛을 휜다.",
|
||||
beforeLabel: "backdrop-filter: blur",
|
||||
afterLabel: "blur + url(#lens)",
|
||||
how: "변위 지도를 backdrop-filter 에 넘긴다. 노이즈로 만들면 값이 중립으로 수렴해 아무 일도 안 일어난다.",
|
||||
verdict: "가장자리에서 격자가 눌린다 · Chromium 전용",
|
||||
how: "변위 지도를 backdrop-filter 에 넘깁니다. 노이즈로 만들면 값이 중립으로 수렴해 아무 일도 안 일어납니다.",
|
||||
verdict: "가장자리에서 격자가 눌립니다 · Chromium 전용",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
@ -270,112 +270,112 @@ export const ko: Content = {
|
|||
eyebrow: "이 페이지의 수치",
|
||||
h2: "카피만 고쳐서는 슬롭이 없어지지 않는다",
|
||||
lead:
|
||||
"문장을 다듬는 것으로 해결되는 것은 문장뿐이다. radius 어휘 수, 반투명 패널 개수, 타입 단계 수는 그대로 남는다. 그래서 이 페이지 자신의 형태 수치를 공개한다. 결과가 나쁘게 나와도 그대로 싣는다.",
|
||||
"문장을 다듬는 것으로 해결되는 것은 문장뿐입니다. radius 어휘 수, 반투명 패널 개수, 타입 단계 수는 그대로 남습니다. 그래서 이 페이지 자신의 형태 수치를 공개합니다. 결과가 나쁘게 나와도 그대로 싣는다.",
|
||||
items: [
|
||||
{ label: "히어로까지 JS", value: "0.6%", note: "예산 50KB 중 실제로 쓴 비율", feature: true },
|
||||
{ label: "하드 게이트", value: "12/12", note: "정적 검사와 320px · 1440px 실렌더 양쪽", feature: true },
|
||||
{ label: "three.js 추가분", value: "184KB", note: "검사대가 화면에 들어올 때만 받는다. 초기 번들에는 없다" },
|
||||
{ label: "three.js 추가분", value: "184KB", note: "검사대가 화면에 들어올 때만 받습니다. 초기 번들에는 없습니다" },
|
||||
{ label: "슬롭 지문", value: "0건", note: "9개 패턴 grep" },
|
||||
{ label: "radius 어휘", value: "2종", note: "3px 과 14px. 알약 모양을 없앴다" },
|
||||
{ label: "반투명 패널", value: "2개", note: "29개까지 늘렸다가 걷어냈다. 유리는 무언가를 담는 액자에만 남겼다" },
|
||||
{ label: "radius 어휘", value: "2종", note: "3px 과 14px. 알약 모양을 없앴습니다" },
|
||||
{ label: "반투명 패널", value: "2개", note: "29개까지 늘렸다가 걷어냈습니다. 유리는 무언가를 담는 액자에만 남겼습니다" },
|
||||
{ label: "타입 단계", value: "6단계", note: "토큰에 정의된 수. 디스플레이는 스케일 밖이고, 최대 글자는 본문의 5.6배" },
|
||||
{ label: "폰트 패밀리", value: "2개", note: "언어판마다 자기 것만 받는다" },
|
||||
{ label: "폰트 패밀리", value: "2개", note: "언어판마다 자기 것만 받습니다" },
|
||||
{ label: "강조색", value: "1개", note: "라이트와 다크는 같은 색의 대응값" },
|
||||
{ label: "첫 인터랙션", value: "미측정", note: "배포 후 Lighthouse 로 잰다" },
|
||||
{ label: "첫 인터랙션", value: "미측정", note: "배포 후 Lighthouse 로 잽니다" },
|
||||
],
|
||||
caveat:
|
||||
"값은 프로덕션 빌드에서 측정한다. 아직 측정하지 않은 항목은 비워둔다. 지어낸 숫자보다 구멍이 정직하다. 이 페이지는 제품 데모이므로 three.js 를 얹으며 JS 예산을 올렸다. 올렸다는 사실을 여기 적는 것이 규칙이다.",
|
||||
"값은 프로덕션 빌드에서 측정합니다. 아직 측정하지 않은 항목은 비워둡니다. 지어낸 숫자보다 구멍이 정직하다. 이 페이지는 제품 데모이므로 three.js 를 얹으며 JS 예산을 올렸다. 올렸다는 사실을 여기 적는 것이 규칙이다.",
|
||||
},
|
||||
preflight: {
|
||||
h2: "이 페이지를 만들며 실제로 걸린 것",
|
||||
lead: "남을 검사한 결과가 아니라 자신을 검사한 결과다.",
|
||||
lead: "남을 검사한 결과가 아니라 자신을 검사한 결과입니다.",
|
||||
items: [
|
||||
|
||||
{
|
||||
caught: "폰트가 교체되는 순간 글자 폭이 13% 튀고 있었다",
|
||||
caught: "폰트가 교체되는 순간 글자 폭이 13% 튀고 있었습니다",
|
||||
fixed:
|
||||
"웹폰트가 오기 전에는 시스템 폰트로 그려진다. 맑은 고딕은 Pretendard 보다 13% 넓어서 교체되는 순간 줄바꿈이 다시 잡힌다. 폴백 폰트에 size-adjust 로 치수를 맞췄다. 실측 차이 0.0%. OS 마다 시스템 폰트가 다르므로 폴백도 따로 정의했다.",
|
||||
"웹폰트가 오기 전에는 시스템 폰트로 그려진다. 맑은 고딕은 Pretendard 보다 13% 넓어서 교체되는 순간 줄바꿈이 다시 잡힙니다. 폴백 폰트에 size-adjust 로 치수를 맞췄다. 실측 차이 0.0%. OS 마다 시스템 폰트가 다르므로 폴백도 따로 정의했습니다.",
|
||||
},
|
||||
{
|
||||
caught: "유리 카드를 29개 뿌렸다. 재질만 바뀌었을 뿐 구조는 카드 나열 그대로였다",
|
||||
caught: "유리 카드를 29개 뿌렸습니다. 재질만 바뀌었을 뿐 구조는 카드 나열 그대로였습니다",
|
||||
fixed:
|
||||
"우리 규칙이 '카드는 elevation 이 진짜 위계를 나를 때만'이라고 정해두고 있었다. 수치는 조판으로, 규칙은 어긋난 목록으로, 감사 기록은 로그로 바꿨다. 유리는 5개만 남았다.",
|
||||
"우리 규칙이 '카드는 elevation 이 진짜 위계를 나를 때만'이라고 정해두고 있었습니다. 수치는 조판으로, 규칙은 어긋난 목록으로, 감사 기록은 로그로 바꿨습니다. 유리는 5개만 남았습니다.",
|
||||
},
|
||||
{
|
||||
caught: "“backdrop-filter 에 SVG 를 얹으면 Chrome 이 버린다” 고 적었는데 사실이 아니었다",
|
||||
caught: "“backdrop-filter 에 SVG 를 얹으면 Chrome 이 버린다” 고 적었는데 사실이 아니었습니다",
|
||||
fixed:
|
||||
"한 번 실패하고 브라우저 탓으로 적어뒀다. 다시 재보니 Chrome 151 에서 computed 값이 none 이 아니라 url(#id) 로 살아 있고 배경도 실제로 굴절된다. 진짜 원인은 feImage 의 width/height 를 요소 크기와 맞추지 않은 것이었다 — backdrop-filter 는 필터 이미지를 자동으로 조정하지 않는다. 한 번 실패한 것을 ‘안 되는 것’ 으로 적으면 그 문장이 다음 사람의 상한선이 된다.",
|
||||
"한 번 실패하고 브라우저 탓으로 적어뒀다. 다시 재보니 Chrome 151 에서 computed 값이 none 이 아니라 url(#id) 로 살아 있고 배경도 실제로 굴절됩니다. 진짜 원인은 feImage 의 width/height 를 요소 크기와 맞추지 않은 것이었습니다 — backdrop-filter 는 필터 이미지를 자동으로 조정하지 않습니다. 한 번 실패한 것을 ‘안 되는 것’ 으로 적으면 그 문장이 다음 사람의 상한선이 됩니다.",
|
||||
},
|
||||
{
|
||||
caught: "가로로 444px 넘쳤다. 레이아웃 전체가 어긋나 있었다",
|
||||
caught: "가로로 444px 넘쳤습니다. 레이아웃 전체가 어긋나 있었습니다",
|
||||
fixed:
|
||||
"main 에서 그리드 클래스가 빠져 있었다. subgrid 는 부모가 그리드일 때만 작동하므로 모든 섹션이 정렬선을 잃은 상태였다. 눈으로는 어색한 정도였고 폭을 재고서야 드러났다.",
|
||||
"main 에서 그리드 클래스가 빠져 있었습니다. subgrid 는 부모가 그리드일 때만 작동하므로 모든 섹션이 정렬선을 잃은 상태였습니다. 눈으로는 어색한 정도였고 폭을 재고서야 드러났다.",
|
||||
},
|
||||
{
|
||||
caught: "스크롤을 끝까지 내려도 파이프라인 6단계 중 뒤 3개가 안 보였다",
|
||||
caught: "스크롤을 끝까지 내려도 파이프라인 6단계 중 뒤 3개가 안 보였습니다",
|
||||
fixed:
|
||||
"scroll-driven animation 의 범위 끝에 페이지 하단 요소는 도달하지 못한다. 이펙트를 끄면 보이고 켜면 사라지는 것은 폴백이 있는 것과 다르다. 한 번 보이면 유지되는 방식으로 바꿨다.",
|
||||
"scroll-driven animation 의 범위 끝에 페이지 하단 요소는 도달하지 못합니다. 이펙트를 끄면 보이고 켜면 사라지는 것은 폴백이 있는 것과 다르다. 한 번 보이면 유지되는 방식으로 바꿨습니다.",
|
||||
},
|
||||
{
|
||||
caught: "다크 밴드 안 버튼의 대비가 2.88:1. 기준은 4.5:1",
|
||||
fixed:
|
||||
"밴드가 강조색만 반전시키고 그 위 글자색은 흰색 그대로였다. 반전 대응 토큰을 하나 더 두고 함께 바꿨다.",
|
||||
"밴드가 강조색만 반전시키고 그 위 글자색은 흰색 그대로였습니다. 반전 대응 토큰을 하나 더 두고 함께 바꿨습니다.",
|
||||
},
|
||||
],
|
||||
note: "열세 건 모두 만들면서 걸린 것이고, 걸린 뒤에 고쳤다. 사람이 잡은 것이 셋이다. 카드가 전형적이라는 말, 폰트를 제대로 다루지 못한다는 말, 그리고 우리 스킬을 진짜로 쓴 게 맞냐는 말.",
|
||||
note: "열세 건 모두 만들면서 걸린 것이고, 걸린 뒤에 고쳤다. 사람이 잡은 것이 셋이다. 카드가 전형적이라는 말, 폰트를 제대로 다루지 못합니다는 말, 그리고 우리 스킬을 진짜로 쓴 게 맞냐는 말.",
|
||||
},
|
||||
typography: {
|
||||
eyebrow: "한글 조판",
|
||||
h2: "라틴 기본값은 한글에서 세 군데가 깨진다",
|
||||
lead:
|
||||
"영어권 디자인 도구에는 한글 조판 규칙이 없다. 그래서 기본값을 그대로 쓰면 아래 셋이 동시에 무너진다. 왼쪽이 기본값, 오른쪽이 이 스킬이 정한 값이다. 글자도 폭도 폰트도 같다.",
|
||||
sample: "스페셜티커피의 로스팅프로파일을 구분하려면 산미와 바디를 수치로 봐야 한다.",
|
||||
"영어권 디자인 도구에는 한글 조판 규칙이 없습니다. 그래서 기본값을 그대로 쓰면 아래 셋이 동시에 무너집니다. 왼쪽이 기본값, 오른쪽이 이 스킬이 정한 값이다. 글자도 폭도 폰트도 같습니다.",
|
||||
sample: "스페셜티커피의 로스팅프로파일을 구분하려면 산미와 바디를 수치로 봐야 합니다.",
|
||||
demos: [
|
||||
{
|
||||
id: "break",
|
||||
title: "줄바꿈",
|
||||
badLabel: "기본값",
|
||||
goodLabel: "word-break: keep-all",
|
||||
note: "기본값은 어절 한가운데를 끊는다. 왼쪽 마지막 줄을 보라 — 한 단어가 두 줄에 걸쳐 있다.",
|
||||
note: "기본값은 어절 한가운데를 끊는다. 왼쪽 마지막 줄을 보라 — 한 단어가 두 줄에 걸쳐 있습니다.",
|
||||
},
|
||||
{
|
||||
id: "leading",
|
||||
title: "행간",
|
||||
badLabel: "line-height 1.45",
|
||||
goodLabel: "line-height 1.75",
|
||||
note: "라틴 표준 1.4~1.5 는 받침이 있는 글자에 좁다. 줄이 서로 붙어 보인다.",
|
||||
note: "라틴 표준 1.4~1.5 는 받침이 있는 글자에 좁습니다. 줄이 서로 붙어 보입니다.",
|
||||
},
|
||||
{
|
||||
id: "size",
|
||||
title: "크기",
|
||||
badLabel: "11px",
|
||||
goodLabel: "15px",
|
||||
note: "라틴에서 11px 은 라벨로 쓴다. 한글은 획이 많아 같은 크기에서 뭉갠다.",
|
||||
note: "라틴에서 11px 은 라벨로 씁니다. 한글은 획이 많아 같은 크기에서 뭉갠다.",
|
||||
},
|
||||
],
|
||||
rules: [
|
||||
"word-break: keep-all 없으면 단어가 아무 데서나 잘린다",
|
||||
"word-break: keep-all 없으면 단어가 아무 데서나 잘립니다",
|
||||
"line-height 1.6에서 1.8 사이. 라틴보다 넉넉하게",
|
||||
"음수 자간 금지. 한글은 이미 고정폭에 가까워 즉시 뭉개진다",
|
||||
"한 줄 25자에서 40자. 라틴 기준 65자를 그대로 쓰면 너무 길다",
|
||||
"한글 폰트를 지정하지 않는 것 자체가 완성도 미달 신호다",
|
||||
"음수 자간 금지. 한글은 이미 고정폭에 가까워 즉시 뭉개집니다",
|
||||
"한 줄 25자에서 40자. 라틴 기준 65자를 그대로 쓰면 너무 깁니다",
|
||||
"한글 폰트를 지정하지 않는 것 자체가 완성도 미달 신호입니다",
|
||||
],
|
||||
},
|
||||
rules: {
|
||||
h2: "규칙마다 왜 그런지 적혀 있다",
|
||||
lead: "근거 없는 금지는 지켜지지 않는다. 사용자가 명시적으로 요구하면 규칙보다 그쪽이 이긴다.",
|
||||
lead: "근거 없는 금지는 지켜지지 않습니다. 사용자가 명시적으로 요구하면 규칙보다 그쪽이 이깁니다.",
|
||||
items: [
|
||||
{ rule: "레퍼런스 없이 시작하지 않는다", why: "머릿속 기본값에서 출발하면 결과는 그 카테고리의 평균이 된다." },
|
||||
{ rule: "강조색은 하나다", why: "두 개가 필요하다고 느끼면 위계 설계가 실패한 것이다." },
|
||||
{ rule: "대담함은 한 곳에만", why: "두 곳에서 소리치면 둘 다 죽는다." },
|
||||
{ rule: "화려함은 4순위다", why: "심사 배점이 Design 40 / Usability 30 / Creativity 20 / Content 10이다." },
|
||||
{ rule: "지어낸 수치를 쓰지 않는다", why: "숫자 모양의 구멍은 정직하고, 지어낸 숫자는 신뢰를 깎는다." },
|
||||
{ rule: "이펙트를 다 꺼도 완성돼 있어야 한다", why: "그 상태가 모든 폴백의 기반이다. 무너지면 이펙트가 구조를 대신하고 있는 것이다." },
|
||||
{ rule: "레퍼런스 없이 시작하지 않는다", why: "머릿속 기본값에서 출발하면 결과는 그 카테고리의 평균이 됩니다." },
|
||||
{ rule: "강조색은 하나다", why: "두 개가 필요하다고 느끼면 위계 설계가 실패한 것입니다." },
|
||||
{ rule: "대담함은 한 곳에만", why: "두 곳에서 소리치면 둘 다 죽습니다." },
|
||||
{ rule: "화려함은 4순위다", why: "심사 배점이 Design 40 / Usability 30 / Creativity 20 / Content 10입니다." },
|
||||
{ rule: "지어낸 수치를 쓰지 않는다", why: "숫자 모양의 구멍은 정직하고, 지어낸 숫자는 신뢰를 깎습니다." },
|
||||
{ rule: "이펙트를 다 꺼도 완성돼 있어야 한다", why: "그 상태가 모든 폴백의 기반이다. 무너지면 이펙트가 구조를 대신하고 있는 것입니다." },
|
||||
],
|
||||
},
|
||||
install: {
|
||||
h2: "한 줄이면 된다",
|
||||
lead: "설치 대상을 감지해 기본 선택해준다. 스킬 파일을 직접 고쳤다면 업데이트가 그 파일을 건드리지 않는다.",
|
||||
lead: "설치 대상을 감지해 기본 선택해준다. 스킬 파일을 직접 고쳤다면 업데이트가 그 파일을 건드리지 않습니다.",
|
||||
command: "npx designpaca",
|
||||
targets: [
|
||||
{ name: "Claude Code", path: "~/.claude/skills/designpaca/" },
|
||||
|
|
@ -386,7 +386,7 @@ export const ko: Content = {
|
|||
{ name: "AGENTS.md", path: "블록 주입. 에이전트 무관" },
|
||||
],
|
||||
commands: [
|
||||
{ cmd: "npx designpaca update", what: "최신 스킬로 갱신. 직접 고친 파일은 건너뛴다" },
|
||||
{ cmd: "npx designpaca update", what: "최신 스킬로 갱신. 직접 고친 파일은 건너뜁니다" },
|
||||
{ cmd: "npx designpaca doctor", what: "설치 상태와 드리프트 진단" },
|
||||
{ cmd: "npx designpaca uninstall", what: "매니페스트 기반 정확한 제거" },
|
||||
],
|
||||
|
|
@ -394,7 +394,7 @@ export const ko: Content = {
|
|||
nav: {
|
||||
items: [
|
||||
{ label: "파이프라인", href: "#pipeline" },
|
||||
{ label: "사례", href: "#showcase" },
|
||||
{ label: "사례", href: "/showcase" },
|
||||
{ label: "수치", href: "#metrics" },
|
||||
{ label: "설치", href: "#install" },
|
||||
],
|
||||
|
|
@ -661,7 +661,7 @@ export const en: Content = {
|
|||
nav: {
|
||||
items: [
|
||||
{ label: "Pipeline", href: "#pipeline" },
|
||||
{ label: "Work", href: "#showcase" },
|
||||
{ label: "Work", href: "/showcase" },
|
||||
{ label: "Numbers", href: "#metrics" },
|
||||
{ label: "Install", href: "#install" },
|
||||
],
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
import Base from "../../layouts/Base.astro";
|
||||
import Hero from "../../components/Hero.astro";
|
||||
import Pipeline from "../../components/Pipeline.astro";
|
||||
import Showcase from "../../components/Showcase.astro";
|
||||
import Materials from "../../components/Materials.astro";
|
||||
import Metrics from "../../components/Metrics.astro";
|
||||
import Preflight from "../../components/Preflight.astro";
|
||||
|
|
@ -20,7 +19,7 @@ import { en as c } from "../../i18n/content";
|
|||
|
||||
① 무엇인가 히어로 · 파이프라인
|
||||
② 무엇을 아는가 SVG 필터 · 한글 조판
|
||||
③ 정말 그런가 사례 · 수치 · 자기 감사 · 규칙
|
||||
③ 정말 그런가 수치 · 자기 감사 · 규칙 (사례는 /showcase 로 뺐다)
|
||||
④ 행동 설치
|
||||
*/}
|
||||
<Hero c={c.hero} ui={c.ui} />
|
||||
|
|
@ -29,7 +28,6 @@ import { en as c } from "../../i18n/content";
|
|||
<Materials c={c.materials} />
|
||||
<Typography c={c.typography} />
|
||||
|
||||
<Showcase c={c.showcase} />
|
||||
<Metrics c={c.metrics} />
|
||||
<Preflight c={c.preflight} />
|
||||
<Rules c={c.rules} />
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
import Base from "../layouts/Base.astro";
|
||||
import Hero from "../components/Hero.astro";
|
||||
import Pipeline from "../components/Pipeline.astro";
|
||||
import Showcase from "../components/Showcase.astro";
|
||||
import Materials from "../components/Materials.astro";
|
||||
import Metrics from "../components/Metrics.astro";
|
||||
import Preflight from "../components/Preflight.astro";
|
||||
|
|
@ -20,7 +19,7 @@ import { ko as c } from "../i18n/content";
|
|||
|
||||
① 무엇인가 히어로 · 파이프라인
|
||||
② 무엇을 아는가 SVG 필터 · 한글 조판
|
||||
③ 정말 그런가 사례 · 수치 · 자기 감사 · 규칙
|
||||
③ 정말 그런가 수치 · 자기 감사 · 규칙 (사례는 /showcase 로 뺐다)
|
||||
④ 행동 설치
|
||||
*/}
|
||||
<Hero c={c.hero} ui={c.ui} />
|
||||
|
|
@ -29,7 +28,6 @@ import { ko as c } from "../i18n/content";
|
|||
<Materials c={c.materials} />
|
||||
<Typography c={c.typography} />
|
||||
|
||||
<Showcase c={c.showcase} />
|
||||
<Metrics c={c.metrics} />
|
||||
<Preflight c={c.preflight} />
|
||||
<Rules c={c.rules} />
|
||||
|
|
|
|||
256
apps/site/src/pages/showcase.astro
Normal file
256
apps/site/src/pages/showcase.astro
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
---
|
||||
import { Image } from "astro:assets";
|
||||
import Base from "../layouts/Base.astro";
|
||||
import BgPhoto from "../components/BgPhoto.astro";
|
||||
import { WORK } from "../data/work.ts";
|
||||
|
||||
/*
|
||||
쇼케이스. 소개 페이지 안의 섹션이 아니라 **별도 페이지**다.
|
||||
|
||||
홈에 있던 예전 섹션은 스크린샷 두 장을 카드에 넣고 각도를 살짝 준 것이었고,
|
||||
들어갈 곳이 없었다 — 그림만 보여주고 끝나는 자리는 증거가 아니라 장식이다.
|
||||
여기서는 타일을 누르면 **실제로 도는 사이트**로 들어간다.
|
||||
|
||||
타일 크기를 섞는다. 균일한 격자는 이 스킬이 금지하는 형태이고,
|
||||
사례마다 중요도가 다른데 같은 크기로 두면 그 차이가 사라진다.
|
||||
*/
|
||||
const title = "사례 · designpaca";
|
||||
const description =
|
||||
"designpaca 파이프라인으로 만든 사이트들. 각 사례의 컨셉과 감수한 리스크, 실측값을 함께 적었습니다. 타일을 누르면 실제 사이트가 열립니다.";
|
||||
---
|
||||
|
||||
<Base lang="ko" title={title} description={description}>
|
||||
<section class="section intro has-bg" aria-labelledby="sc-h">
|
||||
<BgPhoto variant="align" mode="fixed" opacity={0.85} />
|
||||
<p class="eyebrow reveal">사례</p>
|
||||
<h1 id="sc-h" class="reveal">파이프라인을 돌려서 나온 것들</h1>
|
||||
<p class="lead intro-lead reveal">
|
||||
모두 0단계부터 6단계까지 그대로 돌려서 나온 결과물입니다. 각 사례마다
|
||||
<strong>감수한 리스크</strong>를 함께 적었습니다 — 그것이 없으면 안전하지만 잊히는 디자인이 됩니다.
|
||||
</p>
|
||||
<p class="muted intro-note reveal">
|
||||
타일을 누르면 실제 사이트가 열립니다. 목업이 아니라 동작하는 페이지입니다.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="section grid-section" aria-label="사례 목록">
|
||||
<ul class="full mosaic">
|
||||
{
|
||||
WORK.map((w, i) => (
|
||||
<li class:list={["tile", `tile-${w.size}`, "reveal"]} style={`--reveal-delay:${i * 80}ms`}>
|
||||
<a class="tile-link" href={w.href}>
|
||||
<span class="tile-shot">
|
||||
<Image
|
||||
src={w.shot}
|
||||
alt={w.alt}
|
||||
widths={[520, 900, 1440]}
|
||||
sizes="(max-width: 820px) 92vw, 46vw"
|
||||
loading={i === 0 ? "eager" : "lazy"}
|
||||
/>
|
||||
{w.shotAlt && (
|
||||
<Image
|
||||
class="tile-shot-alt"
|
||||
src={w.shotAlt}
|
||||
alt=""
|
||||
widths={[520, 900, 1440]}
|
||||
sizes="(max-width: 820px) 92vw, 46vw"
|
||||
loading="lazy"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
|
||||
<span class="tile-body">
|
||||
<span class="tile-head">
|
||||
<span class="tile-name">{w.name}</span>
|
||||
<span class="tile-kind muted">{w.kind}</span>
|
||||
</span>
|
||||
<span class="tile-concept">{w.concept}</span>
|
||||
<span class="tile-risk">
|
||||
<span class="risk-label">감수한 리스크</span>
|
||||
{w.risk}
|
||||
</span>
|
||||
<span class="tile-metrics mono">
|
||||
{w.metrics.map((m) => (
|
||||
<span class="metric">{m}</span>
|
||||
))}
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
|
||||
</section>
|
||||
</Base>
|
||||
|
||||
<style>
|
||||
/* .page 그리드는 base.css 가 준다. 여기서 다시 정의하면
|
||||
main 칼럼이 본문 폭(--measure)에서 벗어나 전 페이지가 어긋난다. */
|
||||
.intro {
|
||||
padding-block: calc(var(--header-h) + var(--space-7)) var(--space-7);
|
||||
}
|
||||
/* 목록 페이지의 제목이라 홈 히어로만큼 클 이유가 없다.
|
||||
h1 기본값(--display)은 여기서 세 줄로 접힌다. */
|
||||
#sc-h {
|
||||
font-size: clamp(2rem, 1.3rem + 2.4vw, 3.4rem);
|
||||
max-width: 18ch;
|
||||
}
|
||||
.intro-lead {
|
||||
margin-bottom: var(--space-4);
|
||||
max-width: var(--measure);
|
||||
}
|
||||
.intro-note {
|
||||
max-width: var(--measure);
|
||||
}
|
||||
|
||||
.grid-section {
|
||||
padding-block: 0 var(--space-8);
|
||||
}
|
||||
|
||||
/* ── 모자이크 ────────────────────────────────────────
|
||||
타일 크기가 섞인다. 사례마다 무게가 다른데 같은 크기로 두면
|
||||
그 차이가 사라지고, 그때부터는 격자가 내용을 지운다. */
|
||||
.mosaic {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
max-width: 1180px;
|
||||
margin-inline: auto;
|
||||
padding: 0 var(--gutter);
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
gap: var(--space-6) var(--space-5);
|
||||
}
|
||||
.tile {
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
.tile-lg {
|
||||
grid-column: span 6;
|
||||
}
|
||||
.tile-md {
|
||||
grid-column: span 3;
|
||||
}
|
||||
.tile-sm {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.tile-link {
|
||||
display: grid;
|
||||
gap: var(--space-4);
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
/* 큰 타일은 그림과 글을 나란히 둔다. 세로로 쌓으면 화면을 통째로 먹는다 */
|
||||
.tile-lg .tile-link {
|
||||
grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
|
||||
gap: var(--space-6);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tile-shot {
|
||||
position: relative;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--line);
|
||||
background: var(--surface-2);
|
||||
aspect-ratio: 16 / 10;
|
||||
}
|
||||
.tile-shot :global(img) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: top center;
|
||||
display: block;
|
||||
}
|
||||
/* 두 번째 컷은 위에 겹쳐 두고 호버에서 바꾼다.
|
||||
스크롤이나 자동 전환으로 바꾸지 않는다 — 읽는 사람의 속도를 뺏지 않는다 */
|
||||
.tile-shot-alt {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.tile-body {
|
||||
display: grid;
|
||||
gap: var(--space-3);
|
||||
align-content: start;
|
||||
}
|
||||
.tile-head {
|
||||
display: grid;
|
||||
gap: var(--space-1);
|
||||
}
|
||||
.tile-name {
|
||||
font-size: var(--step-1);
|
||||
font-weight: var(--weight-medium);
|
||||
}
|
||||
.tile-kind {
|
||||
font-size: var(--step-0);
|
||||
}
|
||||
.tile-concept {
|
||||
color: var(--ink);
|
||||
max-width: 34em;
|
||||
}
|
||||
.tile-risk {
|
||||
display: grid;
|
||||
gap: var(--space-1);
|
||||
padding-top: var(--space-3);
|
||||
border-top: 1px solid var(--line);
|
||||
color: var(--ink-muted);
|
||||
max-width: 38em;
|
||||
}
|
||||
.risk-label {
|
||||
font-size: var(--step-0);
|
||||
letter-spacing: var(--tracking-label);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.tile-metrics {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-2) var(--space-4);
|
||||
font-size: var(--mono-size);
|
||||
color: var(--ink-muted);
|
||||
}
|
||||
.metric + .metric::before {
|
||||
content: "·";
|
||||
margin-right: var(--space-4);
|
||||
color: var(--line-strong);
|
||||
}
|
||||
|
||||
|
||||
/* ── 호버: 그림이 바뀌고 테두리가 선다 ─────────────── */
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.tile-shot :global(img) {
|
||||
transition: opacity var(--dur-slow) var(--ease-soft);
|
||||
}
|
||||
.tile-shot {
|
||||
transition: border-color var(--dur-quick) var(--ease-out);
|
||||
}
|
||||
.tile-link:hover .tile-shot,
|
||||
.tile-link:focus-visible .tile-shot {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
.tile-link:hover .tile-shot-alt,
|
||||
.tile-link:focus-visible .tile-shot-alt {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.mosaic {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.tile-lg,
|
||||
.tile-md,
|
||||
.tile-sm {
|
||||
grid-column: span 1;
|
||||
}
|
||||
.tile-lg .tile-link {
|
||||
grid-template-columns: 1fr;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Loading…
Add table
Add a link
Reference in a new issue