feat(site): rework 19 marketing showcases for composition diversity
Some checks are pending
ci / build (push) Waiting to run

Measured convergence before: split hero 13/19, top-bar nav 19/19,
one-line notice footer 14/19, body Pretendard 17/21, 10 similarity
pairs above 0.78. Each showcase got a pre-assigned hero family, nav
form, footer form, type pair, background, accent hue, radius, motion
grammar and material from outputs/rework-2026-10-06/plan.md; direction
cards and the chosen reason are recorded in every design.md.

After: 0 similarity warnings, 19 distinct display faces, 7 hero
families, 5 nav forms, 5 footer forms, no scroll reveals, 5 showcases
with SVG filter materials. Example contact values replace bracket
placeholders under the existing fictional-brand notices. Thumbnails,
OG images, card copy and three e2e assertions updated; dead CSS in
dure-enrollment and hannam-jari removed; similarity.mjs learns the new
font families.
This commit is contained in:
Yun Chan 2026-10-06 16:55:50 +09:00
parent 88c11682b1
commit 8e3cdb0d3b
296 changed files with 19131 additions and 17538 deletions

View file

@ -2,26 +2,45 @@
## 브리프
- 무엇을: 분산 엣지 확인과 보고 필드를 보여 주는 가상 모니터링 워크플로.
- 누구에게: 예시 프레임의 시간·단위·위치·응답값을 비교하려는 방문자.
- 제약: 실제 probe·계정 생성이 없다는 표기와 기존 값·단위·원장형 정렬을 유지한다.
- 무엇을: 한 엔드포인트를 세계 여러 지점에서 같은 형식으로 확인하는 가상 엣지 모니터링 서비스.
- 누구에게: 지점별 응답 시간을 한눈에 비교하고, 늦어진 곳만 골라 보려는 운영 담당자.
- 제약: 실제 점검·계정 생성이 없다는 사실을 페이지 범위에서 한 번 밝힌다. 기존 값(6개 지점의 DNS·TLS·첫 바이트·p99)과 표 구조, Tab으로 닿는 내부 앵커를 유지한다.
- 기본 언어: 한국어(`lang="ko"`). 지점 코드와 프로토콜 이름만 라틴으로 둔다.
## 2026-10 재작업 이유
2026-09판은 전형적인 개발도구 다크 템플릿이었고 lumina-optics·synapse-bci와 같은 틀이었다(유사도 0.84·0.83). 영어 페이지에 고지 단어가 32회 나왔고 히어로 본문이 "This page illustrates…"로 시작했다.
## 결정표
| 축 | 관찰과 적용 범위 | 결정·역할 토큰 | 기각 조건 |
| 축 | 이 브리프의 재료 | 결정·역할 토큰 | 기각 조건 |
|---|---|---|---|
| 구도 | 기준 화면에서 명령과 제한 설명이 분산되고 히어로 하단 빈 면이 컸다. | 명령과 설명을 하나의 왼쪽 경계 군집으로 묶고 히어로 하단 간격을 줄인다. `--space-5`, `--space-6`, `--accent-stroke`. | 군집 경계가 코드 스크롤이나 제한 문구를 가리면 제거한다. |
| 타입 | 시간·단위와 위치 행이 차트보다 약하면 값의 해석 범위가 흐려진다. | 머리말·행을 `--step--1`, 값은 `--font-mono` tabular 정렬로 유지한다. | 320px/200%에서 값 열이 잘리면 의도된 표 스크롤을 유지하고 행을 축소하지 않는다. |
| 색 | 선택된 시계열과 보조 시계열이 같은 위계로 읽혔다. | `--accent-stroke` 선택선, `--ink-muted` 보조선, `--ink-faint` 먼 선으로 역할을 분리한다. | 코발트가 실제 서비스 상태·알람으로 오해되거나 단위보다 강하면 선 대비를 낮춘다. |
| 은유 | 지점 이름 ICN·NRT·SIN·SJC·LHR·FRA가 원래 공항 코드다. | 공항 출발 안내판. 표가 곧 히어로 이미지이고, 지점 코드와 숫자를 플랩 낱장 한 칸에 한 글자씩 올린다. | 플랩 때문에 표가 화면 낭독기에서 읽히지 않으면 원문 `sr-only` 사본을 확인한다. |
| 바탕 | 안내판과 도로 표지의 신호 노랑. 운영 화면은 보통 어둡다 — 그 관습을 뒤집어 이 서비스만의 얼굴로 쓴다. | `--surface: #ffd23a`, 잉크 `#121110`(대비 ≈13:1). 강조는 검은 낱장과 `--accent: #b3121f` '주의' 판정 하나. | 빨강 판정이 노랑 위에서 4.5:1 아래면 더 어둡게 한다. |
| 판정 | p99 150 ms를 넘은 FRA-01만 다르게 보여야 한다. | 판정 열을 새로 두고 `주의 · 150 초과`만 빨강. 나머지는 `정상`. | 판정 색만으로 구분하지 않는다 — 문구가 함께 바뀐다. |
| 타입 | 안내판 글자는 좁고 각진 간판체다. | 표제·플랩 도현(Do Hyeon), 본문 Gothic A1. 모노 없음 — 등폭은 낱장 칸이 맡는다. `code`도 본문 서체 800. | 도현의 좁은 자폭 때문에 두 줄 표제가 세 줄이 되면 `--step-3` 상한을 낮춘다. |
| 구도 | 첫 화면에서 표제와 안내판이 함께 보여야 한다. | 왼쪽 큰 표제 + 오른쪽 짧은 설명·행동, 그 아래 화면 폭 전체 안내판. 좁은 화면은 DNS·TLS 열을 접어 p99와 판정을 남긴다. | 390 폭에서 표제와 안내판 머리가 첫 화면에 같이 들어오지 않으면 히어로 여백을 줄인다. |
| 모션 | 기계식 안내판은 한 번 넘어가고 멈춘다. | 첫 로드에 플랩이 260~680 ms 넘어가다 표의 값에 멈춘다. 값을 지어내지 않는다. 움직임 줄이기면 넘기지 않는다. 호버는 즉시 반전. 스크롤 등장 없음. | — |
| 형태 | 낱장 모서리만 둥글다. | `--radius: 4px`(플랩·버튼). 면과 구분선은 각지게. | — |
## 근거와 경계
## 카피 방향 (2026-10-02)
[art-direction.md](../../../../../packages/skill/references/art-direction.md)의 Hofmann 관련 군집·정렬 관찰은 명령/설명과 수치 원장을 묶는 데만 적용했다. [typography.md](../../../../../packages/skill/references/typography.md)의 Ruder 관련 숫자·긴 라벨 proof는 표와 instrument 행에만 적용했다. [visual-design-lineage.md](../../../../../packages/skill/references/visual-design-lineage.md)의 Albers 관련 맥락색 관찰은 cobalt 선택선과 보조선 대비에만 적용했다. 원전은 판독 속도나 제품 성과 법칙으로 사용하지 않는다.
표제·섹션 제목은 무엇을 하는 서비스인지 사실대로 쓴다. "28개 도시가 10초마다 묻는다"처럼 지점을 사람처럼 말하게 하는 문장(무생물 주어 + 사람 동사)을 "28개 지점에서 10초마다 점검"으로 바꿨다. 안내판은 디자인 은유로만 두고 카피에서는 "지점별 응답 시간", "점검 방식"처럼 운영 담당자가 쓰는 말을 쓴다. 어미는 합니다체 하나로 통일한다.
[trustworthy-showcases.md](../../../../../packages/skill/references/trustworthy-showcases.md)에 따라 illustrative latency frame, 시간·단위 설명, 실제 probe 부재 표기를 각 값·행동 가까이에 유지한다.
## 진실 범위
## 2026-09-12 현재 검증 기록
헤더 아래 띠 한 곳("지점·응답 시간·요금은 예시이며, 이 페이지는 실제 점검을 실행하지 않습니다")과 요금표 열 머리글 `요금(예시)` 한 번. 표제·히어로 본문·안내판 셀에는 고지 문구를 넣지 않는다.
- `verify-final` 18개 묶음은 16개가 즉시 통과했다. 전체 묶음의 invariant·visual 검사는 Gaon·Dure를 대상으로 하며, 나머지 2개는 수정 뒤 release 독립 재실행에서 ALL PASS·ALL MATCH였다.
- root는 28개 모바일, 21개 데스크톱, 7개 하위 경로와 30개 앱 상태를 직접 검토해 수용했다. stress 112건은 hard failure 0이며, 가로 넘침 4건은 targeted fix 뒤 390px 재측정에서 0이다.
- 이 기록은 렌더·계약 범위의 현재 결과다. 모든 폭·키보드 경로·인쇄 등 개별 기능을 완전 재실행했다는 뜻이 아니며, 참여자 과업 효과도 미측정이다.
## 검증
- `node tools/instrument-showcases-e2e.mjs` — 내부 앵커 대상 존재·Tab 도달, 표 읽기 가능.
- `node tools/similarity.mjs` — 재작업 엄격 대상.
## 내비·푸터 결정 (2026-10-06 연장)
- 내비 없음: 섹션이 넷(히어로·응답 시간·점검 방식·요금)이고 행동은 하나(무료 요금제로 시작)뿐이라 상단 바의 섹션 링크를 뺐다. 상단 바에는 워드마크와 `.btn.btn-head` "무료로 시작"(`#plan-free`, 요금표의 무료 행)만 남긴다. 히어로의 "요금 보기"·"점검 방식 보기" 두 링크도 같은 이유로 뺐다 — 표가 바로 아래에 있어 안내가 필요 없다.
- 푸터: 다음 행동 하나 크게. `.foot-cta`(노랑 낱장 색, 표제 크기 `--step-3`, 높이 9~15rem)가 푸터 면적의 대부분이고, 아래 한 줄에 브랜드와 쇼케이스 표기만 둔다. 링크는 헤더와 같은 `#plan-free`. 가입 화면은 없다(진실 범위: 이 페이지는 계정을 만들지 않는다)고 이미 띠에 적혀 있으므로 푸터에 고지를 반복하지 않는다. 검은 푸터 위 포커스 링은 잉크색이 사라져 노랑(`--surface`)으로 바꿨다.
## 변경 기록
- 2026-10-06 연장: 상단 바 내비 제거, 히어로 행동 2개 제거, 푸터를 "다음 행동 하나 크게"로 교체. 토큰·서체·히어로 구도·표·플랩 모션은 그대로. E2E(`instrument-showcases-e2e.mjs`)가 보는 내부 앵커는 `#main`·`#plan-free`로 줄었고 모두 대상이 있다.

View file

@ -1,276 +1,165 @@
<!doctype html>
<html lang="en">
<html lang="ko">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Pulsegate · Edge Monitoring Workflow</title>
<title>Pulsegate · 전 세계 서버 응답 모니터링</title>
<meta
name="description"
content="An illustrative workflow for checking an endpoint from distributed edge locations. It does not create probes or provide a live service."
/>
<meta property="og:title" content="Pulsegate · Edge Monitoring Workflow" />
<meta
property="og:description"
content="An illustrative edge-monitoring flow with endpoints, locations, and reporting fields."
content="전 세계 28개 지점에서 같은 엔드포인트를 10초마다 확인하고, 지점별 응답 시간을 한 표로 보여 주는 가상 모니터링 서비스입니다."
/>
<meta property="og:title" content="Pulsegate · 전 세계 서버 응답 모니터링" />
<meta property="og:description" content="지점별 DNS·TLS·첫 바이트 응답 시간을 한 표에서 비교하는 엣지 모니터링 서비스." />
<meta property="og:type" content="website" />
<meta name="theme-color" content="#0B0E12" />
<meta property="og:image" content="./og.png" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="theme-color" content="#ffd23a" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600&family=IBM+Plex+Sans+KR:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap"
href="https://fonts.googleapis.com/css2?family=Do+Hyeon&family=Gothic+A1:wght@400;700;800&display=swap"
/>
<link rel="stylesheet" href="./styles/main.css?v=20260912all" />
<link rel="stylesheet" href="./styles/main.css?v=20261006" />
<link
rel="icon"
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' fill='%230B0E12'/%3E%3Cpath d='M6 17h6l3-8 4 14 3-6h4' fill='none' stroke='%236CA3EE' stroke-width='2'/%3E%3C/svg%3E"
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' fill='%23FFD23A'/%3E%3Crect x='8' y='5' width='16' height='22' rx='2' fill='%23141414'/%3E%3Cpath d='M8 16h16' stroke='%232D2D2D'/%3E%3Cpath d='M12 11h8M16 11v12' stroke='%23FFD23A' stroke-width='2.4'/%3E%3C/svg%3E"
/>
</head>
<body>
<a class="skip" href="#main">Skip to main content</a>
<a class="skip" href="#main">본문으로 건너뛰기</a>
<header class="site-head">
<div class="wrap head-inner">
<a class="wordmark" href="#main">pulsegate<span class="wordmark-dot">.</span></a>
<nav class="nav" aria-label="Sections">
<a href="#wiring">Wiring</a>
<a href="#numbers">Numbers</a>
<a href="#pricing">Pricing</a>
</nav>
<a class="wordmark" href="#main"><span class="wordmark-flap" aria-hidden="true">P</span>Pulsegate</a>
<a class="btn btn-head" href="#plan-free">무료로 시작</a>
</div>
</header>
<div class="scope-note">
<p class="wrap">가상 모니터링 서비스의 쇼케이스입니다. 지점·응답 시간·요금은 예시이며, 이 페이지는 실제 점검을 실행하지 않습니다.</p>
</div>
<main id="main">
<section class="hero" aria-labelledby="hero-h">
<div class="wrap hero-grid">
<div class="hero-text">
<h1 id="hero-h" class="reveal">
Understand an edge<br />check at a glance
</h1>
<p class="lead reveal" style="--delay: 80ms">
This page illustrates how one endpoint can be checked from several
edge locations and reported with the same fields. Locations,
timings, plans, and results are examples; no probe runs from this page.
<h1 id="hero-h">28개 지점에서<br />10초마다 점검</h1>
<div class="hero-side">
<p class="lead">
서울부터 프랑크푸르트까지 모든 지점에서 같은 <code>GET /health</code> 요청을 보내고,
응답이 기준보다 늦은 지점만 빨간색으로 표시합니다.
</p>
<dl class="probe-proof reveal" style="--delay: 110ms">
<div>
<dt>example endpoint</dt>
<dd><code>GET /health</code></dd>
</div>
<div>
<dt>example origin</dt>
<dd>Seoul</dd>
</div>
<div>
<dt>example cadence</dt>
<dd>10 s · 28 locations</dd>
</div>
</dl>
<div class="hero-actions reveal" style="--delay: 140ms">
<a class="btn btn-primary" href="#pricing">View example plans</a>
<a class="link-quiet" href="#wiring">See the check flow</a>
</div>
<div class="workflow-command">
<div class="command reveal" style="--delay: 200ms">
<span class="command-prompt" aria-hidden="true">$</span>
<code>GET /health → inspect the response</code>
</div>
<p class="hero-note reveal" style="--delay: 240ms">
Illustrative workflow only — this page does not create an account,
run a check, or collect a destination URL.
</p>
</div>
</div>
</div>
</section>
<figure class="instrument reveal" style="--delay: 160ms">
<figcaption class="instrument-head">
<span>ILLUSTRATIVE LATENCY FRAME · P99 · 24H</span>
<span>example 10s interval</span>
</figcaption>
<svg
viewBox="0 0 560 264"
role="img"
aria-label="Illustrative 24-hour p99 latency chart with example locations and timings; it is not live monitoring data."
>
<g class="chart-grid">
<line x1="0" y1="8" x2="560" y2="8" />
<line x1="0" y1="70" x2="560" y2="70" />
<line x1="0" y1="132" x2="560" y2="132" />
<line x1="0" y1="194" x2="560" y2="194" />
<line x1="0" y1="256" x2="560" y2="256" class="chart-axis" />
<line x1="70" y1="0" x2="70" y2="264" />
<line x1="140" y1="0" x2="140" y2="264" />
<line x1="210" y1="0" x2="210" y2="264" />
<line x1="280" y1="0" x2="280" y2="264" />
<line x1="350" y1="0" x2="350" y2="264" />
<line x1="420" y1="0" x2="420" y2="264" />
<line x1="490" y1="0" x2="490" y2="264" />
</g>
<polyline
class="series series-faint"
points="0,96 35,94 70,97 105,92 140,95 175,93 205,88 210,74 215,60 220,74 225,90 260,95 280,93 315,96 350,94 385,97 420,93 455,96 490,94 525,97 560,95"
/>
<polyline
class="series series-muted"
points="0,224 35,222 70,225 105,221 140,224 175,223 210,226 245,222 280,224 315,223 350,226 385,222 420,225 455,223 490,226 525,223 560,225"
/>
<polyline
class="series series-accent"
points="0,243 35,242 70,244 105,242 140,243 175,244 210,242 245,243 280,244 315,242 350,243 385,244 420,243 455,242 490,244 525,243 560,243"
/>
</svg>
<div class="instrument-rows" role="group" aria-label="Illustrative location readings, p99 over one example frame">
<div><span>ICN-01 · Seoul</span><span class="num">14.2 ms</span></div>
<div><span>NRT-02 · Tokyo</span><span class="num">28.6 ms</span></div>
<div><span>SIN-01 · Singapore</span><span class="num">34.0 ms</span></div>
<div><span>SJC-04 · San Jose</span><span class="num">88.4 ms</span></div>
<div><span>LHR-02 · London</span><span class="num">126.8 ms</span></div>
<div><span>FRA-01 · Frankfurt</span><span class="num">142.1 ms</span></div>
</div>
</figure>
<section id="board" class="board" aria-labelledby="board-h">
<div class="wrap">
<div class="board-head">
<h2 id="board-h">지점별 응답 시간</h2>
<p>p99 · 최근 24시간 · 10초 간격 · 단위 ms</p>
</div>
<div class="table-wrap">
<table class="board-table">
<caption class="sr-only">지점별 응답 시간: DNS, TLS, 첫 바이트, 24시간 p99와 판정</caption>
<thead>
<tr>
<th scope="col">지점</th>
<th scope="col">도시</th>
<th scope="col" class="num">DNS</th>
<th scope="col" class="num">TLS</th>
<th scope="col" class="num">첫 바이트</th>
<th scope="col" class="num">p99</th>
<th scope="col">판정</th>
</tr>
</thead>
<tbody>
<tr><th scope="row" data-flap>ICN-01</th><td class="city">서울</td><td class="num" data-flap>1.8</td><td class="num" data-flap>6.4</td><td class="num" data-flap>14.2</td><td class="num" data-flap="alt">15.9</td><td class="verdict">정상</td></tr>
<tr><th scope="row" data-flap>NRT-02</th><td class="city">도쿄</td><td class="num" data-flap>2.1</td><td class="num" data-flap>7.1</td><td class="num" data-flap>28.6</td><td class="num" data-flap="alt">31.2</td><td class="verdict">정상</td></tr>
<tr><th scope="row" data-flap>SIN-01</th><td class="city">싱가포르</td><td class="num" data-flap>2.4</td><td class="num" data-flap>8.2</td><td class="num" data-flap>34.0</td><td class="num" data-flap="alt">37.5</td><td class="verdict">정상</td></tr>
<tr><th scope="row" data-flap>SJC-04</th><td class="city">새너제이</td><td class="num" data-flap>3.0</td><td class="num" data-flap>9.6</td><td class="num" data-flap>88.4</td><td class="num" data-flap="alt">94.7</td><td class="verdict">정상</td></tr>
<tr><th scope="row" data-flap>LHR-02</th><td class="city">런던</td><td class="num" data-flap>4.0</td><td class="num" data-flap>11.2</td><td class="num" data-flap>126.8</td><td class="num" data-flap="alt">133.0</td><td class="verdict">정상</td></tr>
<tr><th scope="row" data-flap>FRA-01</th><td class="city">프랑크푸르트</td><td class="num" data-flap>4.2</td><td class="num" data-flap>11.8</td><td class="num" data-flap>142.1</td><td class="num" data-flap="alt">151.8</td><td class="verdict verdict-warn">주의 · 150 초과</td></tr>
<tr class="row-more"><td colspan="7">나머지 22개 지점은 모두 기준 이내라서 접어 두었습니다.</td></tr>
</tbody>
</table>
</div>
</div>
</section>
<section id="wiring" class="section" aria-labelledby="wiring-h">
<div class="wrap">
<h2 id="wiring-h" class="reveal">How an edge check is mapped</h2>
<p class="lead reveal" style="--delay: 80ms">
The diagram separates an endpoint, distributed checking locations,
and the metrics returned to a monitoring system. It is a conceptual
route, not a map of operating facilities.
</p>
</div>
<div class="wrap wiring-diagram reveal" style="--delay: 120ms">
<svg viewBox="0 0 1120 220" role="img" aria-label="Illustrative workflow diagram: locations check an endpoint, an engine evaluates responses, and fields flow to a monitoring system.">
<path
class="trace"
d="M 40 60 H 300 M 40 110 H 300 M 40 160 H 300 M 300 110 H 470 M 650 110 H 820 M 820 60 V 160 M 820 60 H 1010 M 820 160 H 1010"
/>
<g class="station">
<rect x="40" y="44" width="14" height="14" />
<rect x="40" y="94" width="14" height="14" />
<rect x="40" y="144" width="14" height="14" />
<text x="66" y="55">ICN · NRT · SIN …</text>
<text x="66" y="105">FRA · LHR · AMS …</text>
<text x="66" y="155">SJC · IAD · ORD …</text>
</g>
<g class="station">
<rect x="470" y="88" width="180" height="44" />
<text x="478" y="106">assertion engine</text>
<text x="478" y="124" class="mono-note">http · grpc · tcp · dns</text>
</g>
<g class="station">
<rect x="1010" y="46" width="70" height="26" />
<rect x="1010" y="146" width="70" height="26" />
<text x="1006" y="38" text-anchor="end">Prometheus</text>
<text x="1006" y="192" text-anchor="end">OpenTelemetry</text>
</g>
<g class="flow-label">
<text x="310" y="100">every 10 s</text>
<text x="660" y="100">p50 · p99 · jitter</text>
</g>
</svg>
</div>
<div class="wrap wiring-notes">
<div class="wiring-note reveal">
<h3>Check from the edge</h3>
<p>
A useful check identifies where it ran and which part of the
response was measured, so results can be compared fairly.
</p>
<div class="section-head">
<h2 id="wiring-h">점검 방식</h2>
<p>각 지점이 요청을 보내면 판정 엔진이 응답을 평가하고, 결과를 지금 쓰는 모니터링 도구로 보냅니다.</p>
</div>
<div class="wiring-note reveal" style="--delay: 80ms">
<h3>Check the response, not only reachability</h3>
<p>
A useful workflow can compare response content, status, certificate
expiry, and timing instead of treating a reachable address as success.
</p>
<div class="wiring-diagram">
<svg viewBox="0 0 1120 220" role="img" aria-label="배선도: 세 묶음의 지점이 엔드포인트를 확인하고, 판정 엔진이 응답을 평가해 Prometheus와 OpenTelemetry로 필드를 보냅니다.">
<path class="trace" d="M 40 60 H 300 M 40 110 H 300 M 40 160 H 300 M 300 60 V 160 M 300 110 H 470 M 650 110 H 820 M 820 60 V 160 M 820 60 H 1010 M 820 160 H 1010" />
<g class="station">
<rect x="40" y="44" width="14" height="32" rx="2" />
<rect x="40" y="94" width="14" height="32" rx="2" />
<rect x="40" y="144" width="14" height="32" rx="2" />
<text x="66" y="52">ICN · NRT · SIN</text>
<text x="66" y="102">FRA · LHR · AMS</text>
<text x="66" y="152">SJC · IAD · ORD</text>
</g>
<g class="station">
<rect class="box" x="470" y="80" width="180" height="60" />
<text x="486" y="106">판정 엔진</text>
<text x="486" y="128">http · grpc · tcp · dns</text>
</g>
<g class="station">
<rect x="1010" y="44" width="70" height="32" rx="2" />
<rect x="1010" y="144" width="70" height="32" rx="2" />
<text x="1000" y="36" text-anchor="end">Prometheus</text>
<text x="1000" y="200" text-anchor="end">OpenTelemetry</text>
</g>
<g class="flow-label">
<text x="316" y="100">10초마다</text>
<text x="666" y="100">p50 · p99 · 지터</text>
</g>
</svg>
</div>
<div class="wiring-note reveal" style="--delay: 160ms">
<h3>Keep the reporting fields consistent</h3>
<p>
The example keeps endpoint, location, time, and response fields
together so they can be handed to an existing monitoring workflow.
</p>
<div class="wiring-notes">
<div class="wiring-note">
<h3>측정 지점을 함께 기록합니다</h3>
<p>지점 코드가 빠진 응답 시간은 비교할 수 없습니다. 모든 행은 공항 코드와 번호로 시작합니다.</p>
</div>
<div class="wiring-note">
<h3>응답 내용까지 확인합니다</h3>
<p>상태 코드, 본문 내용, 인증서 만료일, 응답 시간을 함께 확인합니다. 접속만 된다고 정상으로 판정하지 않습니다.</p>
</div>
<div class="wiring-note">
<h3>같은 형식으로 내보냅니다</h3>
<p>엔드포인트·지점·시각·응답 시간을 한 묶음으로 보내므로, 받는 쪽 대시보드에서 형식을 다시 맞출 필요가 없습니다.</p>
</div>
</div>
</div>
</section>
<section id="numbers" class="section band" aria-labelledby="numbers-h">
<div class="wrap">
<h2 id="numbers-h" class="reveal">One example frame, many locations</h2>
<p class="lead reveal" style="--delay: 80ms">
This table shows how an endpoint, DNS, TLS, response time, and a
percentile can be read together. Its values and locations are illustrative.
</p>
<div class="table-wrap reveal" style="--delay: 120ms">
<table>
<caption class="sr-only">
Illustrative location readings: DNS, TLS, TTFB, and p99 over one example frame
</caption>
<thead>
<tr>
<th scope="col">POP</th>
<th scope="col">City</th>
<th scope="col" class="num">DNS</th>
<th scope="col" class="num">TLS</th>
<th scope="col" class="num">TTFB</th>
<th scope="col" class="num">p99 / 24h</th>
</tr>
</thead>
<tbody>
<tr><th scope="row">ICN-01</th><td>Seoul</td><td class="num">1.8</td><td class="num">6.4</td><td class="num">14.2</td><td class="num">15.9</td></tr>
<tr><th scope="row">NRT-02</th><td>Tokyo</td><td class="num">2.1</td><td class="num">7.1</td><td class="num">28.6</td><td class="num">31.2</td></tr>
<tr><th scope="row">SIN-01</th><td>Singapore</td><td class="num">2.4</td><td class="num">8.2</td><td class="num">34.0</td><td class="num">37.5</td></tr>
<tr><th scope="row">SJC-04</th><td>San Jose</td><td class="num">3.0</td><td class="num">9.6</td><td class="num">88.4</td><td class="num">94.7</td></tr>
<tr><th scope="row">LHR-02</th><td>London</td><td class="num">4.0</td><td class="num">11.2</td><td class="num">126.8</td><td class="num">133.0</td></tr>
<tr><th scope="row">FRA-01</th><td>Frankfurt</td><td class="num">4.2</td><td class="num">11.8</td><td class="num">142.1</td><td class="num">151.8</td></tr>
<tr class="row-more"><td colspan="6">…22 more illustrative locations in the same example frame</td></tr>
</tbody>
</table>
</div>
<p class="measure-note reveal" style="--delay: 160ms">
Illustrative 24-hour frame · 10 s example interval · milliseconds shown
for DNS, TLS, TTFB, and p99.
</p>
</div>
</section>
<section id="pricing" class="section" aria-labelledby="pricing-h">
<div class="wrap pricing-grid">
<div class="pricing-text">
<h2 id="pricing-h" class="reveal">Example plan structure</h2>
<p class="lead reveal" style="--delay: 80ms">
These rows show how interval, retention, and export options can be
compared. They are not offers, prices, or an account signup flow.
</p>
<div class="wrap">
<div class="section-head">
<h2 id="pricing-h">요금은 지점 수 기준입니다</h2>
<p>요금제마다 점검 간격, 보관 기간, 내보내기 기능이 다르고 판정 규칙은 같습니다.</p>
</div>
<div class="pricing-rows reveal" style="--delay: 120ms">
<div class="price-row">
<div>
<h3>Example Free</h3>
<p>3 probes · 60 s interval · 7-day retention</p>
</div>
<div class="price-val">
<span>Example $0</span>
<a class="btn btn-primary" href="#pricing">View the example</a>
</div>
</div>
<div class="price-row">
<div>
<h3>Example Pro</h3>
<p>Unlimited probes · 10 s interval · 13-month retention · OTel export</p>
</div>
<div class="price-val">
<span>Example $0.40 <em>per probe-month</em></span>
<a class="link-quiet" href="#pricing">Example only</a>
</div>
</div>
<div class="table-wrap">
<table class="price-table">
<caption class="sr-only">요금제별 지점 수, 간격, 보관 기간과 요금(예시)</caption>
<thead>
<tr>
<th scope="col">요금제</th>
<th scope="col">지점 · 간격 · 보관</th>
<th scope="col">요금(예시)</th>
</tr>
</thead>
<tbody>
<tr id="plan-free"><th scope="row">무료</th><td>지점 3개 · 60초 간격 · 7일 보관</td><td class="price">0원</td></tr>
<tr><th scope="row">프로</th><td>지점 무제한 · 10초 간격 · 13개월 보관 · OTel 내보내기</td><td class="price">지점당 550원 <small>/ 월</small></td></tr>
</tbody>
</table>
</div>
</div>
</section>
@ -278,23 +167,60 @@
<footer class="site-foot">
<div class="wrap foot-inner">
<p>pulsegate — illustrative edge-monitoring workflow</p>
<p class="foot-meta">no live probes, accounts, prices, or operating locations</p>
<a class="foot-cta" href="#plan-free">
<span class="foot-cta-label">무료로 시작하기</span>
<span class="foot-cta-sub">지점 3개까지 0원, 60초 간격으로 7일 보관합니다.</span>
</a>
<div class="foot-meta">
<p class="foot-brand">Pulsegate</p>
<p>엣지 응답 모니터링 · designpaca 쇼케이스</p>
</div>
</div>
</footer>
<script>
const io = new IntersectionObserver(
(entries) => {
for (const e of entries) {
if (!e.isIntersecting) continue;
e.target.classList.add("is-in");
io.unobserve(e.target);
}
},
{ rootMargin: "0px 0px -8% 0px", threshold: 0.05 },
);
for (const el of document.querySelectorAll(".reveal")) io.observe(el);
// 플랩: 셀 글자를 낱장으로 나눈다. 원래 글자는 화면 낭독용으로 남긴다.
const reduced = matchMedia("(prefers-reduced-motion: reduce)").matches;
const POOL = "0123456789ABCDEFGHJKLMNPRSTUVWXYZ";
const flaps = [];
for (const cell of document.querySelectorAll("[data-flap]")) {
const text = cell.textContent.trim();
const sr = document.createElement("span");
sr.className = "sr-only";
sr.textContent = text;
const row = document.createElement("span");
row.className = cell.dataset.flap === "alt" ? "flaps flaps-alt" : "flaps";
row.setAttribute("aria-hidden", "true");
for (const ch of text) {
const flap = document.createElement("span");
flap.className = ch === " " ? "flap is-space" : "flap";
flap.textContent = ch;
row.append(flap);
if (ch !== " " && ch !== "." && ch !== "-") flaps.push({ el: flap, final: ch });
}
cell.replaceChildren(sr, row);
}
// 첫 로드에 한 번만 넘긴다. 멈춘 값이 곧 표의 값이며, 값 자체를 지어내지 않는다.
if (!reduced) {
flaps.forEach(({ el, final }, i) => {
const stopAt = 260 + (i % 7) * 70;
const started = performance.now();
el.classList.add("is-flipping");
let n = i;
const tick = (now) => {
if (now - started >= stopAt) {
el.textContent = final;
el.classList.remove("is-flipping");
return;
}
n = (n + 7) % POOL.length;
el.textContent = POOL[n];
setTimeout(() => requestAnimationFrame(tick), 60);
};
requestAnimationFrame(tick);
});
}
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View file

@ -1,8 +1,9 @@
@import url("./tokens.css?v=20260912all");
@import url("./tokens.css?v=20261002");
/* ==========================================================================
Pulsegate — dark-instrument. 명도차로만 읽히는 계기판.
레이아웃 → 재질 → 모션 순서.
Pulsegate — 신호 노랑 위의 출발 안내판.
구도: 표제가 왼쪽을 크게 차지하고, 바로 아래 안내판(표)이 화면 폭 전체를 쓴다.
안내판이 이 페이지의 이미지다 — 사진도 차트 패널도 두지 않는다.
========================================================================== */
*,
@ -18,7 +19,7 @@ html {
-webkit-text-size-adjust: 100%;
scroll-behavior: smooth;
background: var(--surface);
color-scheme: dark;
color-scheme: light;
}
@media (prefers-reduced-motion: reduce) {
html {
@ -29,51 +30,43 @@ html {
body {
word-break: keep-all;
overflow-wrap: break-word;
font-synthesis: none;
background: var(--surface);
color: var(--ink);
font-family: var(--font-body);
font-size: var(--step-0);
line-height: var(--leading-normal);
-webkit-font-smoothing: antialiased;
font-variant-numeric: tabular-nums;
}
:where(h1, h2, h3) {
font-weight: 500;
font-family: var(--font-display);
font-weight: 400;
line-height: var(--leading-tight);
letter-spacing: -0.015em;
overflow-wrap: break-word;
letter-spacing: 0;
text-wrap: balance;
}
h1 {
font-size: var(--step-3);
}
h2 {
font-size: var(--step-2);
}
h3 {
font-size: var(--step-1);
}
.lead {
color: var(--ink-muted);
max-width: 38em;
:where(p, th, td, li) {
text-wrap: pretty;
}
code {
font-family: var(--font-mono);
font-size: 0.9em;
}
a {
color: inherit;
}
:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 3px;
code {
font-family: var(--font-body);
font-weight: 800;
}
:focus-visible {
outline: 3px solid var(--ink);
outline-offset: 3px;
}
.skip {
position: absolute;
left: -9999px;
@ -84,17 +77,16 @@ a {
z-index: 100;
}
.skip:focus {
left: 0;
left: var(--space-3);
top: var(--space-3);
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0 0 0 0);
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
@ -104,496 +96,418 @@ a {
margin-inline: auto;
}
/* ── 헤더 ─────────────────────────────────────────────────────────────── */
/* ── 머리 ── */
.site-head {
position: sticky;
top: 0;
z-index: 50;
background: color-mix(in srgb, var(--surface) 86%, transparent);
backdrop-filter: blur(8px);
border-bottom: 1px solid var(--line);
border-bottom: 3px solid var(--ink);
}
.head-inner {
display: flex;
align-items: baseline;
align-items: center;
justify-content: space-between;
gap: var(--space-4);
padding-block: var(--space-3);
gap: var(--space-3);
min-height: 4.25rem;
flex-wrap: wrap;
}
.wordmark {
font-family: var(--font-mono);
font-size: var(--step-1);
font-weight: 500;
display: inline-flex;
align-items: center;
gap: 0.5rem;
font-family: var(--font-display);
font-size: 1.75rem;
line-height: 1;
text-decoration: none;
letter-spacing: -0.02em;
white-space: nowrap;
}
.wordmark-dot {
color: var(--accent);
.wordmark-flap {
display: inline-grid;
place-items: center;
width: 1.6rem;
height: 1.9rem;
border-radius: var(--radius);
background: var(--flap);
color: var(--flap-ink);
font-size: 1.25rem;
}
.nav {
display: flex;
gap: clamp(var(--space-3), 2vw, var(--space-5));
/* 내비 없음 — 섹션 넷에 행동 하나뿐이라 상단 바에는 브랜드와 그 행동만 둔다. */
.btn-head {
min-height: 44px;
padding-inline: 1rem;
font-size: 1.125rem;
}
.scope-note {
border-bottom: 1px solid var(--ink);
font-size: var(--step--1);
}
.nav a {
text-decoration: none;
color: var(--ink-muted);
padding-block: 2px;
border-bottom: 1px solid transparent;
transition: color var(--dur-quick) var(--ease-soft),
border-color var(--dur-quick) var(--ease-soft);
}
.nav a:hover {
color: var(--ink);
border-color: var(--accent);
.scope-note p {
padding-block: 0.55rem;
}
/* ── 히어로 ───────────────────────────────────────────────────────────── */
/* ── 히어로 ── */
.hero {
padding-block: clamp(4rem, 3rem + 5vw, 7rem) var(--space-6);
padding-block: var(--space-6) var(--space-5);
}
.hero-grid {
display: grid;
grid-template-columns: minmax(0, 6fr) minmax(0, 6fr);
gap: var(--space-6) var(--space-7);
align-items: start;
grid-template-columns: minmax(0, 7fr) minmax(0, 4fr);
gap: var(--space-5);
align-items: end;
}
.hero-text .lead {
margin-top: var(--space-4);
.hero h1 {
font-size: var(--step-3);
}
.probe-proof {
.hero-side {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: var(--space-3);
margin-top: var(--space-4);
padding: var(--space-3) 0;
border-block: 1px solid var(--line);
}
.probe-proof > div {
min-width: 0;
}
.probe-proof dt,
.probe-proof dd {
font-family: var(--font-mono);
font-size: 0.6875rem;
letter-spacing: 0.04em;
}
.probe-proof dt {
color: var(--ink-faint);
}
.probe-proof dd {
margin-top: 0.2rem;
color: var(--ink-muted);
overflow-wrap: anywhere;
}
.probe-proof code {
color: var(--accent);
}
.hero-actions {
margin-top: var(--space-5);
display: flex;
align-items: center;
gap: var(--space-4);
flex-wrap: wrap;
padding-bottom: 0.5rem;
}
.btn-primary {
.lead {
font-size: var(--step-0);
color: var(--ink-muted);
}
.lead code {
color: var(--ink);
}
.btn {
display: inline-flex;
align-items: center;
min-height: 44px;
min-height: 52px;
padding: 0 1.4rem;
border-radius: var(--radius);
background: var(--ink);
color: var(--accent-ink);
font-weight: 500;
color: var(--surface);
font-family: var(--font-display);
font-size: 1.25rem;
text-decoration: none;
padding: 0.75rem 1.375rem;
border-radius: var(--radius);
transition: background var(--dur-quick) var(--ease-soft);
}
/* 히어로에서 가장 밝은 덩어리 = CTA. 색이 아니라 명도로 2위. */
.btn-primary:hover {
background: var(--ink-bright);
}
.link-quiet {
color: var(--ink-muted);
text-decoration: none;
border-bottom: 1px solid var(--line-strong);
padding-bottom: 1px;
transition: color var(--dur-quick) var(--ease-soft),
border-color var(--dur-quick) var(--ease-soft);
}
.link-quiet:hover {
color: var(--accent);
border-color: var(--accent);
}
.command {
display: flex;
align-items: center;
gap: var(--space-3);
background: var(--surface-frame);
border: 1px solid var(--line);
border-radius: var(--radius);
padding: var(--space-3) var(--space-4);
font-family: var(--font-mono);
font-size: var(--step--1);
max-width: 30rem;
}
.command-prompt {
color: var(--accent);
}
.command code {
display: block; /* 인라인이면 nowrap 최소폭이 그리드를 밀어낸다(320px 실패) */
min-width: 0;
color: var(--ink);
overflow-x: auto;
white-space: nowrap;
scrollbar-width: thin;
}
.workflow-command {
margin-top: var(--space-5);
max-width: 30rem;
border-left: 2px solid var(--accent-stroke);
padding-left: var(--space-3);
}
.hero-note {
margin-top: var(--space-3);
font-size: var(--step--1);
color: var(--ink-faint);
max-width: 30rem;
.btn:hover {
background: var(--accent);
color: var(--flap-ink-alt);
}
/* ── 계기(정적 프레임) ─────────────────────────────────────────────────── */
.instrument {
min-width: 0;
border: 1px solid var(--line);
border-radius: var(--radius-frame);
background: var(--surface-raised);
padding: var(--space-4);
/* ── 안내판 ── */
.board {
padding-bottom: var(--space-8);
}
.instrument-head {
.board-head {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: var(--space-3);
font-family: var(--font-mono);
gap: var(--space-2) var(--space-4);
padding-block: var(--space-3);
border-top: 3px solid var(--ink);
font-weight: 700;
}
.board-head h2 {
font-size: var(--step-1);
}
.board-head p {
color: var(--ink-muted);
font-size: var(--step--1);
letter-spacing: 0.08em;
color: var(--ink-faint);
margin-bottom: var(--space-3);
align-self: center;
}
.instrument-head > span {
min-width: 0;
overflow-wrap: anywhere;
.table-wrap {
position: relative; /* 셀 안 sr-only(absolute)가 스크롤 상자 밖으로 새어 문서를 넓히지 않게 */
overflow-x: auto;
}
.instrument svg {
display: block;
.board-table {
width: 100%;
height: auto;
min-width: 760px;
border-collapse: separate;
border-spacing: 0 6px;
}
.chart-grid line {
stroke: color-mix(in srgb, var(--line) 68%, transparent);
stroke-width: 1;
}
.chart-grid .chart-axis {
stroke: var(--line-strong);
}
.series {
fill: none;
stroke-width: 1.4;
stroke-linejoin: round;
}
.series-faint {
stroke: var(--ink-faint);
opacity: 0.62;
}
.series-muted {
stroke: var(--ink-muted);
opacity: 0.82;
}
.series-accent {
stroke: var(--accent-stroke);
stroke-width: 2.1;
}
.instrument-rows {
margin-top: var(--space-3);
border-top: 1px solid var(--line);
}
.instrument-rows > div {
display: flex;
justify-content: space-between;
gap: var(--space-3);
padding-block: 0.5rem;
border-bottom: 1px solid var(--line);
.board-table thead th {
text-align: left;
font-size: var(--step--1);
color: var(--ink);
font-weight: 800;
padding: var(--space-2) var(--space-2) 0;
color: var(--ink-muted);
}
.instrument-rows > div > span:first-child {
min-width: 0;
overflow-wrap: anywhere;
.board-table th[scope="row"],
.board-table td {
padding: 0 var(--space-2);
vertical-align: middle;
text-align: left;
}
.num {
font-family: var(--font-mono);
font-variant-numeric: tabular-nums;
.board-table .num {
text-align: right;
}
.board-table thead .num {
text-align: right;
}
.board-table .city {
font-weight: 700;
font-size: 1.125rem;
}
.board-table .row-more td {
padding-top: var(--space-2);
color: var(--ink-muted);
font-size: var(--step--1);
}
/* 플랩 낱장 — 글자 하나가 칸 하나. 가운데 이음매가 안내판의 물성이다. */
.flaps {
display: inline-flex;
gap: 3px;
}
.flap {
position: relative;
display: inline-grid;
place-items: center;
width: 1.65rem;
height: 2.35rem;
border-radius: var(--radius);
background: var(--flap);
color: var(--flap-ink);
font-family: var(--font-display);
font-size: 1.5rem;
line-height: 1;
}
.flap::after {
content: "";
position: absolute;
left: 0;
right: 0;
top: 50%;
height: 1px;
background: var(--flap-seam);
}
.flap.is-space {
background: transparent;
}
.flap.is-space::after {
display: none;
}
.flaps-alt .flap {
color: var(--flap-ink-alt);
}
.flap.is-flipping {
animation: flap-turn 90ms steps(2) infinite;
}
@keyframes flap-turn {
50% {
transform: scaleY(0.82);
}
}
@media (prefers-reduced-motion: reduce) {
.flap.is-flipping {
animation: none;
}
}
.verdict {
font-family: var(--font-display);
font-size: 1.375rem;
}
.verdict-warn {
color: var(--accent);
}
/* ── 섹션 공통 ────────────────────────────────────────────────────────── */
/* ── 배선 ── */
.section {
padding-block: var(--space-8);
border-top: 3px solid var(--ink);
}
.band {
background: var(--surface-raised);
border-block: 1px solid var(--line);
.section-head {
display: grid;
grid-template-columns: minmax(0, 6fr) minmax(0, 5fr);
gap: var(--space-5);
align-items: end;
margin-bottom: var(--space-6);
}
.section h2 + .lead {
margin-top: var(--space-3);
}
/* ── 배선 다이어그램 ──────────────────────────────────────────────────── */
.wiring-diagram {
margin-top: var(--space-6);
border: 1px solid var(--line);
border-radius: var(--radius-frame);
background: var(--surface-raised);
padding: var(--space-4) var(--space-3);
.section-head p {
color: var(--ink-muted);
}
.wiring-diagram svg {
display: block;
width: 100%;
height: auto;
display: block;
}
.trace {
fill: none;
stroke: var(--accent-stroke);
stroke-width: 1.5;
stroke: var(--ink);
stroke-width: 3;
}
.station rect {
fill: none;
stroke: var(--line-strong);
stroke-width: 1;
fill: var(--flap);
}
.station text {
.station .box {
fill: none;
stroke: var(--ink);
stroke-width: 3;
}
.station text,
.flow-label text {
fill: var(--ink);
font-family: var(--font-body);
font-size: 15px;
fill: var(--ink-muted);
}
.station .mono-note {
font-family: var(--font-mono);
font-size: 12px;
fill: var(--ink-faint);
font-weight: 700;
}
.flow-label text {
font-family: var(--font-mono);
font-size: 12px;
fill: var(--accent);
font-weight: 400;
}
/* 스크롤 드로잉 — 이중 게이트. 폴백은 '이미 다 그어진 도면' */
@media (prefers-reduced-motion: no-preference) {
@supports (animation-timeline: view()) {
.trace {
stroke-dasharray: 2400;
stroke-dashoffset: 2400;
animation: trace-draw 1.6s var(--ease-soft) both;
animation-timeline: view();
animation-range: entry 0% entry 60%;
}
@keyframes trace-draw {
to {
stroke-dashoffset: 0;
}
}
}
}
.wiring-notes {
margin-top: var(--space-5);
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
gap: var(--space-5) var(--space-6);
grid-template-columns: repeat(3, minmax(0, 1fr));
margin-top: var(--space-6);
}
.wiring-note {
padding: var(--space-4) var(--space-4) 0 0;
border-top: 1px solid var(--ink);
}
.wiring-note + .wiring-note {
padding-left: var(--space-4);
border-left: 1px solid var(--ink);
}
.wiring-note h3 {
font-size: var(--step-0);
font-weight: 600;
margin-bottom: var(--space-3);
}
.wiring-note p {
margin-top: var(--space-2);
color: var(--ink-muted);
font-size: var(--step--1);
}
/* ── 수치 밴드 ────────────────────────────────────────────────────────── */
.table-wrap {
margin-top: var(--space-5);
overflow-x: auto;
}
table {
/* ── 요금 ── */
.price-table {
width: 100%;
border-collapse: collapse;
}
thead th {
.price-table th,
.price-table td {
text-align: left;
font-weight: 500;
font-size: var(--step--1);
color: var(--ink-faint);
padding: var(--space-2) var(--space-3);
border-bottom: 1px solid var(--line-strong);
}
tbody th {
font-weight: 500;
font-family: var(--font-mono);
font-size: var(--step--1);
}
tbody td,
tbody th {
padding: var(--space-2) var(--space-3);
border-bottom: 1px solid var(--line);
padding: var(--space-4) var(--space-3) var(--space-4) 0;
border-bottom: 1px solid var(--ink);
vertical-align: baseline;
}
.price-table thead th {
padding-block: 0 var(--space-2);
font-size: var(--step--1);
font-weight: 800;
color: var(--ink-muted);
border-bottom: 3px solid var(--ink);
}
.price-table tbody th {
font-family: var(--font-display);
font-weight: 400;
font-size: var(--step-2);
width: 22%;
}
.price-table .price {
font-family: var(--font-display);
font-size: var(--step-1);
white-space: nowrap;
}
tbody td {
.price-table .price small {
font-family: var(--font-body);
font-size: var(--step--1);
color: var(--ink-muted);
}
td.num {
color: var(--ink);
}
.row-more td {
color: var(--ink-faint);
font-size: var(--step--1);
padding-block: var(--space-3);
}
.measure-note {
margin-top: var(--space-3);
font-size: var(--step--1);
color: var(--ink-faint);
}
/* ── 요금 ─────────────────────────────────────────────────────────────── */
.pricing-grid {
display: grid;
grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
gap: var(--space-6) var(--space-7);
align-items: start;
}
.pricing-text .lead {
margin-top: var(--space-3);
}
.pricing-rows {
border-top: 1px solid var(--line-strong);
}
.price-row {
display: flex;
justify-content: space-between;
align-items: center;
gap: var(--space-4);
padding-block: var(--space-4);
border-bottom: 1px solid var(--line);
flex-wrap: wrap;
}
.price-row h3 {
font-weight: 600;
}
.price-row p {
margin-top: var(--space-1);
color: var(--ink-muted);
font-size: var(--step--1);
}
.price-val {
display: flex;
align-items: center;
gap: var(--space-4);
flex-wrap: wrap;
}
.price-val > span {
font-family: var(--font-mono);
font-size: var(--step-1);
font-variant-numeric: tabular-nums;
}
.price-val em {
font-style: normal;
font-size: var(--step--1);
color: var(--ink-faint);
}
/* ── 푸터 ─────────────────────────────────────────────────────────────── */
/* 푸터 — 다음 행동 하나가 푸터의 대부분을 차지한다. 색인·링크 묶음을 두지 않는다. */
.site-foot {
border-top: 1px solid var(--line);
padding-block: var(--space-5);
background: var(--ink);
color: var(--surface);
padding-block: var(--space-4) var(--space-5);
font-size: var(--step--1);
}
.foot-inner {
display: flex;
justify-content: space-between;
display: grid;
gap: var(--space-4);
}
.foot-cta {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
align-items: end;
gap: var(--space-3) var(--space-5);
flex-wrap: wrap;
font-size: var(--step--1);
color: var(--ink-muted);
min-height: clamp(9rem, 24vw, 15rem);
padding: var(--space-4) var(--space-5);
border-radius: var(--radius);
background: var(--flap-ink);
color: var(--flap);
text-decoration: none;
}
.foot-cta:hover {
background: var(--accent);
color: var(--flap-ink-alt);
}
.foot-cta:focus-visible {
/* 검은 푸터 위에서는 잉크색 포커스 링이 사라지므로 노랑으로 바꾼다. */
outline-color: var(--surface);
outline-offset: 4px;
}
.foot-cta-label {
font-family: var(--font-display);
font-size: var(--step-3);
line-height: var(--leading-tight);
}
.foot-cta-sub {
max-width: 22rem;
font-size: var(--step-0);
font-weight: 700;
line-height: 1.5;
padding-bottom: 0.35rem;
}
.foot-meta {
font-family: var(--font-mono);
font-size: 0.75rem;
color: var(--ink-faint);
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: var(--space-3);
}
.foot-brand {
font-family: var(--font-display);
font-size: var(--step-1);
}
/* ── 등장 모션 ────────────────────────────────────────────────────────── */
.reveal {
opacity: 0;
translate: 0 12px;
transition: opacity var(--dur-normal) var(--ease-soft),
translate var(--dur-normal) var(--ease-soft);
transition-delay: var(--delay, 0ms);
}
.reveal.is-in {
opacity: 1;
translate: 0 0;
}
@media (prefers-reduced-motion: reduce) {
.reveal {
opacity: 1;
translate: 0 0;
transition: none;
}
}
/* ── 반응형 ───────────────────────────────────────────────────────────── */
/* ── 반응형 ── */
@media (max-width: 900px) {
.hero-grid,
.pricing-grid {
.section-head {
grid-template-columns: minmax(0, 1fr);
gap: var(--space-5);
gap: var(--space-4);
}
.wiring-notes {
grid-template-columns: minmax(0, 1fr);
}
.wiring-note + .wiring-note {
padding-left: 0;
border-left: 0;
}
.wiring-note {
padding-bottom: var(--space-4);
}
}
@media (max-width: 600px) {
.hero {
padding-block: var(--space-6);
padding-top: var(--space-5);
}
}
@media (max-width: 640px) {
.probe-proof {
grid-template-columns: 1fr;
gap: var(--space-2);
}
}
@media (max-width: 560px) {
.head-inner {
flex-direction: column;
align-items: flex-start;
gap: var(--space-1);
min-height: 3.75rem;
}
.nav {
width: 100%;
justify-content: space-between;
.foot-cta {
grid-template-columns: minmax(0, 1fr);
align-items: start;
padding: var(--space-4);
}
.price-row {
align-items: flex-start;
flex-direction: column;
.foot-cta-sub {
padding-bottom: 0;
}
.flap {
width: 1.35rem;
height: 1.95rem;
font-size: 1.25rem;
}
/* 좁은 화면은 DNS·TLS 열을 접는다. 판정에 쓰는 값은 p99 하나다. */
.board-table {
min-width: 0;
}
.board-table tr > :nth-child(3),
.board-table tr > :nth-child(4) {
display: none;
}
.board-table .row-more td {
display: table-cell;
}
.price-table tbody th {
width: auto;
}
}

View file

@ -1,45 +1,43 @@
/* ==========================================================================
Pulsegate — 디자인 토큰
프리셋: dark-instrument
컨셉 한 문장: 야간 당직실의 계기판 — 발광이 아니라 명도차로 읽히는 판.
레퍼런스: R1 checklyhq.com(구조·명령어 노출·시선 수직 낙하)
R2 fitzcarraldoeditions.com(강조색은 글자로만 존재 — 배경·채움 금지)
R3 codrops scroll-driven SVG draw → CSS view()+dashoffset 로 치환
하지 않은 것: 티커 마퀴, 발광 상태점·펄스, 모노를 라벨 장식으로 쓰기,
가짜 샌드박스(Math.random), 3열 요금제+배지, 좌측 아이콘 레일.
이유: 전부 리서치 "안 할 것" 1~6에 해당.
Pulsegate — 디자인 토큰 (2026-10 재작업)
컨셉 한 문장: 공항 출발 안내판처럼 읽는 엣지 점검 — 지점 이름이 원래 공항
코드(ICN·NRT·SIN·SJC·LHR·FRA)라는 사실에서 출발했다.
재작업 이유: 2026-09판은 lumina-optics·synapse-bci와 같은 니어블랙 개발도구
템플릿이었다(레드팀 06 P0-1, similarity 0.84·0.83).
결정:
- 바탕: 공항·도로 안내의 신호 노랑. 검은 잉크 대비 ≈13:1. 한 화면에서 가장
강한 색이 바탕 자체라서, 강조는 검은 낱장(플랩)과 빨간 '주의' 하나로 한다.
- 서체: 표제·플랩은 도현(좁고 각진 간판체), 본문은 Gothic A1. 모노를 쓰지
않는다 — 등폭은 낱장 한 칸이 맡는다.
- 형태: 플랩 낱장의 모서리 4px. 그 밖의 면은 각지게 둔다.
- 모션: 첫 로드에 플랩이 한 번 넘어가 값에 멈춘다. 스크롤 등장 없음,
호버 전환은 즉시(기계식 안내판은 미끄러지지 않는다).
하지 않은 것: 발광 상태점·펄스, 티커 마퀴, 3열 요금 카드+배지, 다크 모드.
========================================================================== */
:root {
/* 타입 — 1.333. 최대(60px) ÷ 본문(16px) = 3.75배 */
--step--1: 0.8125rem; /* 13px — 코드·캡션 */
--step-0: 1rem; /* 16px — 본문 */
--step-1: 1.1875rem; /* 19px — 소제목 */
--step-2: clamp(1.375rem, 1.2rem + 0.9vw, 1.75rem); /* 28px — 섹션 표제 */
--step-3: clamp(1.875rem, 1.3rem + 2.6vw, 3.25rem); /* 52px — 두 줄 계약 */
/* 타입 — 표제 최대 128px ÷ 본문 17px ≈ 7.5배 */
--step--1: 0.875rem;
--step-0: 1.0625rem;
--step-1: 1.375rem;
--step-2: clamp(2rem, 1.4rem + 2.4vw, 3.5rem);
--step-3: clamp(3rem, 1.2rem + 7.4vw, 8rem);
--leading-tight: 1.22;
--leading-tight: 1;
--leading-normal: 1.7;
--font-body: "IBM Plex Sans", "IBM Plex Sans KR", Pretendard, -apple-system, sans-serif;
--font-mono: "JetBrains Mono", monospace;
--font-display: "Do Hyeon", "Gothic A1", sans-serif;
--font-body: "Gothic A1", "Apple SD Gothic Neo", sans-serif;
/* 색 — 야간 당직실. 위계는 명도차로만. 코발트는 '잉크'다: 글자와 가는 선. */
--surface: #0b0e12;
--surface-raised: #10141b;
--surface-frame: #131824;
--ink: #e8ebef;
--ink-bright: #ffffff; /* CTA hover — 페이지에서 가장 밝은 값 */
--ink-muted: #9aa3ae;
--ink-faint: #8a94a0;
--line: #232a35;
--line-strong: #3a4451;
--accent: #6ca3ee; /* 코발트 텍스트 — 본문 대비 7:1 */
--accent-ink: #0b0e12;
--accent-stroke: #3d7bd6; /* 차트 선 전용 — 그래픽 3:1 기준 */
--surface: #ffd23a; /* 신호 노랑 */
--surface-deep: #f2c21c; /* 표 머리·구분 띠 */
--ink: #121110;
--ink-muted: #4a3f05; /* 노랑 위 대비 ≈7:1 */
--flap: #141414; /* 플랩 낱장 */
--flap-seam: #2d2d2d;
--flap-ink: #ffd23a;
--flap-ink-alt: #f4f1e6;
--accent: #b3121f; /* '주의' 판정 — 노랑 위 대비 ≈5:1 */
--space-1: 0.25rem;
--space-2: 0.5rem;
@ -50,14 +48,10 @@
--space-7: 6rem;
--space-8: clamp(4.5rem, 3rem + 6vw, 9rem);
--radius: 2px;
--radius-frame: 6px;
--radius: 4px;
--container: 1240px;
--container-narrow: 640px;
--pad-inline: clamp(1.25rem, 1rem + 2.5vw, 5rem);
--container: 1320px;
--pad-inline: clamp(1rem, 0.6rem + 2.4vw, 4rem);
--dur-quick: 160ms;
--dur-normal: 420ms;
--ease-soft: cubic-bezier(0.25, 1, 0.35, 1);
--flap-dur: 520ms;
}