iArt.ai
AI 모션 그래픽 소프트웨어, 누구나 사용할 수 있는 After Effects 대안, 텍스트 프롬프트로 몇 분 안에 애니메이션·설명 영상 제작
실제 대기 데이터를 기반으로 매일 생성되는 예술, 17가지 아티스트 렌즈와 9가지 맞춤 FLUX.1-dev LoRA 파인튜닝 제공
Daily generative art from real atmospheric data. 17 artist lenses, 9 with custom FLUX.1-dev LoRA fine-tunes.
미디어Astro · TypeScriptGitHub @jtannahill온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Production Astro 6 site for art.jamestannahill.com: daily generative weather art. Static build, Pagefind search, deploys to S3/CloudFront on push.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Source of art.jamestannahill.com.
Astro 6, fully static, deployed on every push to main.
The design system (colors, type, components, motion, accessibility rules)
is recorded in DESIGN.md.
• Astro 6.2: static output, file-based routing, View Transitions
• TypeScript + Astro Content Collections with Zod schemas
• DynamoDB → JSON snapshots at build time (no DB calls at runtime)
• Pagefind for local-first search across every piece's rationale
• Mapbox GL for the world-map page
• @astrojs/rss for /rss.xml
• CI: GitHub Actions: pull data → type-check → build → S3 sync → CloudFront invalidateREADME 전문 보기 ↗
Source of art.jamestannahill.com.
Astro 6, fully static, deployed on every push to main.
The design system (colors, type, components, motion, accessibility rules)
is recorded in DESIGN.md.
@astrojs/rss for /rss.xmlEvery push to main runs .github/workflows/deploy.yml (it also runs
daily at 07:17 UTC, after the upstream pipeline, and on manual
workflow_dispatch):
npm run pull-data: scan DynamoDB art-generator table, write
src/data/generated/{weather,palettes}.json (gitignored)astro check: TypeScript validation across .astro filesnpm run build:no-pull: astro build produces dist/ (~3,000 pages),
then pagefind --site dist --output-subdir _pagefind builds the local
search indexscripts/backfill-thumbs.py (via uv run): generates any WebP
variants the resize Lambda has not written yet. Never fails the deploy.aws s3 sync ./dist/ to the site/ prefix of the S3 bucket, in two
passes: HTML and data with a 5-minute cache, _astro/* and images as
immutable (no --delete: preserves pipeline-written PNG/WebP/SVG assets)aws cloudfront create-invalidation --paths "/*"End-to-end: ~45 s on a clean runner.
Pieces are generated by the upstream art-weather-render Lambda
(in art-generator),
which writes weather/{run_id}/{slug}/preview-2048.png to S3.
The art-image-resize Lambda (S3 PUT-triggered, also in art-generator)
builds the web previews preview-{480,960,1200,1920}.webp from the
upscaled 4K master (preview-4k.png), falling back to preview-2048.png
only when no master exists. The 1200 variant is the og:image. The asset-mirror Lambda
(art-site-rebuild, the retired-and-shrunk legacy renderer) copies
everything from weather/ to site/weather/ once per daily pipeline
run so CloudFront's origin path serves the assets.
src/components/Thumb.astro renders <picture> with WebP <source srcset>
at three widths (480/960/1920) plus PNG fallback. Card grids load ~30 KB
WebP at 480 w instead of ~1.5 MB PNG.
Piece pages credit the model from the piece's renderer field (the
visible "Model" and "Format" rows plus the JSON-LD artMedium):
flux-dev-lora:* is "FLUX.1-dev + custom LoRA", flux-1.1-pro is
"Flux 1.1 Pro", and anything else is a Claude SVG (Claude Sonnet 4 via
Amazon Bedrock).
npm install
npm run pull-data # AWS creds from ~/.aws/credentials
npm run dev # http://localhost:4321
npm run build # pull-data + astro build + Pagefind index
npm run build:no-pull # same, reusing the existing src/data/generated/*.json
npm test # tsx --test src/lib/seo.test.ts (14 tests)
npm run build pulls data from AWS, so it needs DynamoDB read access to
the art-generator table. Use build:no-pull to rebuild offline from the
last pull. The tests cover titles and descriptions in src/lib/seo.ts,
including piece H1s and that no title contains an em-dash.
| Var | Purpose |
|---|---|
PUBLIC_MAPBOX_TOKEN | Public client token for the /map/ globe. Schema-validated via astro:env. |
CI reads from repo secrets. Local dev: copy .env.example to .env.
scripts/
pull-data.ts DynamoDB → src/data/generated/*.json
src/
content.config.ts Zod schemas for weather + palettes collections
components/
Thumb.astro <picture> with WebP srcset
Methodology.astro Bloomberg block for LoRA-trained artists
ConsentBanner.astro Analytics consent banner
data/
artists.ts 16-artist roster + 7 LoRA configs
counts.ts Single source of truth for every site-wide count
faq.ts About-page FAQ (drives JSON-LD + accordion)
lib/
seo.ts Page titles, descriptions, piece H1s
seo.test.ts node:test suite for seo.ts (npm test)
layouts/
Base.astro Shared header, footer, search modal, subscribe form
pages/
index.astro Homepage: today's run, then the paid Generate panel
about.astro /about/
archive/[...page].astro Paginated /archive/ (12 pages of 8 runs each)
artist/index.astro /artist/ index (artist-card mosaic)
artist/[key]/[...page].astro Per-artist gallery (60/page) + methodology block
weather/[run_id]/ Per-run page
weather/[run_id]/[slug]/ Single-piece page + print drawer (~2,700 of these)
palettes/ /palettes/ + per-location detail
comparison/index.astro Style comparison (10 cells × 3+ artists)
map/index.astro Mapbox dark globe with clustering
studies/index.astro Studies (empty until DynamoDB has them)
search.astro /search/ standalone Pagefind page
rss.xml.ts RSS feed
feed.xml.ts Legacy /feed.xml, same handler as rss.xml
llms.txt.ts /llms.txt
api/{today,runs,artists,palettes}.json.ts Static JSON endpoints
404.astro
DESIGN.md is the design record: tokens, type, components
and the rules below. Current behavior worth knowing before editing:
| as the separator and never an em-dash. This is
enforced by src/lib/seo.test.ts. Unnamed grid cells title by their
coordinates, e.g. "Region 30°N, 150°E", instead of a bare "Region".aria-pressed; sold-out sizes are
disabled.Base.astro) is modal with a focus trap and
returns focus on close.view-transition-name only
when clicked, so grid pages do not snapshot every thumb on navigation.prefers-reduced-motion rules and visible focus rings.art-generator: upstream
Lambdas + DynamoDB pipeline. Daily Step Function runs:
art-weather-ingest → art-weather-render × 10 →
art-satellite-ingest → art-palette-extract →
(art-image-resize triggers on each PNG) → art-site-rebuild (asset
mirror) → this repo's Astro build (manual or push-triggered).
CloudFront: one distribution with origin path /site/.
S3: the shared art-generator S3 bucket behind CloudFront. Astro publishes to site/,
pipeline writes pieces under weather/ + palettes/, mirror copies them.
IAM: a dedicated CI deploy user with scoped s3:*Object on
site/*, dynamodb:Scan on art-generator, and
cloudfront:CreateInvalidation on the distro.
Security headers: CloudFront response headers policy
art-jamestannahill-security-csp, mirrored in infra/response-headers-policy.json. The CSP is the source of
truth for which third-party hosts the site may load: Mapbox, Google Fonts
(style-src + font-src), and Google Analytics (script-src for
googletagmanager, connect-src + img-src for the collect endpoints).
Adding a third-party script without adding it here means it silently
never runs. To apply a change (POLICY_ID is that policy's ID, from
aws cloudfront list-response-headers-policies --type custom):
aws cloudfront update-response-headers-policy \
--id "$POLICY_ID" \
--if-match "$(aws cloudfront get-response-headers-policy-config \
--id "$POLICY_ID" --query ETag --output text)" \
--response-headers-policy-config file://infra/response-headers-policy.json
GA4 property art.jamestannahill.com (measurement ID G-5KG25EY8YL),
loaded from public/scripts/analytics.js and gated by
public/scripts/consent.js. gtag loads only after the visitor accepts.
Page views: gtag('config', ...) sends the first one, and
astro:after-swap sends one per ClientRouter navigation. after-swap does
not fire on the initial load, so views are not double-counted. consent.js
binds from astro:page-load only, for the same reason.
Country detection reads a jt_country cookie set by the
art-viewer-country-cookie CloudFront Function (viewer-response,
infra/viewer-country-cookie.js) from the CloudFront-Viewer-Country
header. /cdn-cgi/trace remains as a fallback but 404s here: this is
CloudFront, not Cloudflare.
CloudFront only populates CloudFront-Viewer-* headers when a policy asks
for them, so the default behavior uses cache policy
art-caching-optimized-viewer-country (mirrored in infra/cache-policy.json) rather than the managed
CachingOptimized policy. It is CachingOptimized plus that one header, which
means the header is part of the cache key and pages cache per country.
Reverting to a policy without the header silently makes the banner show to
everyone again, because the function has no country to copy.
Originally a parallel staging build at /next/ while the Jinja2
renderer in art-generator/lambdas/site_rebuild/ owned the apex.
Cut over on 2026-05-06. The legacy Lambda was shrunk to a ~50-line
asset-mirror; templates were retired.