@font-face { font-family: first; src: url(assets/fonts/first-Regular.ttf); }

/* pc: --u is a constant 1px, so every calc(N * var(--u)) is just Npx — a fixed 1920x1080 canvas
   that never grows or shrinks. A window smaller than that crops it (no scaling, no white gaps at
   the edges of the canvas itself); a bigger window shows the extra space around it. */
:root { --u: 1px; }
/* the one exception: the central paint/cyber illustration still scales with the window */
:root { --illus-u: min(100vw / 1920, 100vh / 1080); }
/* pc: artwork size shared by the world list and the world-detail page, so the same work never
   renders at two different sizes depending which page you view it from. Fixed regardless of
   window size — the detail page's text no longer needs the image to shrink to avoid overlap,
   since .info's difference blend mode handles that instead (see .info below). */
:root { --work-img-w: 500px; }
@media (max-width: 768px) {
  /* mobile keeps scaling to fit the viewport — only pc goes fixed-size */
  :root { --u: min(100vw / 360, 100vh / 800); }
}
* { box-sizing: border-box; }
body { margin: 0; background: #fff; color: #000; overflow-x: hidden;
  font: calc(20.2 * var(--u)) / calc(22.5 * var(--u)) first, "Apple SD Gothic Neo", "Malgun Gothic", sans-serif; }
@media (max-width: 768px) {
  /* home only: .stage is a fixed 360x800-unit box that needs body letterboxed to the same width
     to stay centered on a viewport wider than that (e.g. a desktop window resized narrow) instead
     of sitting flush left. Other mobile pages lay out at the real viewport width — letterboxing
     them too double-counts with their own 60u padding whenever height (not width) drives --u,
     e.g. a short/wide window, ballooning the side margins well past 60. */
  body.home { max-width: calc(360 * var(--u)); margin: 0 auto; }
}
a { color: inherit; text-decoration: none; }
img { display: block; }
h1, p { margin: 0; font-weight: 400; }

/* shared chrome: logo, back arrow, arc */
/* fixed (not absolute) so they stay put on screen through scroll, on every page — world.html's
   work list is the one that actually gets tall enough to scroll under them */
.logo, .back { position: fixed; z-index: 2; }
.logo { left: calc(30 * var(--u)); top: calc(30 * var(--u)); width: 136.3px; }
/* right edge flush with the logo's own right edge (30 + 136.3 - 40), height unchanged */
.back { left: calc(126.3 * var(--u)); top: calc(167.2 * var(--u)); width: 40px; }
.logo img, .back img { width: 100%; }
/* pc: the curve at its fixed 1920x1080 size, centered — a smaller window just crops its
   left/right (and top/bottom) edges rather than shrinking or restretching the curve itself */
.arc { position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background: url(assets/arc-pc.svg) 50% 50% / calc(1920 * var(--u)) calc(1080 * var(--u)) no-repeat; }
@media (max-width: 768px) {
  .logo { left: calc(50% - 40 * var(--u)); top: calc(26 * var(--u)); width: calc(80 * var(--u)); }
  .back { left: calc(60 * var(--u)); top: calc(80 * var(--u)); width: calc(28 * var(--u)); }
  /* mobile still scales to fit the viewport, so size it by percentage, not the pc-fixed px above */
  .arc { background-image: url(assets/arc-mobile.svg); background-size: 100% auto; }
}
.page { position: relative; min-height: 100vh; overflow: hidden; }

/* main */
body.home { min-height: 100vh; }
@media (max-width: 768px) {
  body.home { display: flex; justify-content: center; align-items: flex-start; }
}
/* pc: the old single flat image is now separate pieces (logo/book/illustration) so each
   can anchor to its own corner of the viewport instead of all moving together as one block.
   mobile still gets a flat logo image + hit overlay, in .stage. Each container holds one
   platform's version and is hidden outright on the other, rather than toggling piece by piece. */
.pc-main .book, .pc-main .illustration-wrap { position: fixed; }
.pc-main .book img { width: 100%; display: block; }
/* height given explicitly (not auto) — a bottom-anchored replaced element with no intrinsic size
   doesn't reliably get its height from its aspect ratio in this renderer, and stretches to fill */
.pc-main .book { left: calc(30 * var(--u)); bottom: calc(20 * var(--u)); width: 70px; height: 54.6px; }
/* fixed size like everything else on pc (not --illus-u) — a smaller window just crops it instead
   of shrinking it. right-anchored, vertically centered on the viewport's own center line */
.pc-main .illustration-wrap { right: calc(30 * var(--u)); top: 50%; transform: translateY(-50%);
  width: 275.4px; height: 174.1px; }
.pc-main .illustration-wrap img { width: 100%; height: 100%; display: block; pointer-events: none; }
.illustration-wrap .hit { left: calc(var(--x) * var(--u)); top: calc(var(--y) * var(--u));
  width: calc(var(--w) * var(--u)); height: calc(var(--h) * var(--u)); }
/* the globes themselves: an animated gif (real rendered rotation frames, not a CSS rotateY spin
   on a flat image — that squashes to a sliver at the 90°/270° point, which read as "too flat").
   cyber is mirrored to spin the opposite way from paint — the grid has no L/R-distinguishing
   marks, so a horizontal flip of the same file reads as a genuine reverse rotation, no 2nd asset. */
.pc-main .illustration-wrap .globe-paint, .pc-main .illustration-wrap .globe-cyber {
  position: absolute; left: calc(205.1 * var(--u)); width: calc(70.3 * var(--u)); height: calc(70 * var(--u)); }
.pc-main .illustration-wrap .globe-paint { top: 0; }
.pc-main .illustration-wrap .globe-cyber { top: calc(100 * var(--u)); transform: scaleX(-1); }
/* hand/arrow accent icons: pulled out of the illustration itself (an <img>'s content can't be
   hovered/targeted from outside it) so hovering the globe next to them can pull them toward it. */
/* idle: reuses the same weightless drift as the profile card (see @keyframes float below) — it
   animates translate/rotate, which compose independently of the transform below, so the hover
   pull and the drift never fight over the same property. click-through: the hit box below is
   sized to the globe alone, not this icon's own area, so clicking the icon does nothing. */
.hand-accent, .arrow-accent { position: absolute; pointer-events: none; transition: transform .6s ease-out;
  animation: float 9s ease-in-out infinite; }
/* different duration (not just a delay) so the two never settle into a fixed, mirrored offset */
.arrow-accent { animation-duration: 7s; animation-delay: -2s; }
.pc-main .illustration-wrap .hand-accent { left: calc(17.9 * var(--u)); top: calc(17.2 * var(--u)); width: calc(27.6 * var(--u)); height: auto; }
.pc-main .illustration-wrap .arrow-accent { left: 0; top: calc(156.4 * var(--u)); width: calc(31.3 * var(--u)); height: auto; }
.pc-main .hit[aria-label="PAINT"]:hover ~ .hand-accent { transform: translateX(40px); }
.pc-main .hit[aria-label="CYBER"]:hover ~ .arrow-accent { transform: translateX(40px); }

.stage { display: none; position: relative; width: calc(360 * var(--u)); height: calc(800 * var(--u)); }
.hit { position: absolute; display: block; left: calc(var(--x) * var(--u)); top: calc(var(--y) * var(--u));
  width: calc(var(--w) * var(--u)); height: calc(var(--h) * var(--u)); }
/* globes/book pulled out of the flat mobile image too, so book can stay pinned to its own
   corner (bottom-left) instead of drifting with the rest of the artwork */
.stage .globes { position: absolute; left: calc(71.3 * var(--u)); top: calc(290 * var(--u));
  width: calc(207.4 * var(--u)); height: calc(220 * var(--u)); }
.stage .globes img { width: 100%; height: 100%; display: block; pointer-events: none; }
.stage .globes .globe-paint, .stage .globes .globe-cyber {
  position: absolute; left: calc(56.9 * var(--u)); width: calc(89.3 * var(--u)); height: calc(89 * var(--u)); }
.stage .globes .globe-paint { top: 0; }
.stage .globes .globe-cyber { top: calc(131 * var(--u)); transform: scaleX(-1); }
.stage .globes .hand-accent { left: 0; top: calc(50.5 * var(--u)); width: calc(23.9 * var(--u)); height: auto; }
.stage .globes .arrow-accent { left: calc(180.2 * var(--u)); top: calc(173.7 * var(--u)); width: calc(27.1 * var(--u)); height: auto; }
.stage .hit[aria-label="PAINT"]:hover ~ .hand-accent { transform: translateX(calc(18 * var(--u))); }
.stage .hit[aria-label="CYBER"]:hover ~ .arrow-accent { transform: translateX(calc(-18 * var(--u))); }
.stage .book { position: fixed; top: auto; right: auto; left: calc(20 * var(--u)); bottom: calc(20 * var(--u)); width: calc(76 * var(--u)); height: calc(62.7 * var(--u)); }
@media (max-width: 768px) {
  .pc-main { display: none; }
  .stage { display: block; }
}

/* world: works stacked newest first, gap stays fixed while sizes vary. Height-capped so a landscape
   image keeps its own aspect instead of being stretched to a portrait image's width. */
.list { min-height: 100vh; display: flex; flex-direction: column; align-items: center;
  gap: calc(100 * var(--u)); padding: 0 0 calc(200 * var(--u)); }
/* the top padding used to just reserve clearance under the fixed logo/back button — giving the
   first work its own full-viewport slot, centered, clears that on any realistic window height too.
   pc only — mobile's own compact stacked layout below never had a top-clearance issue to begin with */
@media (min-width: 769px) {
  .list a:first-child { min-height: 100vh; display: flex; align-items: center; }
}
/* capped at 600px per the design file; no neighboring fixed panel here, so just keep it off the
   viewport edges on a narrow window */
.list img { max-width: var(--work-img-w); height: auto; }
@media (max-width: 768px) {
  /* explicit 60u side padding, image fills the rest — a fixed 240u width centered by the flexbox
     only came out to a 60u gap when the container happened to be exactly 360u wide (a phone-shaped
     viewport); on anything wider (a short/wide window) the gap is whatever centering leaves over */
  .list { gap: calc(50 * var(--u)); padding: calc(150 * var(--u)) calc(60 * var(--u)) 0; align-items: stretch; }
  .list img { width: 100%; max-width: none; height: auto; }
}

/* detail (pc): scrolling steps through the images while the page stays put */
.scroller { height: calc(100vh + (var(--n, 1) - 1) * 50vh); }
.scroller > .page { position: sticky; top: 0; height: 100vh; }
.main { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); }
/* shrinks before the window gets narrow enough for the fixed-width info panel to overlap it */
.main img { max-width: var(--work-img-w); height: auto; }
/* sold dot: flush with the image's own bottom edge, 20u past its right edge, so it tracks whatever size the image renders at */
.sold { position: absolute; visibility: hidden; width: calc(20 * var(--u)); height: calc(20 * var(--u));
  border: 1px solid #000; border-radius: 50%; }
.sold.paint { visibility: visible; }
.sold.on { background: #f00; border-color: #f00; }
.main .sold { left: 100%; bottom: 0; margin-left: calc(20 * var(--u)); }
/* pinned to their own viewport corner (not centre-relative) so they hold position as the window
   shrinks, and fixed-px (not --u) sized so they hold their reading size too, not just their spot */
.thumbs { position: fixed; left: calc(30 * var(--u)); bottom: calc(30 * var(--u)); width: 650px;
  display: flex; flex-wrap: wrap; gap: 17.5px; }
.thumbs img { width: 52.5px; height: 40px; object-fit: cover; opacity: .35; cursor: pointer; }
.thumbs img.on { opacity: 1; }
.slider { display: none; }
/* white text + difference blend: reads normal black on the white page, and auto-inverts pixel by
   pixel wherever it happens to sit over the artwork image instead — no overlap math needed. The
   blend-mode has to be set on .info itself (the outermost fixed-position element) to correctly see
   the true backdrop behind it — putting it on a nested descendant instead (even .info's own text
   children individually) silently stops it from blending against anything outside .info at all,
   which is also why .souvenir must NOT be a child of .info (see .souvenir below). */
/* bottom:30 flush with the page's own bottom padding — the same spot .souvenir itself sits at
   when there's no button to stack above (sold paint / cyber with no file): otherwise the panel
   would keep floating at the button-stacked height and read as detached from the bottom edge.
   when the button IS showing (next sibling .souvenir not [hidden]), stack above it instead:
   30 (base) + 20 (button height) + 20 (gap) = 70, so the button's own spot never moves. */
.info { position: fixed; right: calc(30 * var(--u)); bottom: calc(30 * var(--u)); width: 240px; font-size: 20.2px; line-height: 22.5px;
  color: #fff; mix-blend-mode: difference; }
.info:has(+ .souvenir:not([hidden])) { bottom: calc(70 * var(--u)); }
.info h1 { font-size: 27.3px; line-height: 30px; }
.info #desc { margin-top: 9.3px; white-space: pre-line; }
.info #meta { margin-top: 25px; font-size: 27.3px; line-height: 30px; }
/* pc: year and materials on their own lines; mobile (below) puts them on one line instead */
.info #meta .sep { display: none; }
.info #meta-mat { display: block; }
.info #meta .hide { display: none !important; }
/* "GET SOUVENIR?" — cyber's download / unsold-paint's buy button. Each state is a whole separate
   asset (background baked in, not just recolored text), so hover is a plain image swap — no
   filter/background-color trickery needed. Right-aligned, independent of .info (not inside it —
   see the comment above .info) but at the page's own bottom:30/right:30, same as .info's own
   default flush position, since .info moves up to make room for it when it's shown (see above). */
.souvenir { display: inline-block; position: fixed; right: calc(30 * var(--u)); bottom: calc(30 * var(--u)); }
.souvenir img { display: block; height: 20px; width: auto; }
.souvenir .on-hover { display: none; }
.souvenir:hover .on-normal { display: none; }
.souvenir:hover .on-hover { display: block; }
.souvenir[hidden] { display: none; }
@media (max-width: 768px) {
  .scroller { height: auto; }
  /* min-height: 0 cancels the shared .page rule's 100vh floor — .info now sits right after .page
     as a sibling (moved there for the pc blend-mode fix below), so that floor was pushing it a
     full screen-height down past the image slider instead of right after it */
  .scroller > .page { position: relative; height: auto; min-height: 0; }
  .main, .thumbs { display: none; }
  .slider { display: flex; align-items: flex-start; gap: calc(30 * var(--u)); margin-top: calc(150 * var(--u)); padding: 0 calc(60 * var(--u));
    overflow-x: auto; scroll-snap-type: x mandatory; scroll-padding: 0 calc(60 * var(--u)); scrollbar-width: none; }
  .slider::-webkit-scrollbar { display: none; }
  /* padding-bottom reserves the sold dot's space below the image — an absolutely-positioned child
     doesn't grow its container on its own, and a horizontal scroller clips anything that does
     overflow it. Reserving the space keeps this correct at any image height, not just one number. */
  .slide { position: relative; flex: none; scroll-snap-align: start; padding-bottom: calc(40 * var(--u)); }
  .slider img { width: calc(240 * var(--u)); height: auto; }
  /* a single-image work has no second thumbnail to peek at, so the 240u size (meant to leave a
     hint of the next one) just reads as a small image stranded in empty space — fill the row instead */
  .slide:only-child { flex: 1; }
  .slide:only-child img { width: 100%; }
  .slide .sold { right: 0; bottom: 0; }
  /* bottom padding moved from .info onto .souvenir below (padding: ...0) — a negative margin
     pulling .souvenir up through .info's own padding to cancel it left .souvenir's box literally
     overlapping .info's (still-hit-testable) padding area, and .info — blended, so it forms its
     own stacking context — won every hover/click there instead of the button underneath it */
  .info { position: static; width: auto; padding: calc(70 * var(--u)) calc(60 * var(--u)) 0; }
  .info #meta { margin-top: calc(16.5 * var(--u)); }
  /* mobile: .info never overlaps an image (the slider sits above it, not behind), so there's no
     blend-mode reason to keep .souvenir out of the flow here — back to a normal static sibling,
     inset to match .info's own left padding since it no longer inherits that padding as a child.
     width:fit-content + margin-left:auto right-aligns it, matching .info's own right inset (60u);
     the bottom margin is .info's old trailing space, now carried by whichever element is last. */
  .souvenir { display: block; width: fit-content; position: static; left: auto; bottom: auto;
    margin: calc(20 * var(--u)) calc(60 * var(--u)) calc(60 * var(--u)) auto; }
  .souvenir img { height: calc(20 * var(--u)); width: auto; }
}

/* profile: the card drifts weightlessly around the centre */
.card { position: absolute; left: 50%; top: 50%; width: calc(619 * var(--illus-u)); transform: translate(-50%, -50%); }
.card img { width: 100%; animation: float 9s ease-in-out infinite; }
@keyframes float {
  0%, 100% { translate: 0 0; rotate: -.7deg; }
  25% { translate: calc(6 * var(--u)) calc(-9 * var(--u)); rotate: .2deg; }
  50% { translate: calc(-4 * var(--u)) calc(-14 * var(--u)); rotate: .8deg; }
  75% { translate: calc(-7 * var(--u)) calc(-4 * var(--u)); rotate: -.3deg; }
}
@media (prefers-reduced-motion: reduce) { .card img, .hand-accent, .arrow-accent { animation: none; } }
/* --illus-u (not --u) so this scales together with the card above it, which also uses --illus-u */
.welcome { position: absolute; left: 50%; top: calc(50% + 236 * var(--illus-u)); transform: translateX(-50%); text-align: center; white-space: nowrap;
  font-size: calc(46 * var(--illus-u)); line-height: calc(54 * var(--illus-u)); }
@media (max-width: 768px) {
  .card { width: calc(276 * var(--u)); }
  .welcome { top: calc(50% - 150.6 * var(--u)); font-size: calc(20.5 * var(--u)); line-height: calc(24 * var(--u)); }
}
