배경 사진을 gpt-image-2 로 만들어 기준면을 강화한다

CSS 그라디언트 광원만으로는 패럴랙스의 기준면이 약했다. 굴절과 그레인이
실제로 들어 있는 이미지를 Codex CLI 내장 image_gen(gpt-image-2)으로 생성했다.
스톡이 아니라 이 페이지의 토큰 값을 프롬프트에 그대로 넣어 만든 것이다.

  바탕 #0B0E12, 광원은 좌상단 딥 틸 #4FD6C4 와 우하단 파랑 #3D7BD6 둘뿐
  보라·마젠타·무지개 금지 — 슬롭 지문이다
  "흰 본문 뒤에 깔리니 활자와 경쟁할 만큼 밝은 곳이 있으면 안 된다.
   중앙은 조용하게, 밝기는 모서리 쪽에"
  사물·글자·사람·플레어·보케·비네트 전부 금지

components/BgPhoto.astro
  CSS background-image 가 아니라 <Image loading="lazy"> 를 절대배치한다.
  background-image 로 깔면 항상 받고 최적화도 안 된다
  설치 섹션은 flip 으로 좌우 반전 — 같은 그림으로 읽히지 않게
  섹션 경계에서 끊기지 않게 위아래를 마스크로 지운다
  패럴랙스는 -46/+46px 로 가장 느리다

빌드 결과 WebP 26KB / 14KB / 7KB. 소스도 PNG 2MB 대신 WebP 37KB.
재료·설치 섹션 대비 실패 0건.
This commit is contained in:
Yun Chan 2026-08-20 14:41:36 +09:00
parent 0e03bbafac
commit 54b9628879
6 changed files with 110 additions and 3 deletions

View file

@ -222,6 +222,34 @@ WebGL 게이트: `prefers-reduced-motion` 이면 **아예 시작하지 않는다
**실측 3층** (스크롤 1000 → 1600): 격자 **+14.9px** · 산출물 **-14px** · 단계명 **0**.
한 층은 반드시 고정이다. 전부 움직이면 기준이 없어 그냥 흔들리는 페이지가 된다.
### 배경 사진 (gpt-image-2 로 생성)
CSS 그라디언트 광원만으로는 기준면이 약해서, 굴절과 그레인이 실제로 들어 있는 이미지를 넣었다.
Codex CLI 의 내장 `image_gen`(gpt-image-2)을 ChatGPT 구독 인증으로 호출해 만들었다 — 스톡 이미지가 아니다.
프롬프트는 **이 페이지의 토큰 값을 그대로** 넣었다:
- 바탕 `#0B0E12` — 순흑도 네이비도 아니게
- 광원 **둘만**: 좌상단 딥 틸 `#4FD6C4`, 우하단 채도 낮은 파랑 `#3D7BD6`.
보라·마젠타·바이올렛·무지개·따뜻한 색 **금지** (슬롭 지문)
- 프로스티드 글래스 판이 거의 옆에서 보이게 겹쳐, 가장자리만 얇게 빛나고 가운데는 거의 안 보이게
- **대비를 아주 낮고 고르게** — "이 이미지는 흰 본문 텍스트 뒤에 깔린다. 활자와 경쟁할 만큼
밝은 곳이 있으면 안 된다. 중앙 띠는 조용하고 어둡게, 밝기는 모서리 쪽에"
- 미세 필름 그레인, 밴딩 없이
- 금지: 사물·로고·글자·숫자·사람·렌즈 플레어·보케·딱딱한 도형·비네트
`components/BgPhoto.astro` 로 감쌌다. CSS `background-image` 가 아니라 `<Image loading="lazy">`
절대배치한다 — background-image 로 깔면 **항상 받고 최적화도 안 된다.**
같은 이미지를 설치 섹션에서는 `flip` 으로 좌우 반전해 같은 그림으로 읽히지 않게 했다.
| 크기 | 빌드 결과 (WebP) |
|---|---|
| 1920px | **26KB** |
| 1400px | 14KB |
| 860px | 7KB |
추상 그라디언트라 압축이 잘 붙는다. 소스도 PNG 2MB 대신 **WebP 37KB** 로 두었다.
## 그리드의 `wide` 트랙을 좁은 화면에서 닫는다
`--wide-pad: clamp(0rem, -3.7rem + 15.2vw, 10rem)`

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View file

@ -0,0 +1,74 @@
---
import { Image } from "astro:assets";
import bgRefraction from "../assets/bg/bg-refraction.webp";
interface Props {
/** 0~1. 이 레이어는 본문 뒤에 깔리므로 기본값을 낮게 둔다 */
opacity?: number;
/** 좌우 반전. 같은 이미지를 두 섹션에 쓸 때 같은 그림으로 안 읽히게 한다 */
flip?: boolean;
}
const { opacity = 0.45, flip = false } = Astro.props;
---
{/*
배경 사진 레이어.
CSS 그라디언트로 만든 광원과 달리 굴절·그레인·빛의 감쇠가 실제로 들어 있어
패럴랙스의 기준면으로 훨씬 잘 읽힌다. 대신 무게가 있으므로 규칙이 붙는다.
- `loading="lazy"` — 초기 번들에 들어가지 않는다. CSS background-image 로 깔면
이 통제권을 잃는다(항상 받고, 최적화도 안 된다). 그래서 <img> 를 절대배치한다
- `aria-hidden` + 빈 alt — 장식이다. 보조기술이 읽을 것이 없다
- opacity 기본 0.45 — 본문 대비를 지키는 선. 올리려면 그 위 글자를 다시 재라
*/}
<div class="bg-photo" class:list={{ flip }} aria-hidden="true" style={`--bg-opacity:${opacity}`}>
<Image
src={bgRefraction}
alt=""
widths={[860, 1400, 1920]}
sizes="100vw"
loading="lazy"
decoding="async"
/>
</div>
<style>
.bg-photo {
position: absolute;
/* 패럴랙스로 오르내리는 만큼 위아래에 여유를 둔다 */
inset: -14% 0;
z-index: -1;
pointer-events: none;
overflow: hidden;
/* 섹션 경계에서 뚝 끊기지 않게 위아래를 지운다 */
-webkit-mask-image: linear-gradient(to bottom, transparent 0%, black 18%, black 78%, transparent 100%);
mask-image: linear-gradient(to bottom, transparent 0%, black 18%, black 78%, transparent 100%);
}
.bg-photo :global(img) {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
opacity: var(--bg-opacity);
}
.flip :global(img) { transform: scaleX(-1); }
@media (prefers-reduced-motion: no-preference) {
@supports (animation-timeline: view()) {
/* 배경이 가장 느리다. 앞의 것보다 크게 움직이면 앞으로 튀어나와 보인다 */
.bg-photo {
--par-from: -46px;
--par-to: 46px;
animation: par linear both;
animation-timeline: view();
animation-range: cover 0% cover 100%;
}
}
}
/* 투명도를 줄여달라는 설정에서는 더 낮춘다 */
@media (prefers-reduced-transparency: reduce) {
.bg-photo :global(img) { opacity: calc(var(--bg-opacity) * 0.5); }
}
</style>

View file

@ -1,5 +1,6 @@
---
import CopyCommand from "./CopyCommand.astro";
import BgPhoto from "./BgPhoto.astro";
import type { Content } from "../i18n/content";
interface Props { c: Content["install"]; ui: Content["ui"]; }
@ -7,7 +8,8 @@ const { c, ui } = Astro.props;
---
<section id="install" class="full install bg-glow" style="--glow-x: 30%" aria-labelledby="install-h">
<section id="install" class="full install has-bg" aria-labelledby="install-h">
<BgPhoto opacity={0.4} flip />
<div class="install-grid">
<div class="install-head">
<h2 id="install-h">{c.h2}</h2>

View file

@ -1,4 +1,5 @@
---
import BgPhoto from "./BgPhoto.astro";
import type { Content } from "../i18n/content";
interface Props { c: Content["materials"]; }
@ -10,7 +11,8 @@ const { c } = Astro.props;
배경은 three.js 셰이더 플레인, 전경은 SVG 필터를 실제로 먹인 타일 3장.
둘 다 설명이 아니라 지금 이 화면에서 돌고 있는 것이다.
*/}
<section class="section materials bg-glow" style="--glow-x: 70%" aria-labelledby="mat-h">
<section class="section materials has-bg" aria-labelledby="mat-h">
<BgPhoto opacity={0.5} />
<h2 id="mat-h" class="reveal">{c.h2}</h2>
<p class="lead mat-lead reveal">{c.lead}</p>

View file

@ -422,7 +422,8 @@ a:hover { color: var(--accent); }
그래서 배경을 깐다. 다만 이미지가 아니라 페이지가 주장하는 ,
간격 눈금 자체를 격자로 그린다. --grid-cell --space-5 . */
.bg-grid,
.bg-glow {
.bg-glow,
.has-bg {
position: relative;
isolation: isolate;
}