designpaca/apps/site/public/work/lumina-optics/index.html
Yun Chan 8e3cdb0d3b
Some checks are pending
ci / build (push) Waiting to run
feat(site): rework 19 marketing showcases for composition diversity
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.
2026-10-06 16:55:50 +09:00

384 lines
18 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Lumina Optics · L-85 85mm f/1.8 렌즈</title>
<meta
name="description"
content="가상 광학 브랜드 Lumina Optics의 85mm f/1.8 렌즈 L-85 소개 페이지. 프리즘 시험대에서 굴절률과 분산을 바꿔 보고, 렌즈 구성·MTF·사양을 확인할 수 있습니다."
/>
<meta property="og:title" content="Lumina Optics · L-85 85mm f/1.8 렌즈" />
<meta property="og:description" content="85mm f/1.8 렌즈 L-85의 굴절 시험대, 렌즈 구성, MTF 곡선과 사양." />
<meta property="og:type" content="website" />
<meta property="og:image" content="./og.png" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="theme-color" content="#a6a9a9" />
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/sun-typeface/SUIT@2/fonts/variable/woff2/SUIT-Variable.css" />
<link rel="stylesheet" href="./styles/main.css?v=20261002" />
<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' rx='16' fill='%23A6A9A9'/%3E%3Ccircle cx='16' cy='16' r='9' fill='none' stroke='%230D0F10' stroke-width='2.4'/%3E%3Cpath d='M2 16h28' stroke='%23D4231A' stroke-width='2'/%3E%3C/svg%3E"
/>
</head>
<body>
<a class="skip" href="#main">본문으로 건너뛰기</a>
<header class="site-head">
<div class="wrap head-inner">
<a class="wordmark" href="#main"><span class="wordmark-lens" aria-hidden="true"></span>Lumina Optics</a>
<nav class="nav" aria-label="섹션">
<a href="#bench">시험대</a>
<a href="#anatomy">단면</a>
<a href="#mtf">MTF</a>
<a href="#spec">사양</a>
</nav>
</div>
</header>
<div class="scope-note">
<p class="wrap">가상 광학 브랜드의 쇼케이스입니다. 렌즈 L-85의 구성·곡선·사양은 설명을 위해 만든 값이며 실측이 아닙니다.</p>
</div>
<main id="main">
<section id="bench" class="hero" aria-labelledby="hero-h">
<div class="wrap hero-grid">
<p class="hero-kicker">L-85 85mm f/1.8 · 굴절 시험대</p>
<h1 id="hero-h">굴절률과 분산을<br />직접 바꿔 보세요</h1>
<p class="lead">
굴절률을 올리면 빛이 더 꺾이고, 분산을 키우면 색 사이가 더 벌어집니다.
프리즘을 끌어 돌리거나 아래 슬라이더 두 개로 값을 바꿔 보세요.
</p>
</div>
<div class="wrap bench">
<div class="bench-stage">
<canvas
id="gl"
aria-label="프리즘 시험대: 왼쪽에서 들어온 흰 빛이 프리즘을 지나 스펙트럼으로 갈라집니다. 끌어서 프리즘을 돌릴 수 있습니다."
></canvas>
<img
class="bench-fallback"
src="./assets/prism-bench.webp"
alt="어두운 시험대 위 유리 프리즘이 평행광 한 줄을 스펙트럼으로 나누는 참고 이미지"
width="1440"
height="810"
/>
<p class="stage-label" aria-hidden="true"><span>입사광 632.8 nm</span><span>평행광</span></p>
</div>
<div class="rail" role="group" aria-label="시험대 조작">
<div class="control">
<div class="control-head"><label for="ior">굴절률 n</label><output id="ior-out" for="ior">1.52</output></div>
<input type="range" id="ior" min="1.45" max="2.20" step="0.01" value="1.52" />
<p class="control-hint">크라운 유리 1.52 · 고굴절 플린트 1.9 이상</p>
</div>
<div class="control">
<div class="control-head"><label for="disp">분산</label><output id="disp-out" for="disp">0.18</output></div>
<input type="range" id="disp" min="0" max="0.6" step="0.01" value="0.18" />
<p class="control-hint">값이 클수록 아베수가 낮은 유리</p>
</div>
<p class="hud-note" id="hud-note">프리즘을 좌우로 끌면 입사각이 바뀝니다.</p>
</div>
</div>
</section>
<section id="anatomy" class="section" aria-labelledby="anatomy-h">
<div class="wrap anatomy-grid">
<figure class="lens-figure">
<div class="lens-disc">
<img
src="./assets/lens-section.webp"
alt="금속 경통 안에 유리 요소가 층층이 쌓인 렌즈 단면, 가장자리 조명으로 굴절 경로가 드러난 참고 이미지"
width="900"
height="1200"
loading="lazy"
/>
</div>
<figcaption>L-85 렌즈 단면 · 6군 7매</figcaption>
</figure>
<div class="anatomy-text">
<h2 id="anatomy-h">6군 7매 구성과<br />렌즈별 역할</h2>
<p>
빛은 렌즈 면을 하나 지날 때마다 조금씩 꺾이고, 마지막 면을 지나
센서에 상을 맺습니다. L-85는 저분산 유리 두 장으로 화면 가장자리의
색 번짐을 줄였습니다.
</p>
<ol class="element-list">
<li><b>1–2</b><span>앞 군 · 빛을 모으는 볼록 렌즈</span></li>
<li><b>3–4</b><span>저분산 접합 · 색수차 보정</span></li>
<li><b>5</b><span>조리개 뒤 렌즈 · 상면 평탄화</span></li>
<li><b>6–7</b><span>뒤 군 · 초점 거리 결정</span></li>
</ol>
</div>
</div>
</section>
<section id="mtf" class="section" aria-labelledby="mtf-h">
<div class="wrap">
<div class="section-head">
<p class="index">02</p>
<h2 id="mtf-h">MTF 곡선과 측정 조건</h2>
<p>MTF는 선쌍 밀도와 방향을 함께 적어야 다른 렌즈와 비교할 수 있습니다. 아래 곡선은 f/1.8 개방, 무한 초점 기준입니다.</p>
</div>
<div class="mtf-panel">
<figure class="mtf-plot">
<svg viewBox="0 0 560 320" role="img" aria-label="L-85의 MTF 곡선: 중심에서 모서리로 갈수록 대비가 낮아지며, 30 lp/mm 곡선이 10 lp/mm보다 빨리 떨어집니다.">
<g class="chart-grid">
<line x1="48" y1="88" x2="536" y2="88" />
<line x1="48" y1="160" x2="536" y2="160" />
<line x1="48" y1="232" x2="536" y2="232" />
<line x1="172" y1="16" x2="172" y2="272" />
<line x1="296" y1="16" x2="296" y2="272" />
<line x1="420" y1="16" x2="420" y2="272" />
<line class="axis" x1="48" y1="16" x2="48" y2="272" />
<line class="axis" x1="48" y1="272" x2="536" y2="272" />
</g>
<g class="axis-labels">
<text x="40" y="20" text-anchor="end">1.0</text>
<text x="40" y="92" text-anchor="end">0.75</text>
<text x="40" y="164" text-anchor="end">0.5</text>
<text x="40" y="236" text-anchor="end">0.25</text>
<text x="48" y="296">중심</text>
<text x="292" y="296" text-anchor="middle">상 높이 (mm)</text>
<text x="536" y="296" text-anchor="end">모서리 21.6</text>
</g>
<polyline class="mtf mtf-10s" points="48,32 172,36 296,44 420,60 536,76" />
<polyline class="mtf mtf-10m" points="48,34 172,40 296,52 420,72 536,96" />
<polyline class="mtf mtf-30s" points="48,84 172,96 296,120 420,160 536,196" />
<polyline class="mtf mtf-30m" points="48,90 172,108 296,140 420,186 536,232" />
</svg>
<figcaption>L-85 · f/1.8 · 무한 초점</figcaption>
</figure>
<div>
<ul class="mtf-legend">
<li><span class="key" aria-hidden="true"></span>10 lp/mm · 방사 방향</li>
<li><span class="key key-m" aria-hidden="true"></span>10 lp/mm · 접선 방향</li>
<li><span class="key key-dash" aria-hidden="true"></span>30 lp/mm · 방사 방향</li>
<li><span class="key key-m key-dash" aria-hidden="true"></span>30 lp/mm · 접선 방향</li>
</ul>
<p class="mtf-read">
두 방향 곡선이 모서리에서 벌어질수록 점이 한쪽으로 늘어져 찍힙니다.
L-85는 상 높이 15 mm까지 두 곡선 차이를 0.1 이내로 맞췄습니다.
</p>
</div>
</div>
</div>
</section>
<section id="spec" class="section" aria-labelledby="spec-h">
<div class="wrap">
<div class="section-head">
<p class="index">03</p>
<h2 id="spec-h">L-85 사양</h2>
<p>위 시험대와 단면도에 쓴 것과 같은 설계값입니다.</p>
</div>
<div class="table-wrap">
<table class="spec-table">
<caption class="sr-only">L-85 사양 — 항목, 값(예시), 기준</caption>
<thead>
<tr>
<th scope="col">항목</th>
<th scope="col">값(예시)</th>
<th scope="col">기준</th>
</tr>
</thead>
<tbody>
<tr><th scope="row">초점 거리</th><td class="num">85 mm</td><td>무한 초점</td></tr>
<tr><th scope="row">조리개</th><td class="num">f/1.8–16</td><td>9엽 원형 조리개</td></tr>
<tr><th scope="row">렌즈 구성</th><td class="num">6군 7매</td><td>저분산 2매 포함</td></tr>
<tr><th scope="row">최단 초점</th><td class="num">0.62 m</td><td>센서면 기준</td></tr>
<tr><th scope="row">필터 지름</th><td class="num">62 mm</td><td>앞 군 나사</td></tr>
<tr><th scope="row">무게</th><td class="num">438 g</td><td>후드·캡 제외</td></tr>
</tbody>
</table>
</div>
</div>
</section>
<section class="closing" aria-labelledby="closing-h">
<div class="wrap closing-grid">
<h2 id="closing-h">굴절률을 바꿔 가며<br />다시 비교해 보세요</h2>
<a class="btn" href="#bench">시험대로 돌아가기</a>
</div>
</section>
</main>
<footer class="site-foot">
<div class="wrap foot-inner">
<p>Lumina Optics · L-85 85mm f/1.8</p>
<p>designpaca 쇼케이스</p>
</div>
</footer>
<script type="module">
const canvas = document.getElementById("gl");
const fallback = document.querySelector(".bench-fallback");
const reduced = matchMedia("(prefers-reduced-motion: reduce)").matches;
let gl = null;
try {
gl = canvas.getContext("webgl2", { antialias: true, alpha: true });
} catch {}
if (!gl || reduced) {
canvas.remove();
document.getElementById("hud-note").textContent =
reduced
? "움직임 줄이기 설정을 따라 정지한 참고 이미지를 보여 줍니다."
: "이 브라우저에서는 3D 시험대를 열 수 없어 정지한 참고 이미지를 보여 줍니다.";
} else {
fallback.remove();
const THREE = await import("https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js");
const renderer = new THREE.WebGLRenderer({ canvas, context: gl, antialias: true, alpha: true });
renderer.setClearColor(0x000000, 0);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(32, 1, 0.1, 50);
camera.position.set(0, 0, 9);
const prismGeo = new THREE.CylinderGeometry(2.1, 2.1, 2.6, 3, 1, false);
prismGeo.rotateX(Math.PI / 2);
prismGeo.rotateZ(Math.PI / 2);
const uniforms = {
uIor: { value: 1.52 },
uDisp: { value: 0.18 },
};
const prismMat = new THREE.ShaderMaterial({
uniforms,
transparent: true,
vertexShader: `
varying vec3 vN;
varying vec3 vView;
void main() {
vec4 mv = modelViewMatrix * vec4(position, 1.0);
vN = normalize(normalMatrix * normal);
vView = normalize(-mv.xyz);
gl_Position = projectionMatrix * mv;
}`,
fragmentShader: `
precision highp float;
varying vec3 vN;
varying vec3 vView;
void main() {
float ndv = abs(dot(normalize(vN), normalize(vView)));
float f = pow(1.0 - ndv, 3.0);
vec3 body = vec3(0.035, 0.082, 0.094) * (0.7 + ndv * 0.65);
vec3 rim = vec3(0.22, 0.70, 0.75) * (0.24 + f * 0.92);
float alpha = clamp(0.18 + f * 0.62, 0.0, 0.82);
gl_FragColor = vec4(body + rim, alpha);
}`,
side: THREE.DoubleSide,
});
const prism = new THREE.Mesh(prismGeo, prismMat);
const prismEdges = new THREE.LineSegments(
new THREE.EdgesGeometry(prismGeo, 8),
new THREE.LineBasicMaterial({ color: 0xbfe8ee, transparent: true, opacity: 0.78 }),
);
prismEdges.renderOrder = 2;
const prismGroup = new THREE.Group();
prismGroup.add(prism, prismEdges);
scene.add(prismGroup);
const beamGroup = new THREE.Group();
const mkBeam = (w, h, color1, color2, op) => {
const m = new THREE.Mesh(
new THREE.PlaneGeometry(w, h),
new THREE.ShaderMaterial({
transparent: true,
depthWrite: false,
blending: THREE.AdditiveBlending,
uniforms: { uOp: { value: op }, uC1: { value: new THREE.Color(color1) }, uC2: { value: new THREE.Color(color2) } },
vertexShader: `varying vec2 vUv; void main(){ vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0); }`,
fragmentShader: `
varying vec2 vUv; uniform float uOp; uniform vec3 uC1; uniform vec3 uC2;
void main(){
float a = smoothstep(0.0, 0.12, vUv.x) * smoothstep(1.0, 0.88, vUv.x);
float b = smoothstep(0.0, 0.5, vUv.y) * smoothstep(1.0, 0.5, vUv.y);
vec3 c = mix(uC1, uC2, vUv.x);
gl_FragColor = vec4(c, a * b * uOp);
}`,
})
);
return m;
};
const beamIn = mkBeam(4.1, 0.12, 0xff6a5a, 0xffffff, 0.92);
beamIn.position.set(-3.05, 0, -0.34);
beamGroup.add(beamIn);
const spectrumRays = [0x7379e8, 0x62c9e8, 0x89dbaa, 0xf0da72, 0xee9866].map((color) => {
const ray = mkBeam(3.9, 0.075, color, color, 0.92);
beamGroup.add(ray);
return ray;
});
scene.add(beamGroup);
function updateSpectrum() {
const spread = 0.11 + uniforms.uDisp.value * 0.56;
const bend = (uniforms.uIor.value - 1.45) * 0.26;
spectrumRays.forEach((ray, i) => {
const offset = i / (spectrumRays.length - 1) - 0.5;
ray.position.set(2.75, offset * spread * 2.4 + bend, -0.38);
ray.rotation.z = offset * spread * 0.76;
});
}
updateSpectrum();
let dragging = false, lastX = 0, rot = -0.08;
canvas.addEventListener("pointerdown", (e) => { dragging = true; lastX = e.clientX; canvas.setPointerCapture(e.pointerId); });
canvas.addEventListener("pointermove", (e) => {
if (!dragging) return;
const dx = e.clientX - lastX;
lastX = e.clientX;
rot += dx * 0.006;
renderFrame();
});
const end = () => (dragging = false);
canvas.addEventListener("pointerup", end);
canvas.addEventListener("pointercancel", end);
canvas.style.touchAction = "none";
const iorEl = document.getElementById("ior");
const dispEl = document.getElementById("disp");
const iorOut = document.getElementById("ior-out");
const dispOut = document.getElementById("disp-out");
const bind = (el, out, uni, fmt) =>
el.addEventListener("input", () => {
uni.value = parseFloat(el.value);
out.textContent = fmt(uni.value);
updateSpectrum();
renderFrame();
});
bind(iorEl, iorOut, uniforms.uIor, (v) => v.toFixed(2));
bind(dispEl, dispOut, uniforms.uDisp, (v) => v.toFixed(2));
const stage = canvas.parentElement;
function fit() {
const r = stage.getBoundingClientRect();
const h = Math.max(320, Math.min(r.height, 760));
const aspect = r.width / h;
renderer.setPixelRatio(Math.min(devicePixelRatio || 1, aspect < 1.25 ? 1.25 : 1.5));
renderer.setSize(r.width, h, false);
camera.aspect = aspect;
camera.position.z = aspect < 1.25 ? 15.5 : 11;
// 넓은 화면은 조작 레일이 상자 아래쪽을 덮으므로 장면을 위로 올린다.
camera.position.y = aspect < 1.25 ? 0 : -0.7;
camera.updateProjectionMatrix();
renderFrame();
}
fit();
new ResizeObserver(fit).observe(stage);
function renderFrame() {
prismGroup.rotation.z = rot;
renderer.render(scene, camera);
}
renderFrame();
}
</script>
</body>
</html>