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.
254 lines
10 KiB
HTML
254 lines
10 KiB
HTML
<!doctype html>
|
||
<html lang="ko">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<title>오버톤 사운드랩 — 공간 음향 설계</title>
|
||
<meta
|
||
name="description"
|
||
content="전시장과 소극장의 반사와 잔향 시간을 먼저 재고 스피커 자리를 정하는 가상 공간 음향 연구소. 2023년부터 2026년까지 작업 네 건의 기록."
|
||
/>
|
||
<meta property="og:title" content="오버톤 사운드랩 — 공간 음향 설계" />
|
||
<meta
|
||
property="og:description"
|
||
content="잔향 시간을 먼저 재고 스피커 자리를 정합니다. 작업 네 건의 채널 수와 측정 조건을 적었습니다."
|
||
/>
|
||
<meta property="og:type" content="website" />
|
||
<meta property="og:image" content="./og.png" />
|
||
<meta property="og:image:alt" content="어두운 바탕 위에 로즈색 파형 조각, 듀오톤 설치 사진, 수치 조각이 겹친 오버톤 사운드랩 첫 화면" />
|
||
<meta name="twitter:card" content="summary_large_image" />
|
||
<meta name="theme-color" content="#12100e" />
|
||
|
||
<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=Black+Han+Sans&family=Gothic+A1:wght@400;700&display=swap"
|
||
/>
|
||
<link rel="stylesheet" href="./styles/tokens.css" />
|
||
<link rel="stylesheet" href="./styles/main.css" />
|
||
<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='%2312100E'/%3E%3Cpath d='M4 16h4l2-7 3 14 3-10 2 5h10' fill='none' stroke='%23EF587E' stroke-width='1.8'/%3E%3C/svg%3E"
|
||
/>
|
||
</head>
|
||
|
||
<body>
|
||
<a class="skip" href="#main">본문으로 건너뛰기</a>
|
||
|
||
<main id="main">
|
||
<section class="hero" aria-labelledby="hero-h">
|
||
<div class="wrap hero-head">
|
||
<p class="brand">오버톤 사운드랩</p>
|
||
<a class="hero-link" href="#works">작업 네 건 보기</a>
|
||
</div>
|
||
|
||
<div class="wrap collage">
|
||
<h1 id="hero-h" class="hero-h">
|
||
잔향 시간을 먼저 재고<br />
|
||
스피커 자리를 정합니다
|
||
</h1>
|
||
|
||
<div class="piece piece-wave" aria-hidden="true">
|
||
<canvas id="wave" class="wave"></canvas>
|
||
<p class="piece-tag">잔향 파형 그래픽</p>
|
||
</div>
|
||
|
||
<figure class="piece piece-photo">
|
||
<div class="duotone">
|
||
<img
|
||
src="./assets/installation.webp"
|
||
alt="종이 콘 스피커가 줄지어 놓인 어두운 전시실, 그 위를 가로지르는 조명 한 줄"
|
||
width="1440"
|
||
height="810"
|
||
fetchpriority="high"
|
||
decoding="async"
|
||
/>
|
||
</div>
|
||
<figcaption class="piece-tag">공명하는 방, 전시장 설치 연출</figcaption>
|
||
</figure>
|
||
|
||
<dl class="piece piece-figures">
|
||
<div class="figure">
|
||
<dt>작업</dt>
|
||
<dd><span class="num">4</span>건, 2023–2026</dd>
|
||
</div>
|
||
<div class="figure">
|
||
<dt>최대 채널</dt>
|
||
<dd><span class="num">48</span>ch</dd>
|
||
</div>
|
||
<div class="figure">
|
||
<dt>바닥 소음</dt>
|
||
<dd><span class="num">−62</span>dB</dd>
|
||
</div>
|
||
<div class="figure">
|
||
<dt>조율 범위</dt>
|
||
<dd><span class="num">220–940</span>Hz</dd>
|
||
</div>
|
||
</dl>
|
||
</div>
|
||
</section>
|
||
|
||
<section id="works" class="section works" aria-labelledby="works-h">
|
||
<div class="wrap">
|
||
<h2 id="works-h" class="section-h">2023년부터 2026년까지 작업 네 건을 했습니다</h2>
|
||
<p class="section-lead">작업마다 채널 수와 측정 조건을 적었습니다. 최근 작업이 먼저 옵니다.</p>
|
||
<ol class="archive">
|
||
<li class="work">
|
||
<p class="work-year">2026</p>
|
||
<div class="work-body">
|
||
<h3>공명하는 방 — 전시장 설치</h3>
|
||
<p>
|
||
전시실에 종이 콘 스피커 48개를 설치했습니다. 관람객이 걷는 속도에 맞춰 채널마다 지연
|
||
시간을 다르게 설정했습니다.
|
||
</p>
|
||
</div>
|
||
<ul class="work-meta">
|
||
<li>48채널</li>
|
||
<li>공간 지연</li>
|
||
<li>상설 설치</li>
|
||
</ul>
|
||
</li>
|
||
<li class="work">
|
||
<p class="work-year">2025</p>
|
||
<div class="work-body">
|
||
<h3>빈 객석 — 공연</h3>
|
||
<p>
|
||
빈 객석의 바닥 소음을 소리 재료로 쓴 공연입니다. 공조기를 끈 뒤 남은 −62dB 소음을
|
||
증폭해 첫 악장에 썼습니다.
|
||
</p>
|
||
</div>
|
||
<ul class="work-meta">
|
||
<li>공연</li>
|
||
<li>바닥 소음 −62dB</li>
|
||
</ul>
|
||
</li>
|
||
<li class="work">
|
||
<p class="work-year">2024</p>
|
||
<div class="work-body">
|
||
<h3>역위상 파도 — 해안 설치</h3>
|
||
<p>
|
||
6시간 녹음한 파도 소리를 역위상으로 재생해, 해안 데크 위에서만 파도 소리가 줄어들게
|
||
했습니다.
|
||
</p>
|
||
</div>
|
||
<ul class="work-meta">
|
||
<li>현장 녹음</li>
|
||
<li>6시간 반복</li>
|
||
</ul>
|
||
</li>
|
||
<li class="work">
|
||
<p class="work-year">2023</p>
|
||
<div class="work-body">
|
||
<h3>유리잔 12개 — 연주 설비</h3>
|
||
<p>물 높이를 달리한 유리잔 12개를 220Hz부터 940Hz까지 조율한 연주 설비입니다.</p>
|
||
</div>
|
||
<ul class="work-meta">
|
||
<li>유리잔 12개</li>
|
||
<li>220–940Hz</li>
|
||
</ul>
|
||
</li>
|
||
</ol>
|
||
</div>
|
||
</section>
|
||
|
||
<section id="method" class="section method" aria-labelledby="method-h">
|
||
<div class="wrap method-grid">
|
||
<h2 id="method-h" class="method-h">음량보다 잔향 시간을 먼저 잽니다.</h2>
|
||
<p class="method-note">
|
||
잔향이 길면 같은 음량에서도 말소리가 뭉개집니다. 그래서 모든 설계는 공간의 잔향 시간(RT60)
|
||
측정에서 시작하고, 그다음에 스피커 배치와 흡음재를 정합니다. 첫 화면의 파형은 분위기를
|
||
보여 주는 그래픽이며 측정값이 아닙니다.
|
||
</p>
|
||
</div>
|
||
</section>
|
||
|
||
<div class="marquee" role="group" aria-label="작업한 공간 유형" tabindex="0">
|
||
<ul class="marquee-track">
|
||
<li>공공 전시장</li>
|
||
<li>도심 복합문화공간</li>
|
||
<li>콘서트홀</li>
|
||
<li>소극장</li>
|
||
<li>해안 야외 공연장</li>
|
||
<li aria-hidden="true">공공 전시장</li>
|
||
<li aria-hidden="true">도심 복합문화공간</li>
|
||
<li aria-hidden="true">콘서트홀</li>
|
||
<li aria-hidden="true">소극장</li>
|
||
<li aria-hidden="true">해안 야외 공연장</li>
|
||
</ul>
|
||
</div>
|
||
</main>
|
||
|
||
<footer class="site-foot">
|
||
<div class="wrap foot-row">
|
||
<p class="foot-note">가상 브랜드 쇼케이스입니다. 작업, 장소, 수치는 모두 예시이고 의뢰를 받지 않습니다.</p>
|
||
<a class="foot-top" href="#main">맨 위로</a>
|
||
</div>
|
||
<p class="foot-mark" aria-hidden="true">오버톤 사운드랩</p>
|
||
</footer>
|
||
|
||
<script>
|
||
// 파형 캔버스 — 화면에 보일 때만 그리고, 화면 밖과 감소 모션에서는 정지 프레임 하나만 남긴다.
|
||
const canvas = document.getElementById("wave");
|
||
const reduced = matchMedia("(prefers-reduced-motion: reduce)").matches;
|
||
const ctx = canvas.getContext("2d");
|
||
const accent = getComputedStyle(document.documentElement).getPropertyValue("--accent-rgb").trim() || "239 88 126";
|
||
const rgba = (a) => `rgb(${accent} / ${a})`;
|
||
let w, h, dpr, running = false;
|
||
|
||
function fit() {
|
||
dpr = Math.min(devicePixelRatio || 1, 1.5);
|
||
w = canvas.clientWidth;
|
||
h = canvas.clientHeight;
|
||
canvas.width = Math.round(w * dpr);
|
||
canvas.height = Math.round(h * dpr);
|
||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||
}
|
||
|
||
function draw(t) {
|
||
ctx.clearRect(0, 0, w, h);
|
||
for (let layer = 0; layer < 3; layer++) {
|
||
ctx.beginPath();
|
||
const amp = h * 0.2 * (1 - layer * 0.28);
|
||
const yMid = h * (0.48 + layer * 0.1);
|
||
const speed = 0.00042 * (1 + layer * 0.7);
|
||
for (let x = 0; x <= w; x += 3) {
|
||
const p = x / w;
|
||
const env = Math.exp(-Math.pow((p - 0.5) * 2.4, 2));
|
||
const y =
|
||
yMid +
|
||
Math.sin(p * 21 + t * speed + layer * 2.1) * amp * env * Math.sin(p * Math.PI) +
|
||
Math.sin(p * 53 - t * speed * 1.7) * amp * 0.25 * env;
|
||
x === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
|
||
}
|
||
ctx.strokeStyle = layer === 0 ? rgba(0.9) : rgba(0.34 - layer * 0.1);
|
||
ctx.lineWidth = layer === 0 ? 1.6 : 1;
|
||
ctx.stroke();
|
||
}
|
||
}
|
||
|
||
function loop(t) {
|
||
if (!running) return;
|
||
draw(t);
|
||
requestAnimationFrame(loop);
|
||
}
|
||
|
||
fit();
|
||
addEventListener("resize", () => { fit(); if (!running) draw(0); }, { passive: true });
|
||
draw(0);
|
||
|
||
if (!reduced) {
|
||
const io = new IntersectionObserver((entries) => {
|
||
const on = entries[0].isIntersecting;
|
||
if (on && !running) {
|
||
running = true;
|
||
requestAnimationFrame(loop);
|
||
} else if (!on) {
|
||
running = false;
|
||
}
|
||
});
|
||
io.observe(canvas);
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|