배경 사진을 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

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;
}