/* ==========================================================================
   Gummoon Industry — Design System
   Light warm-gray / vivid orange accent
   ========================================================================== */

:root{
  --bg:#e7e5e1;
  --bg-2:#ffffff;
  --surface:#ffffff;
  --surface-2:#f1efeb;
  --line:#dedbd5;
  --line-soft:#e6e3de;
  --text:#1c1a17;
  --text-mid:#57534c;
  --text-dim:#8b8780;
  --red:#ff5a1e;
  --red-soft:#e0491a;
  --amber:#ff8b3d;
  --amber-soft:#c9450f;
  --accent-grad:linear-gradient(90deg,#ff5a1e,#ff8b3d);
  --icon-bg:#f7f6f3;

  --font-en: "Archivo","Inter",-apple-system,BlinkMacSystemFont,sans-serif;
  --font-ko: "Pretendard Variable","Pretendard","Archivo",-apple-system,BlinkMacSystemFont,"Malgun Gothic",sans-serif;
  --font-base: var(--font-en);

  --container:1180px;
  --header-h:82px;
  --radius-sm:8px;
  --radius-md:12px;
  --radius-lg:20px;
  --shadow-sm:0 2px 10px rgba(30,24,14,.08);
  --shadow-md:0 16px 40px rgba(30,24,14,.14);
  --ease:cubic-bezier(.4,0,.2,1);
}

*,*::before,*::after{ box-sizing:border-box; }
html{ scroll-behavior:smooth; scroll-padding-top:calc(var(--header-h) + 16px); }
body{
  margin:0;
  font-family:var(--font-base);
  color:var(--text);
  background:var(--bg);
  line-height:1.6;
  -webkit-font-smoothing:antialiased;
  word-break:keep-all;
  overflow-wrap:normal;
}
[lang="ko"]{ font-family:var(--font-ko); }
[lang="en"]{ font-family:var(--font-en); }
/* Every literal "&" is wrapped in this at build time (see wrapAmpersands in
   build.js) so it renders in Arial instead of inheriting Archivo/Pretendard. */
.amp{ font-family:Arial, Helvetica, sans-serif; }
/* Forces a line break only on mobile, for copy that should stay on one line
   at wider widths — hidden by default, shown only under the mobile breakpoint. */
.mobile-break{ display:none; }
@media (max-width:760px){ .mobile-break{ display:block; } }
img{ max-width:100%; display:block; }
a{ color:inherit; text-decoration:none; }
ul{ margin:0; padding:0; list-style:none; }
h1,h2,h3,h4,p{ margin:0; }
button{ font-family:inherit; cursor:pointer; }

/* ---- Language toggle visibility ---- */
html.lang-ko [lang="en"]{ display:none !important; }
html.lang-en [lang="ko"]{ display:none !important; }

/* ---- Layout helpers ---- */
.container{ max-width:var(--container); margin:0 auto; padding:0 30px; }
.section{ padding:96px 0; }
.section--tight{ padding:64px 0; }
.section-divider{ height:1px; background:var(--line); margin:64px 0; }

/* Solid-fill sections read as full-bleed "big cards" — edge-to-edge, no side margin,
   all four corners rounded. Sections stay flush (no gap, no overlap) against whatever
   comes before/after; the rounded corners simply clip each section's own square edge,
   so a sliver of the neighboring section (or the page bg, between two plain sections)
   shows through the curve at both the top and the bottom — the stacked-card look. */
.section--gray, .section--black, .section--dark-accent{
  border-radius:40px;
  position:relative;
  overflow:hidden;
}
.section--gray{ background:var(--bg-2); }
.section--black{ background:var(--bg-2); }
/* Production Lineup card (home page): flush against the stat band above it,
   so only the bottom corners curve — the top stays square. Squaring the top
   alone would still leave the black card's own bottom corners rounded, which
   clip to reveal whatever's behind them — normally the page bg. Pulling this
   section up by that same radius (and padding it back down so the content
   doesn't shift) puts this section's own white behind that curve instead, so
   the reveal is white, not a strip of page-bg gray. z-index keeps the black
   card painting on top everywhere except inside its own corner clip. */
.section--dark-accent{ z-index:2; }
#production-lineup{
  border-top-left-radius:0; border-top-right-radius:0;
  margin-top:-40px; padding-top:calc(96px + 40px);
  z-index:1;
}
.section--black .section-kicker{ color:var(--red-soft); }
.section--black .section-title{ color:var(--text); }
.section--black .section-desc{ color:var(--text-mid); }

.section-head{ max-width:760px; margin-bottom:56px; }
.section-head.center{ margin-left:auto; margin-right:auto; text-align:center; }
.section-kicker{
  display:inline-flex; align-items:center; gap:11px;
  font-size:11.5px; font-weight:600; letter-spacing:.2em; text-transform:uppercase;
  color:var(--red-soft); margin-bottom:16px;
}
.section-kicker::before{ content:""; width:24px; height:2px; border-radius:2px; background:var(--accent-grad); display:inline-block; }
.section-title{ font-size:34px; font-weight:700; letter-spacing:-.02em; color:var(--text); margin-bottom:14px; }
.section-desc{ font-size:16px; color:var(--text-mid); }

.grid{ display:grid; gap:28px; }
.grid-2{ grid-template-columns:repeat(2,1fr); }
.grid-3{ grid-template-columns:repeat(3,1fr); }
.grid-4{ grid-template-columns:repeat(4,1fr); }
.grid-6{ grid-template-columns:repeat(6,1fr); }

/* Pill shape (border-radius:999px) + arrow icon, referenced from
   one-of-one-ai.com/studio's "Reach Out" button: the arrow sits at a natural
   45° (pointing up-right, exactly as drawn in the SVG below — no rotation at
   rest) and rotates clockwise to horizontal/pointing right on hover. Rotating
   a vector that points up-right needs a POSITIVE (clockwise) angle to level
   it out to due-right — rotate(-45deg) would swing it the other way, up to
   due-north/straight-up, which was wrong. */
.btn{
  display:inline-flex; align-items:center; justify-content:center; gap:8px;
  padding:13px 26px; border-radius:999px; font-size:13.5px; font-weight:600; letter-spacing:.01em;
  border:1px solid var(--line); background:var(--surface-2); color:var(--text);
  transition:background .2s var(--ease), color .2s var(--ease), border-color .2s var(--ease), transform .15s var(--ease);
  white-space:nowrap;
}
.btn:hover{ border-color:#c9c4bb; transform:translateY(-1px); }
.btn-arrow{ display:inline-flex; transition:transform .3s var(--ease); }
.btn-arrow svg{ width:14px; height:14px; display:block; }
.btn:hover .btn-arrow{ transform:rotate(45deg); }
.btn--outline{ background:transparent; color:var(--text-mid); border-color:var(--line); }
.btn--outline:hover{ border-color:var(--text-dim); color:var(--text); }

/* Sustainable Management page — 6 policy-document buttons below the lead
   sentence; each opens its own standalone page (target=_blank, see
   .policy-doc in this file) in a new tab. */
.policy-btn-row{ display:grid; grid-template-columns:repeat(3,1fr); gap:12px; margin:32px auto 0; max-width:720px; }
.policy-btn-row .btn{ width:100%; }
/* Light-grey hover for these 6 specific buttons, on top of .btn--outline's
   own (border/text-color-only) hover — a filled background reads clearer
   as "this opens something" for a document-launcher row than the plain
   outline buttons elsewhere on the site. */
.policy-btn-row .btn:hover{ background:var(--surface-2); }
@media (max-width:760px){
  .policy-btn-row{ grid-template-columns:1fr; max-width:420px; }
}
.fairtrade-btn-row{ margin-top:24px; }
.fairtrade-btn-row .btn:hover{ background:var(--surface-2); }
.she-btn-row{ display:flex; flex-wrap:wrap; gap:12px; margin-top:24px; }
.she-btn-row .btn:hover{ background:var(--surface-2); }
.btn--red{ background:var(--red); border-color:var(--red); color:#fff; }
.btn--red:hover{ background:#ff7038; border-color:#ff7038; }
.btn--gray{ background:#d9d6d0; border-color:#d9d6d0; color:var(--text); }
.btn--gray:hover{ background:var(--red); border-color:var(--red); color:#fff; }
.btn--sm{ padding:9px 18px; font-size:12.5px; }

.tag{
  display:inline-block; padding:4px 12px; border-radius:999px; font-size:12px;
  font-weight:700; letter-spacing:.04em; background:var(--surface-2); color:var(--text-mid);
}
.tag--red{ background:rgba(255,90,30,.14); color:var(--red-soft); }

/* ==========================================================================
   Header / Nav
   ========================================================================== */
/* Floating "island" nav bar, transplanted from wakatosmedia.com's header:
   inset from the viewport on all sides (not full-bleed), a rounded pill with a
   constant translucent dark fill + backdrop-filter blur (so whatever scrolls
   underneath is always blurred through it — no scroll-position JS needed for
   the blur itself) and a thin, faint 1px stroke. Reference values, used as-is:
   16px top gap, 20px min side gap, 16px corner radius, blur(10px),
   rgba(0,0,0,.5) fill, rgba(255,255,255,.1) border. */
.site-header{
  position:fixed; top:0; left:0; right:0; z-index:500;
  padding:16px 20px 0; background:transparent;
}
.site-header .container{
  display:flex; align-items:center; height:64px; padding:0 28px;
  background:rgba(0,0,0,.5); backdrop-filter:blur(10px); -webkit-backdrop-filter:blur(10px);
  border:1px solid rgba(255,255,255,.1); border-radius:16px;
  /* Forces the browser to composite this blur on its own GPU layer instead of
     recomputing it inline with the page's layout/paint. Without this, any
     reflow of the content scrolling underneath (a nav click loading a new
     page, a font swap, an image finishing its load) can make the backdrop-
     filter re-sample mid-change and flash a stray vertical seam through the
     blur for a frame — this pins the layer so those reflows don't touch it. */
  transform:translateZ(0);
}

/* The pill's own dark fill is the nav's permanent backdrop regardless of what
   page content is scrolled underneath, so the logo and nav text no longer need
   to track light/dark page state (no more .solid swapping here) — they're
   always styled for a dark surface. */
.brand{ display:flex; align-items:center; gap:10px; flex-shrink:0; margin-right:150px; }
.brand-mark{ height:26px; width:auto; flex-shrink:0; display:block; }
/* Full wordmark by default; swapped for the symbol-only mark on phones (see
   the max-width:760px block). */
.brand-mark--symbol{ display:none; }

.nav{ display:flex; align-items:center; gap:4px; flex:0 0 auto; margin-right:200px; }
.nav > li{ position:relative; }
.nav > li > a, .nav > li > button.nav-link{
  display:flex; align-items:center; gap:6px; padding:12px 16px; font-size:14px; font-weight:500;
  color:#fff; border-radius:8px; background:none; border:none;
  transition:opacity .15s var(--ease);
}
.nav > li > a:hover, .nav > li > button.nav-link:hover, .nav > li.active > a, .nav > li.active > button.nav-link{ opacity:.72; }
/* Hamburger <-> X, driven by .dd-open (set in main.js), not :hover — a plain CSS
   :hover trigger can't be "closed" by a click while the mouse is still over it. */
.nav .burger{ position:relative; width:16px; height:11px; flex-shrink:0; }
.nav .burger span{
  position:absolute; left:0; right:0; height:1.6px; border-radius:1px; background:currentColor;
  transition:transform .25s var(--ease), opacity .2s var(--ease), top .25s var(--ease);
}
.nav .burger span:nth-child(1){ top:0; }
.nav .burger span:nth-child(2){ top:4.7px; }
.nav .burger span:nth-child(3){ top:9.4px; }
.nav li.has-dropdown.dd-open .burger span:nth-child(1){ top:4.7px; transform:rotate(45deg); }
.nav li.has-dropdown.dd-open .burger span:nth-child(2){ opacity:0; }
.nav li.has-dropdown.dd-open .burger span:nth-child(3){ top:4.7px; transform:rotate(-45deg); }

.dropdown{
  /* The trigger <li> is shorter than the pill it sits in (vertically centered
     within the pill's fixed height), so anchoring purely off the <li>'s own
     bottom (100%) landed the dropdown a few px above the pill's true bottom
     edge, overlapping it. +15.4px (not the more obvious +8px) is tuned to land
     exactly ~3px below the pill's actual bottom, not the <li>'s. */
  position:absolute; top:calc(100% + 15.4px); left:50%; transform:translateX(-50%);
  min-width:248px; background:var(--surface-2); border-radius:14px; box-shadow:var(--shadow-md);
  border:1px solid var(--line); padding:8px; opacity:0; visibility:hidden;
  transition:opacity .18s var(--ease), visibility .18s;
}
.nav li.has-dropdown.dd-open .dropdown{ opacity:1; visibility:visible; }
.dropdown a{ display:flex; align-items:center; gap:10px; padding:11px 14px; border-radius:8px; font-size:14px; font-weight:500; color:var(--text-mid); }
.dropdown a:hover{ background:rgba(0,0,0,.045); color:var(--text); }
.dropdown a .dot{ width:6px; height:6px; border-radius:50%; background:var(--line); flex-shrink:0; }
.dropdown a:hover .dot{ background:var(--red-soft); }

/* Mega dropdown: one translucent panel with the 3 links stacked as plain text rows
   (not individual boxed cards), stretching open/rolling up via a max-height transition
   (not the base .dropdown's fade) so it visibly grows/retracts. Width is now
   intrinsic (shrink-to-fit) instead of a fixed 260px — align-items:stretch still
   makes every row match the widest one, so the box ends up exactly as wide as
   the longest label plus its own 10px side padding, not an arbitrary fixed size.
   Color matches the nav pill's fill exactly (rgba(0,0,0,.5), same blur/border)
   so the dropdown reads as a continuation of the same surface, not a lighter
   panel bolted underneath it. */
.dropdown--mega{
  box-sizing:border-box; flex-direction:column; align-items:stretch; gap:2px; min-width:0; width:max-content;
  overflow:hidden; padding:8px 4px;
  background:rgba(0,0,0,.5); backdrop-filter:blur(10px); -webkit-backdrop-filter:blur(10px);
  border-color:rgba(255,255,255,.1);
  /* Rolls open via clip-path, not max-height. Animating max-height meant this
     backdrop-filter box was resizing every frame — the compositor re-sampling
     a blur on a box whose bounds are changing each frame is what showed up
     as a flickering vertical seam every time the Company button was clicked.
     clip-path keeps the box's own size constant (so the blur never resizes)
     and just reveals/hides it — same "grow open" look, GPU-composited, no
     backdrop resampling. */
  clip-path:inset(0 0 100% 0 round 14px);
  transition:clip-path .38s var(--ease), opacity .18s var(--ease), visibility .18s;
}
.nav li.has-dropdown.dd-open .dropdown--mega{ clip-path:inset(0 0 0% 0 round 14px); }
/* ".dropdown a" (base rule above) also matches these — since it adds a type
   selector its specificity beats plain ".dd-group", so this has to be
   ".dropdown a.dd-group" to actually override the padding/font/color here
   instead of silently losing that fight. It's also flex (inherited from
   ".dropdown a"), so centering needs justify-content, not text-align. */
.dropdown a.dd-group{
  justify-content:center; gap:0; padding:14px 6px; border-radius:8px; font-size:14.5px; font-weight:600; color:#fff;
  background:none; border:none;
  transition:background .15s var(--ease), color .15s var(--ease);
}
.dropdown a.dd-group:hover{ background:rgba(255,255,255,.1); color:#fff; }

.header-actions{ display:flex; align-items:center; gap:10px; flex-shrink:0; margin-left:auto; }
/* Lang toggle and mobile menu button both mix a light thumb/pill with dark text
   on top of it, so they can't use mix-blend-mode the way the plain nav links do
   (a dark glyph would blend-invert to whatever's behind it and disappear instead
   of staying readable against its own thumb). Instead they carry their own
   dark, translucent fill — legible on any background without needing to track
   light/dark state.
   No backdrop-filter of their own, deliberately: they already sit on top of
   .site-header .container's own blur(10px). A second, nested backdrop-filter
   here blurs an already-blurred, already-translucent layer, and its blur
   radius doesn't perfectly respect this pill's border-radius clip — the
   result was a faint rectangular seam bleeding past the rounded edge, most
   visible as a vertical line just left of the KOR/ENG pill, and most
   noticeable whenever anything behind the header was animating enough to
   force a re-sample. The plain fill alone (no blur) reads the same against
   an already-blurred backdrop, without the seam. */
.lang-toggle{
  display:flex; align-items:center; background:rgba(20,19,18,.45);
  border-radius:999px; padding:3px; position:relative; border:1px solid rgba(255,255,255,.16);
}
.lang-toggle button{
  position:relative; z-index:1; border:none; background:none; padding:7px 14px; font-size:12px; font-weight:600; letter-spacing:.03em;
  border-radius:999px; color:rgba(255,255,255,.7); transition:color .2s var(--ease);
}
.lang-toggle button.active{ color:var(--text); }
.lang-toggle .thumb{
  position:absolute; top:3px; left:3px; width:calc(50% - 3px); height:calc(100% - 6px);
  background:#fff; border-radius:999px; transition:transform .25s var(--ease);
}
html.lang-en .lang-toggle .thumb{ transform:translateX(100%); }
/* Plain-text "Kr / En" on phones (see max-width:760px block) instead of the
   pill+sliding-thumb version above — this separator sits between the two
   buttons there; hidden here since the pill doesn't need it. */
.lang-toggle .lang-sep{ display:none; color:rgba(255,255,255,.4); font-size:12px; }

.menu-toggle{
  display:none; width:40px; height:40px; border:none; border-radius:8px;
  background:rgba(20,19,18,.45);
  align-items:center; justify-content:center;
}
.menu-toggle span, .menu-toggle span::before, .menu-toggle span::after{
  content:""; display:block; width:18px; height:2px; background:#fff; position:relative;
}
.menu-toggle span::before{ position:absolute; top:-6px; }
.menu-toggle span::after{ position:absolute; top:6px; }

/* Phone header layout: one row, three evenly-anchored slots — symbol-only
   logo left, a small Kr/En pill truly centered (grid's middle "auto" column
   sits dead-center between two equal 1fr outer columns regardless of how
   wide the logo vs. hamburger are — flexbox space-between can't guarantee
   that unless the outer items happen to match width), hamburger right.
   .header-actions normally wraps the lang toggle + hamburger as one flex
   group for desktop; display:contents here makes it "disappear" from the
   grid's perspective so its two children become independent grid items
   instead, without needing to change the shared HTML structure. */
@media (max-width:760px){
  /* Outer columns size to their own content (auto) — fixed equal-px columns
     (e.g. 130px/130px) looked simpler but the logo (~128px) plus a middle
     column wide enough for the toggle didn't actually fit in ~295px of
     available header width, and pushed the hamburger off the right edge.
     auto/1fr/auto can't overflow that way — the middle 1fr track just takes
     whatever's left after the two content-sized outer columns. */
  .site-header .container{
    display:grid; grid-template-columns:auto 1fr auto; align-items:center;
    height:auto; padding:14px 20px;
  }
  .brand{ grid-column:1; justify-self:start; margin-right:0; }
  .brand-mark{ height:21px; }
  /* .nav (desktop's full inline nav) and .mobile-nav (the old full-screen
     drawer) both stay hidden on phones — .menu-popup below is what the
     hamburger opens now. */
  .nav{ display:none !important; }
  .header-actions{ display:contents; }
  /* Centering this on the container's true midpoint (position:absolute +
     left:50%) sounds more precise, but the logo (~128px) is wide enough
     relative to this ~335px header that true-center sits only ~11px past
     the logo's own right edge — a small rightward nudge for "move it right"
     pushed it straight into an overlap with the logo. Centering it in the
     1fr track instead (its natural grid position, between the logo and the
     hamburger) has no such risk, since that track's bounds are exactly
     "whatever's left after the logo and hamburger" — it can't overlap
     either by construction. It reads a bit right of true-center as a
     result (the logo takes up more of the row than the hamburger does),
     which already reads as "moved right" without needing an extra nudge. */
  .lang-toggle{ grid-column:2; justify-self:center; transform:scale(.85); }
  .menu-toggle{ display:flex !important; grid-column:3; justify-self:end; width:36px; height:36px; }
}

/* Hamburger popup (phones): a small card dropped just under the header
   pill, toggled by .menu-toggle via .open (see main.js) — replaces the old
   full-screen .mobile-nav drawer, which is still valid markup/JS underneath
   but no longer wired to anything that opens it. */
.menu-popup{
  display:none;
  position:fixed; top:calc(var(--header-h) + 8px); left:20px; right:20px; z-index:250;
  background:rgba(20,19,18,.7); backdrop-filter:blur(14px); -webkit-backdrop-filter:blur(14px);
  border:1px solid rgba(255,255,255,.14); border-radius:20px; padding:8px;
  box-shadow:var(--shadow-md);
  opacity:0; transform:translateY(-10px); pointer-events:none;
  transition:opacity .2s var(--ease), transform .2s var(--ease);
}
@media (max-width:760px){ .menu-popup{ display:block; } }
.menu-popup.open{ opacity:1; transform:translateY(0); pointer-events:auto; }
.menu-popup ul{ display:flex; flex-direction:column; }
.mp-top{
  display:block; padding:14px 14px; font-size:15px; font-weight:700; color:#fff;
  border-bottom:1px solid rgba(255,255,255,.1);
}
.menu-popup li:last-child > .mp-top{ border-bottom:none; }
/* Company's 3 sub-links, shown directly under it (not an accordion) in a
   visibly lighter weight than the bold top-level links above. */
.mp-sub{ padding:2px 14px 12px; border-bottom:1px solid rgba(255,255,255,.1); }
.mp-sub a{ display:block; padding:8px 2px; font-size:13px; font-weight:300; color:rgba(255,255,255,.65); }

/* ==========================================================================
   Hero
   ========================================================================== */
.hero{
  position:relative; min-height:100vh; display:flex; align-items:center;
  background:var(--bg); color:var(--text); overflow:hidden;
}
/* Background video (assets/hero-video.mp4), autoplay+loop, no poster — the
   photo slideshow this replaced is gone entirely, not just hidden underneath. */
.hero .hero-video{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; }
.hero::before{
  content:""; position:absolute; inset:0; z-index:2;
  background:linear-gradient(180deg, rgba(0,0,0,.35) 0%, rgba(0,0,0,.15) 34%, rgba(0,0,0,.62) 72%, #000 100%);
}
.hero-content{
  position:relative; z-index:3; width:100%;
  display:flex; align-items:center; justify-content:space-between; gap:48px;
}
.hero-eyebrow-col{ flex:0 0 auto; max-width:320px; }
.hero-headline-col{ flex:0 0 auto; text-align:right; }
.hero-eyebrow{ display:inline-flex; align-items:center; gap:11px; font-size:17px; font-weight:600; letter-spacing:.2em; text-transform:uppercase; color:var(--red-soft); }
/* EN runs longer than KO at the same size, so it gets its own smaller size —
   17px * 0.8 (~20% smaller) — rather than sharing the KO size. */
.hero-eyebrow [lang="en"]{ font-size:13.6px; }
.hero-eyebrow::before{ content:""; width:24px; height:2px; border-radius:2px; background:var(--accent-grad); flex-shrink:0; }
.hero h1{ font-weight:300; letter-spacing:-.02em; max-width:none; color:#fff; }
.hero h1 .line{ display:block; white-space:nowrap; font-size:clamp(12.5px, 3.6vw, 46px); line-height:1.28; }
.hero h1 .line b{ font-weight:600; }
.hero h1 em{ color:var(--red-soft); font-style:normal; }
.hero p.lede{ margin-top:22px; font-size:17px; color:#e7e7ea; max-width:600px; }
.hero-actions{ margin-top:34px; display:flex; gap:12px; flex-wrap:wrap; }
@media (max-width:900px){
  .hero-content{ flex-direction:column; align-items:flex-start; gap:28px; }
  .hero-eyebrow-col{ max-width:none; }
  .hero-headline-col{ text-align:left; }
}
/* Phones: the eyebrow line is a small label above the real headline, not a
   second headline — the clamp() above has a floor of 12.5px, which on a
   ~375px phone (3.6vw ≈ 13.5px) rendered both of them at roughly the same
   tiny size instead of establishing that hierarchy. Eyebrow shrinks further,
   the headline gets its own phone-specific clamp with a much taller floor. */
@media (max-width:760px){
  .hero-eyebrow{ font-size:11px; gap:8px; }
  .hero-eyebrow [lang="en"]{ font-size:9px; }
  .hero-eyebrow::before{ width:16px; }
  .hero h1 .line{ font-size:clamp(28px, 9vw, 46px); }
}
.hero-location{ position:absolute; right:30px; bottom:32px; z-index:3; text-align:right; font-size:13px; color:rgba(255,255,255,.72); }
.hero-location strong{ display:block; font-size:15px; color:#fff; font-weight:700; }

.hero-scroll-hint{ position:absolute; left:50%; bottom:28px; transform:translateX(-50%); z-index:3; }
.hero-scroll-hint .line{ display:block; width:1px; height:42px; background:linear-gradient(var(--red), transparent); animation:scrollDrop 1.8s ease-in-out infinite; }
@keyframes scrollDrop{ 0%,100%{ opacity:.3; transform:scaleY(.6); } 50%{ opacity:1; transform:scaleY(1); } }

/* Lead reveal (scroll-triggered centered statement) */
.lead-reveal{ padding:64px 0; text-align:center; }
.lead-reveal-text{
  font-size:clamp(22px, 3.2vw, 38px); font-weight:300; line-height:1.55; letter-spacing:-.01em;
  color:var(--text); max-width:880px; margin:0 auto;
  opacity:0; transform:translateY(36px); filter:blur(8px);
  transition:opacity 1.1s cubic-bezier(.2,.7,.15,1), transform 1.1s cubic-bezier(.2,.7,.15,1), filter 1.1s cubic-bezier(.2,.7,.15,1);
}
.lead-reveal-text.is-visible{ opacity:1; transform:translateY(0); filter:blur(0); }
.lead-reveal-text--center{ text-align:center; }

/* Word-by-word rise-in for top-of-page headlines (words are pre-split at
   build time — see wrapWordsHTML in build.js). Each word starts lowered/
   invisible and rises into place with a per-word delay once the headline's
   .is-visible class lands. When this sits on a .lead-reveal-text (page-header
   statements), it replaces that class's own whole-block blur/slide — the
   words do the revealing instead.

   Layer isolation, and why it's on .word-reveal (the sentence) as well as
   each .word-reveal-word: on pages where this sentence lives inside
   .page-header, that ancestor spatially overlaps the fixed header's
   backdrop-filter sampling area (see .page-header's own comment). will-change
   on 20-50+ individual inline word spans is a hint, not a guarantee —
   Chromium can (and does) decide that's too many tiny layers and "squash"
   them back into their parent's layer instead of promoting each one. When
   that happens, every word's transition still forces a repaint of the whole
   shared layer, which for a sentence inside .page-header means the header-
   overlapping region repaints too — a vertical seam in the header that walks
   left-to-right as each successive word's region gets marked dirty, i.e.
   exactly tracking each word's on-screen position.
   transform:translateZ(0) is a hard guarantee of its own layer (not a hint),
   so putting it directly on .word-reveal carves the whole sentence out as
   one dedicated layer, separate from .page-header's — .page-header's own
   (header-overlapping) background pixels then never need to repaint no
   matter what's happening word-by-word inside this sentence. */
.word-reveal{ transform:translateZ(0); isolation:isolate; }
.word-reveal-word{
  display:inline-block; will-change:opacity, transform;
  opacity:0; transform:translateY(10px);
  transition:opacity .9s cubic-bezier(.16,1,.3,1), transform .9s cubic-bezier(.16,1,.3,1);
}
.is-visible .word-reveal-word{ opacity:1; transform:translateY(0); }
.lead-reveal-text.word-reveal{ opacity:1; filter:none; }

.lead-reveal-card{
  position:relative; border-radius:var(--radius-lg); overflow:hidden;
  background-size:cover; background-position:center; background-repeat:no-repeat;
  display:flex; align-items:center; justify-content:center;
  width:100%; aspect-ratio:2.05; padding:48px; box-shadow:var(--shadow-md);
}
.lead-reveal-card::before{
  content:''; position:absolute; inset:0; z-index:0;
  background:linear-gradient(180deg, rgba(10,9,8,.42) 0%, rgba(10,9,8,.58) 100%);
}
.lead-reveal-card .lead-reveal-text{ position:relative; z-index:1; margin:0 auto; }
.lead-reveal-text--onimage{ color:#fff; }
@media (max-width:600px){
  /* aspect-ratio:2.05 (from the base rule above) is wide-and-short — fine on
     desktop where the line-height:1.55 paragraph only needs 2-3 lines, but
     on a ~360-390px phone the same text wraps to 5-6 lines and the fixed
     ratio doesn't have the height for it, so it was clipping out of the
     card. aspect-ratio:auto here means the card's height just follows its
     content instead, so all the text always fits regardless of how many
     lines it wraps to. */
  .lead-reveal-card{ aspect-ratio:auto; padding:56px 28px; }
  .lead-reveal-card .lead-reveal-text{ line-height:1.35; }
}

/* Quick links */
.quick-links{ margin-top:-64px; position:relative; z-index:3; }
.quick-links .grid-4{ gap:20px; }
.quick-card{
  background:var(--surface); border:1px solid var(--line); border-radius:var(--radius-lg);
  padding:26px 22px; box-shadow:var(--shadow-md); transition:transform .2s var(--ease), border-color .2s var(--ease);
  display:flex; flex-direction:column; gap:14px;
}
.quick-card:hover{ transform:translateY(-4px); border-color:var(--red-soft); }
.quick-card .icon{
  width:46px; height:46px; border-radius:12px; background:var(--surface-2); color:var(--text);
  display:flex; align-items:center; justify-content:center;
}
.quick-card .icon svg{ width:22px; height:22px; }
.quick-card h3{ font-size:16px; font-weight:700; color:var(--text); }
.quick-card span.arrow{ margin-top:auto; font-size:13px; font-weight:700; color:var(--red-soft); display:flex; align-items:center; gap:6px; }

/* ==========================================================================
   Page header (non-home pages)
   ========================================================================== */
.page-header{
  /* Used to extend up behind the fixed header (padding pushed the visible
     content down instead) so its background would read through the header's
     blur with no seam. Multiple GPU-layer-isolation attempts on the word-
     reveal sentence inside this box (translateZ(0), isolation:isolate,
     will-change) never fully stopped a word-by-word animation anywhere in
     here from showing up as a vertical seam walking across the header,
     synced to each word — because backdrop-filter fundamentally re-samples
     whatever's live behind it, and this box was, by design, partly behind
     it. Rather than continue chasing that through compositor internals:
     margin-top instead of folding the header's height into padding means
     this box's own background now starts right where the fixed header ends,
     not behind it — no more shared pixels for any animation in here to
     disturb. Invisible in itself: body{background:var(--bg)} is the exact
     same color, so the area now behind the header instead reads identically
     to before. */
  margin-top:var(--header-h); padding:64px 0 48px; background:var(--bg); color:var(--text); position:relative; overflow:hidden;
}
.page-header--slim{ padding-bottom:20px; }
.page-header .container{ position:relative; z-index:1; }
.breadcrumb{ font-size:13px; color:var(--text-dim); margin-bottom:16px; display:flex; gap:10px; align-items:center; }
.breadcrumb a{ color:var(--text-mid); }
.breadcrumb a:hover{ color:var(--text); }
.breadcrumb .sep{ color:rgba(0,0,0,.22); }
.breadcrumb .current{ color:var(--text); font-weight:600; }
.page-header h1{ font-size:clamp(30px,4.4vw,44px); font-weight:300; letter-spacing:-.02em; color:var(--text); }
.page-header p{ margin-top:14px; color:var(--text-mid); max-width:640px; font-size:16px; }
/* Higher specificity than ".page-header p" above, so a .lead-reveal-text placed in a
   page-header keeps its own font-size/weight instead of inheriting the plain-paragraph
   defaults. */
.page-header p.lead-reveal-text{ font-size:clamp(22px, 3.2vw, 38px); font-weight:300; color:var(--text); max-width:880px; }
/* Small centered label above a .subnav row, for grouping multiple pill rows (e.g.
   Exterior / Interior on the Products page). Only affects .subnav margin/alignment
   when it directly follows a label — other pages' plain .subnav (e.g. Production
   Process's left-aligned pill row) keep their existing defaults untouched. */
.subnav-row-label{
  text-align:center; font-size:13px; font-weight:700; letter-spacing:.1em; text-transform:uppercase;
  color:var(--text-dim); margin-top:32px;
}
.subnav-row-label:first-of-type{ margin-top:36px; }
.subnav-row-label + .subnav-rows{ margin-top:14px; }
/* Each column's pills are pre-split (in HTML) into rows of at most 3, rather than one
   flex-wrap row — .subnav-rows stacks those rows; nowrap keeps each row exactly as
   grouped instead of letting the browser reflow item counts per row on its own. */
.subnav-rows{ display:flex; flex-direction:column; gap:10px; }
.subnav-rows .subnav{ justify-content:center; flex-wrap:nowrap; margin-top:0; }
/* Exterior/Interior split left/right instead of stacked rows — each column gets its
   own label + pill rows, so the label no longer needs the block-level top margin above
   (the grid wrapper carries that instead). */
.subnav-columns{ margin-top:100px; gap:48px; position:relative; }
.subnav-columns .subnav-row-label{ margin-top:0; }
/* Thin vertical divider centered in the gap between the Exterior/Interior columns. */
.subnav-columns::before{
  content:""; position:absolute; top:0; bottom:0; left:50%; width:1px;
  background:var(--text-mid);
}
@media (max-width:760px){
  .subnav-columns{ grid-template-columns:1fr; gap:28px; }
  .subnav-rows .subnav{ flex-wrap:wrap; }
  .subnav-columns::before{ display:none; }
}
.subnav{ display:flex; gap:8px; margin-top:32px; flex-wrap:wrap; }
.subnav a{
  padding:9px 16px; border-radius:999px; font-size:13px; font-weight:500; border:1px solid transparent;
  background:var(--surface-2); color:var(--text-mid);
  transition:all .18s var(--ease);
}
.subnav a:hover, .subnav a.active{ background:var(--red); border-color:var(--red); color:#fff; }
/* One-Stop Service Policy's 6 pills: EN labels ("Design & Development",
   "Assembly & Packaging"...) are long enough that plain flex-wrap doesn't
   settle into an even 3-and-3 the way the short KO labels do, so force a
   3-column grid for English only; KO keeps the natural flex-wrap. Scoped to
   #onestop so it doesn't touch products.html's own .subnav rows. */
html.lang-en #onestop .subnav{ display:grid; grid-template-columns:repeat(3,1fr); }
/* Grid rows stretch every cell to the row's tallest item — when "Design &
   Development"/"Assembly & Packaging" wrap to 2 lines, their row-mates
   ("MES System", "Injection Molding", "Plating", "Painting") get stretched
   taller too, but plain padding leaves their text pinned to the top of that
   taller box instead of centered. flex + center fixes it regardless of
   which pill ends up tallest. */
html.lang-en #onestop .subnav a{ text-align:center; display:flex; align-items:center; justify-content:center; }

/* ==========================================================================
   Cards / components
   ========================================================================== */
/* min-width:0 overrides the grid-item default of min-width:auto, which
   otherwise won't let a .card shrink below an overflow:auto child's natural
   content width (e.g. a wide .table-scroll table) — without it, a card
   inside a narrow grid column stretches to fit its child instead of
   respecting the column's actual width, blowing out the page on mobile. */
.card{ min-width:0; background:var(--surface); border:1px solid var(--line); border-radius:var(--radius-lg); padding:28px; transition:box-shadow .2s var(--ease), transform .2s var(--ease), border-color .2s var(--ease); }
.card:hover{ box-shadow:var(--shadow-md); transform:translateY(-3px); border-color:#c9c4bb; }
.card .num{ font-size:12px; font-weight:700; color:var(--red-soft); letter-spacing:.08em; margin-bottom:12px; text-transform:uppercase; }
.card .people-icon{ width:72px; height:72px; object-fit:cover; border-radius:16px; background:var(--icon-bg); display:block; margin-bottom:18px; }
.card h3{ font-size:18px; font-weight:600; margin-bottom:10px; color:var(--text); }
.card p{ font-size:14.5px; color:var(--text-mid); }

.icon-tile{ width:48px; height:48px; border-radius:12px; background:rgba(255,90,30,.14); color:var(--red-soft); display:flex; align-items:center; justify-content:center; margin-bottom:16px; }
.icon-tile svg{ width:24px; height:24px; }
.icon-tile.dark{ background:var(--surface-2); color:var(--text); border:1px solid var(--line); }

.media-card{ border-radius:var(--radius-lg); overflow:hidden; border:1px solid var(--line); background:var(--surface); }
.media-card .media{ aspect-ratio:4/3; overflow:hidden; background:var(--bg-2); position:relative; }
.media-card .media img{ width:100%; height:100%; object-fit:cover; transition:transform .35s var(--ease); filter:saturate(.94); }
.media-card:hover .media img{ transform:scale(1.05); }
.media-card .media.placeholder, .media.placeholder{ display:flex; align-items:center; justify-content:center; color:var(--text-dim); font-size:13px; text-align:center; padding:20px; background:var(--bg-2); }
.media-card .body{ padding:18px 20px; }
.media-card .body h3{ font-size:15px; font-weight:600; color:var(--text); }
.media-card .body p{ font-size:13px; color:var(--text-dim); margin-top:4px; }

/* Production Lineup teaser (home page): the photo itself keeps rounded corners,
   but there's no card box (no border/background/shadow) around it — it sits
   directly on the section's own background instead of floating in a white card. */
.product-teaser .media{ aspect-ratio:4/3; overflow:hidden; border-radius:var(--radius-lg); position:relative; background:var(--bg-2); }
.product-teaser-video{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; }
.product-teaser .body{ padding:18px 4px 0; text-align:center; }
.product-teaser .body h3{ font-size:15px; font-weight:600; color:var(--text); }

/* Product category layout (Products page): one large hero photo per category, then
   a row of smaller supporting shots underneath. */
.product-hero{
  margin:32px 0 0; border-radius:var(--radius-lg); overflow:hidden; border:1px solid var(--line);
  aspect-ratio:21/9; background:var(--bg-2);
}
.product-hero img{ width:100%; height:100%; object-fit:cover; }
/* Real photo categories are all native 3:2 — match the container to that ratio so
   object-fit:cover never has to crop into the subject (was cropping top/bottom on
   the 21:9 hero and left/right on the 4:3 subgrid cards). Placeholder "coming soon"
   categories keep the original 21:9/4:3 boxes untouched. */
.product-hero--photo{ aspect-ratio:3/2; }
.media-card .media--photo{ aspect-ratio:3/2; }
/* Source photo is portrait, shown in this landscape hero box rotated 90° so it
   reads right-side-up-sideways instead of cropped. Sized/centered manually
   (not object-fit:cover) so the rotated image is never cropped — it's scaled
   to its own aspect ratio and centered, black filling whatever the rotated
   footprint doesn't cover. */
.product-hero--rotate{ position:relative; background:#000; }
.product-hero--rotate img{
  width:auto; height:auto; max-width:100%; max-height:100%; object-fit:contain;
  position:absolute; top:50%; left:50%; transform:translate(-50%,-50%) rotate(90deg);
}
/* object-fit:contain + black backdrop instead of cover, for source photos whose
   own aspect ratio doesn't match this box — shows the full product instead of
   cropping into it. The "loose"/"tight" padding variants additionally shrink
   the product further inside its already-uncropped box, for more breathing
   room than a plain contain-fit gives on its own. */
.product-hero--contain-loose{ background:#000; }
.product-hero--contain-loose img{ object-fit:contain; padding:8%; box-sizing:border-box; }
/* Stays object-fit:cover — just nudges the already-cropped photo within its
   own frame (parent's overflow:hidden clips it same as before). scale must
   come first so the later translateY isn't itself scaled down to ~45px.
   Shrinking it below the frame's own size exposes the frame's background at
   the edges — background:#000 so that's black margin, not the default white. */
.product-hero--shift{ background:#000; }
.product-hero--shift img{ transform:translateY(50px) scale(.9); }
/* .media-card .media{background:var(--bg-2)} and .media-card .media img{object-fit:cover}
   (the base card rules) are both more specific than a single .media--contain
   class alone would be — scope through .media-card on both so they actually
   win instead of silently losing the cascade back to white/cover. */
.media-card .media--contain{ background:#000; }
.media-card .media--contain img{ object-fit:contain; }
.media-card .media--contain-tight{ background:#000; }
.media-card .media--contain-tight img{ object-fit:contain; padding:10%; box-sizing:border-box; }
/* A fixed px padding (not %) — % padding is relative to the box's WIDTH on
   every side including top/bottom, which is what previously blew out the
   vertical space on this 3:2 box (see git history). A flat 10px has no such
   risk, and matches "about a 10px margin" literally instead of a proportion. */
.media-card .media--contain-xl{ background:#000; }
.media-card .media--contain-xl img{ object-fit:contain; padding:10px; box-sizing:border-box; }
.product-subgrid{ display:grid; grid-template-columns:repeat(3,1fr); gap:16px; margin-top:16px; }
/* For categories with exactly 2 supporting photos — the plain 3-col grid left an
   empty, awkward third slot on the right. */
.product-subgrid--2{ grid-template-columns:repeat(2,1fr); }

/* Subgrid carousel: used instead of the plain 3-col grid when a category has more
   than 3 supporting images — same card look, but the row scrolls with prev/next
   arrows instead of wrapping to extra rows. The track carries left/right padding
   so the active card sits centered with the prev/next cards peeking in at the
   edges (scroll-snap-align:center, not :start) — an earlier version had the
   first card flush against the track's left edge with no peek, which is what
   this replaced. The mask-image fade is applied to the track itself (not the
   outer .subgrid-carousel wrapper, which also holds the prev/next arrow
   buttons) — masking the wrapper would fade the arrows too, since they sit
   well inside the fade zone at only 8px from the edge. */
.subgrid-carousel{ position:relative; margin-top:16px; }
/* Desktop: no left padding and scroll-snap-align:start, so the first (and
   every subsequent) card's own LEFT edge sits flush with the hero card's
   left edge above it — a centered/padded track (still used on mobile, see
   the media query below) put visible space in front of card 1 instead.
   Three full cards fit the row at a time (no partial/peeking card), so
   there's no mask-image fade here either — that was only needed to soften
   a card getting visually cut off at the edge, which doesn't happen now. */
.product-subgrid--scroll{
  margin-top:0; display:flex; flex-wrap:nowrap; overflow-x:auto; scroll-snap-type:x mandatory;
  scroll-behavior:smooth; scrollbar-width:none;
}
.product-subgrid--scroll::-webkit-scrollbar{ display:none; }
.product-subgrid--scroll .media-card{ flex:0 0 calc((100% - 32px)/3); scroll-snap-align:start; }
/* Nav bar (below the track) — reuses R&D's .peek-nav/.peek-nav-thumb styling
   verbatim (via the shared class names in the markup) for visual
   consistency; .subgrid-nav here is purely a visibility-scoping class so
   hiding/showing it doesn't also touch R&D's always-on nav bar, which shares
   the same .peek-nav class. Hidden by default; shown per-breakpoint below. */
.subgrid-nav{ display:none; }
/* Reference-image redesign, scoped to this page's nav bars only — R&D's
   thin-line-plus-riding-pill style (white line on its dark section
   background) stays exactly as it was. This version is a solid gray capsule
   track with the orange thumb inset inside it (shorter than the track, not
   riding on top of a thin line), and the thumb is a light-to-dark orange
   gradient instead of a flat fill. Two-class selectors so they reliably
   beat .peek-nav/.peek-nav-thumb's own rules regardless of which comes
   first in the file. */
.peek-nav.subgrid-nav::before{
  height:10px; border-radius:999px; background:var(--line);
}
.peek-nav.subgrid-nav .peek-nav-thumb{
  height:6px; border-radius:999px;
  background:linear-gradient(90deg, var(--red), var(--red-soft));
}

/* Desktop (arrows removed entirely, see products.html): a section only gets
   the scrolling nav-bar carousel if it actually has more than 3 cards to
   scroll through — 3 or fewer already show completely at the 3-up card
   width above, so there's nothing to page through and the nav bar/scrolling
   would be inert chrome. :has() lets this be decided per-section straight
   from the markup instead of a JS-computed class. */
@media (min-width:761px){
  .subgrid-carousel:has(.media-card:nth-child(4)) + .subgrid-nav{ display:block; }
  .product-subgrid--scroll:not(:has(.media-card:nth-child(4))){
    overflow-x:visible; scroll-snap-type:none;
  }
  /* Exactly 2 cards: fill the row 2-up instead of 3-up-with-an-empty-slot. */
  .product-subgrid--scroll:not(:has(.media-card:nth-child(3))) .media-card{
    flex:0 0 calc((100% - 16px)/2);
  }
}
@media (max-width:760px){
  .product-hero{ aspect-ratio:4/3; }
  .product-hero--photo{ aspect-ratio:3/2; }
  .product-subgrid{ grid-template-columns:1fr; }
  .subgrid-nav{ display:block; }
  /* Mobile keeps the padded, centered-card layout (unchanged by the desktop
     flush-left rework above) — restores the padding, mask, and
     scroll-snap-align:center that the desktop rules no longer set. Runs for
     every section regardless of card count — mobile isn't part of this
     3-vs-4+ split, still the always-on peek carousel from before. */
  .product-subgrid--scroll{
    padding:0 12%;
    -webkit-mask-image:linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
    mask-image:linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
  }
  /* Same content-box caveat as the desktop rule above: padding:12% a side
     leaves a 76%-wide content box, so flex-basis:100% is what renders each
     card at 76% of the track's outer width. */
  .product-subgrid--scroll .media-card{ flex:0 0 100%; scroll-snap-align:center; }
}

.section--dark-accent{ background:#141312; }
.stat-band-lead{
  text-align:center; max-width:820px; margin:0 auto 94px; font-size:19px; font-weight:400;
  line-height:1.65; color:rgba(255,255,255,.82);
}

/* Stat band (single bar, 4 columns divided by rules) */
.stat-band{ display:flex; background:#141312; border:1px solid #d9d6d0; border-radius:var(--radius-lg); overflow:hidden; }
.stat-col{ flex:1; min-width:0; padding:40px 32px; border-left:1px solid #d9d6d0; }
.stat-col:first-child{ border-left:none; }
/* min-height reserves room for the tallest title (the 4th card's 2-line
   "글로벌 기준, 검증된 신뢰" / lh-tight at 1.32 = 73.9px) so every .stat-lead
   below starts at the same top regardless of whether its own title is one
   line or two — the other 3 cards' single-line titles just leave blank
   space above their paragraph instead of the paragraphs landing higher. */
.stat-figure{ font-size:28px; font-weight:800; color:#f7f4f0; letter-spacing:-.01em; min-height:74px; }
.stat-figure b{ color:var(--red); font-weight:800; }
/* Icon + two-line title as a flex row: the icon sits beside the WHOLE title
   block (both lines share one left edge, indented past the icon+gap) rather
   than being inline before just the first line's text — an earlier inline-
   before-the-text version left the icon at the box's left edge while the
   wrapped second line also started flush at that same left edge, which read
   as the icon "belonging" only to line 1 and line 2 looking oddly placed
   relative to it. This flex layout is what the reference screenshot shows:
   icon on the left, both text lines uniformly indented next to it. */
.stat-figure-row{ display:flex; align-items:flex-start; gap:10px; }
.stat-figure-row .lh-tight{ line-height:1.32; }
/* Sized to each language's actual rendered line-box height (measured via
   Range.getBoundingClientRect on the first character, not canvas ink-only
   metrics) so the icon visually spans the same vertical space as the first
   line of text — matching the reference screenshot's proportions. Korean
   Pretendard and Latin Archivo have different font metrics at the same
   font-size, hence the different pixel values. */
.stat-icon{ flex-shrink:0; stroke:var(--red); fill:none; }
.stat-icon--ko{ width:34px; height:34px; }
/* The EN icon's top edge sits ~3.2px above 'C's own top edge at every
   breakpoint tested (PC, iPhone 12 mini, Galaxy A36 — measured via
   Range.getBoundingClientRect, same offset each time), so this nudge applies
   globally rather than being scoped to a specific viewport width. */
.stat-icon--en{ width:30px; height:30px; margin-top:3px; }
/* Checkmark draws left-to-right in sync with the 3 number cards' count-up —
   pathLength="1" normalizes the dash units so 0..1 always means "start..end"
   regardless of the path's real geometric length. main.js adds .is-drawn to
   this path at the exact moment it also starts the sibling count-up numbers
   (same IntersectionObserver callback, same trigger instant), rather than a
   separate scroll observer that could fire at a slightly different scroll
   position and drift out of sync with the counters. */
.stat-check-path{ stroke-dasharray:1; stroke-dashoffset:1; transition:stroke-dashoffset 1s ease; }
.stat-check-path.is-drawn{ stroke-dashoffset:0; }
@media (prefers-reduced-motion: reduce){
  .stat-check-path{ transition:none; stroke-dashoffset:0; }
}
.stat-lead{ margin-top:14px; font-size:13.5px; line-height:1.65; color:rgba(255,255,255,.55); }
@media (max-width:900px){
  .stat-band{ flex-wrap:wrap; border-radius:var(--radius-lg); }
  .stat-col{ flex:0 0 50%; border-left:none; border-top:1px solid #d9d6d0; }
  .stat-col:nth-child(odd){ border-left:none; }
  .stat-col:nth-child(1), .stat-col:nth-child(2){ border-top:none; }
}
@media (max-width:560px){
  .stat-col{ flex:0 0 100%; border-top:1px solid #d9d6d0; }
  .stat-col:first-child{ border-top:none; }
  /* The max-width:900px block above exempts item 2 from border-top (it's the
     top-right item in that breakpoint's 2-column layout, so no line belongs
     above it) — that rule's :nth-child(2) makes it more specific than the
     plain .stat-col above, so it kept winning here too even though at this
     narrower, single-column layout item 2 is now stacked directly under
     item 1 and needs the line back. */
  .stat-col:nth-child(2){ border-top:1px solid #d9d6d0; }
}

.photo-strip{ display:grid; grid-template-columns:repeat(4,1fr); gap:16px; }
.photo-strip figure{ margin:0; border-radius:var(--radius-md); overflow:hidden; border:1px solid var(--line); aspect-ratio:4/3; background:var(--bg-2); }
.photo-strip figure img{ width:100%; height:100%; object-fit:cover; filter:saturate(.94); }

/* History — repeating S-shaped serpentine timeline, scroll-colored */
.history-serpentine{ max-width:1080px; margin:0 auto; position:relative; }
.hs-row{ position:relative; display:flex; padding:0 0 30px; }
.hs-row.reverse{ flex-direction:row-reverse; }

/* row's own horizontal line: gray track + red fill overlay (fills via --fill 0..1) */
.hs-row::before{
  content:""; position:absolute; top:6px; left:12.5%; right:12.5%; height:2px; background:var(--line); z-index:1;
}
.hs-row::after{
  content:""; position:absolute; top:6px; left:12.5%; right:12.5%; height:2px; background:var(--red); z-index:1;
  transform-origin:left center; transform:scaleX(var(--fill,0)); box-shadow:0 0 8px rgba(255,90,30,.55);
}
.hs-row.reverse::after{ transform-origin:right center; }

/* connector: fixed-height flow gap between rows (purely a layout spacer now — the
   turn itself is drawn by a JS-generated .hs-turn SVG, see main.js). .stub is likewise
   unused on desktop — both are repurposed as the mobile fallback spine, see media query below. */
.hs-connector{ height:34px; }
.hs-row .stub{ display:none; }

/* Turn between two rows: ONE continuous semicircle (radius = half the real measured gap
   between the two dots it joins, computed in main.js via getBoundingClientRect), not a
   corner+straight+corner assembly — a true half-circle is tangent-perfect at both ends by
   construction, so there's no seam to misalign and no separate piece to pop into "lit"
   out of sync with the row-line fill it hands off to/from (dash-offset is driven
   continuously alongside --fill in main.js, off the same scroll-progress value). */
.hs-turn{ position:absolute; z-index:1; overflow:visible; pointer-events:none; }
.hs-turn .track{ fill:none; stroke:var(--line); stroke-width:2; stroke-linecap:round; }
.hs-turn .fill{ fill:none; stroke:var(--red); stroke-width:2; stroke-linecap:round; filter:drop-shadow(0 0 3px rgba(255,90,30,.55)); }

.hs-item{
  flex:1; min-width:0; position:relative; z-index:2; display:flex; flex-direction:column;
  align-items:center; text-align:center; padding:0 14px;
}
.hs-item .dot{
  width:12px; height:12px; border-radius:50%; background:var(--bg); border:2.5px solid var(--line);
  margin-bottom:14px; flex-shrink:0; transition:border-color .4s ease, box-shadow .4s ease;
}
.hs-item .dot.lit{ border-color:var(--red); box-shadow:0 0 8px rgba(255,90,30,.55); }
.hs-item .year{ font-weight:700; color:var(--red-soft); font-size:13px; letter-spacing:.03em; }
.hs-item p{ margin-top:5px; font-size:13px; color:var(--text-mid); line-height:1.55; max-width:165px; }

@media (max-width:760px){
  .history-serpentine{ padding-left:28px; }
  .hs-row, .hs-row.reverse{ flex-direction:column; align-items:flex-start; gap:24px; padding-bottom:24px; }
  .hs-row::before, .hs-row::after{ display:none; }
  .hs-turn{ display:none; }
  /* mobile falls back to a single straight left-edge spine: .stub covers each row's own
     height, .hs-connector bridges the gap to the next row. top:8px lines the spine's start
     up with the dot's true vertical center (dot is top:2px, 12px tall -> center at 2+6=8px;
     was hardcoded to 20px before, which sat visibly below the dot instead of through it).
     Each piece is a gray track (background) with an orange ::after overlay that scales in
     from the top via the same --fill (row) / --fill (connector, set in main.js) progress
     used for the desktop row-line and turn-curve fills, so the mobile spine recolors on
     scroll exactly like the desktop version instead of staying permanently gray. */
  .hs-row .stub{ display:block; position:absolute; left:0; top:8px; bottom:0; width:2px; background:var(--line); overflow:hidden; }
  .hs-row .stub::after{
    content:""; position:absolute; inset:0; background:var(--red);
    transform-origin:top; transform:scaleY(var(--fill,0));
  }
  .hs-connector{ position:relative; }
  .hs-connector::before{ content:""; position:absolute; left:0; top:0; bottom:0; width:2px; background:var(--line); }
  .hs-connector::after{
    content:""; position:absolute; left:0; top:0; bottom:0; width:2px; background:var(--red);
    transform-origin:top; transform:scaleY(var(--fill,0));
  }
  .hs-item{ align-items:flex-start; text-align:left; padding:0 0 0 22px; max-width:none; }
  /* left:-5px (not -6px): the spine (.stub/.hs-connector) is a 2px-wide box
     starting at left:0, so its visual center sits at x=1, not x=0. The dot
     (12px wide) needs its own center at that same x=1, i.e. left = 1 - 6 = -5. */
  .hs-item .dot{ position:absolute; left:-5px; top:2px; margin-bottom:0; }
  .hs-item p{ max-width:none; }
}

/* Certification cards */
.cert-card{ text-align:center; padding:32px 24px; }
.cert-card .cert-badge-img{ width:168px; height:168px; margin:0 auto 18px; display:block; }

/* Table */
/* Wraps a .data-table so wide multi-column tables (5+ cols with long labels)
   can scroll horizontally within themselves instead of forcing their own
   min-content width onto ancestors — on narrow viewports a wide table's
   intrinsic width otherwise pushes .container/.body wider than the actual
   device width, since `width:100%` on a table never shrinks below its
   content's min-content size. */
/* scrollbar-width/-ms-overflow-style/::-webkit-scrollbar hide the native
   scrollbar (Android Chrome draws a persistent one for overflow:auto) so
   only the explicit .table-scroll-bar below shows — without this, Android
   showed both its own native bar and ours stacked together. */
.table-scroll{ overflow-x:auto; -webkit-overflow-scrolling:touch; scrollbar-width:none; -ms-overflow-style:none; }
.table-scroll::-webkit-scrollbar{ display:none; }
.data-table{ width:100%; border-collapse:collapse; font-size:14.5px; }
/* Scroll-position indicator for a .table-scroll — mobile only. Native
   scrollbar visibility for an overflow:auto element differs by platform
   (Android Chrome draws a persistent thin scrollbar; iOS Safari shows none
   at rest), so a wide table read as "scrollable" on one device and not the
   other with identical CSS. This bar is drawn explicitly (JS-driven width/
   position, see main.js) so both platforms get the same visible affordance. */
.table-scroll-bar{ display:none; }
@media (max-width:760px){
  .table-scroll-bar{
    display:block; position:relative; height:4px; background:var(--line);
    border-radius:999px; margin-top:10px; overflow:hidden;
  }
  .table-scroll-thumb{ position:absolute; top:0; left:0; height:100%; background:var(--red); border-radius:999px; }
  /* Tapping a card to scroll its table triggers :hover on touch devices
     (and it can stick until something else is tapped) — the lift/shadow
     reads as an unwanted "pressed" glitch here, so it's switched off for
     table cards specifically on mobile; desktop mouse-hover is untouched. */
  .card:has(.data-table):hover{ box-shadow:none; transform:none; border-color:var(--line); }
}
.data-table th, .data-table td{ padding:14px 18px; border-bottom:1px solid var(--line); text-align:left; }
.data-table thead th{ background:var(--surface-2); color:var(--text); font-weight:600; font-size:12.5px; letter-spacing:.05em; text-transform:uppercase; }
.data-table thead th:first-child{ border-top-left-radius:8px; }
.data-table thead th:last-child{ border-top-right-radius:8px; }
.data-table tbody tr:hover{ background:rgba(0,0,0,.03); }
.data-table tbody tr:last-child td{ font-weight:700; color:var(--red-soft); border-bottom:none; }

/* Locations — one wide card per row: dark photo+text on the left, map on the right */
.locations-list{ display:flex; flex-direction:column; gap:24px; }
.location-card{
  display:flex; border:1px solid var(--line); border-radius:var(--radius-lg); overflow:hidden;
  background:var(--surface); height:320px;
}
.location-card .map-frame{ flex:0 0 50%; height:100%; background:var(--bg-2); position:relative; }
/* Korean addresses show a Naver embed in KO and a Google embed in EN (the US
   branch is Google either way, so it doesn't need this split). Each language's
   badge+iframe pair is wrapped in its own absolutely-positioned, inset:0 box —
   iframe's height:100% needs a sized containing block, and [lang] display:none
   alone wouldn't give the inactive one one to size against. */
.location-card .map-frame .map-lang{ position:absolute; inset:0; }
.location-card .map-frame iframe{ width:100%; height:100%; border:0; filter:grayscale(.1); }
.location-card .map-badge{
  position:absolute; top:12px; left:12px; z-index:2; background:var(--surface-2); color:var(--text);
  font-size:11px; font-weight:700; letter-spacing:.06em; padding:6px 12px; border-radius:999px; text-transform:uppercase;
  border:1px solid var(--line);
}
.location-card .map-badge.google{ background:var(--red); border-color:var(--red); color:#fff; }

/* KO map-lang used to embed Naver Map (map.naver.com/p/search/...), but Naver
   locked that URL down with `X-Frame-Options: DENY` for every user agent
   (verified via curl with a desktop Chrome UA) — a real embed isn't possible
   with it anymore, and a static-image-plus-link fallback wasn't acceptable
   (the client wants an actual live, interactive map, not a picture). Kakao
   Map's own page (map.kakao.com/?urlX=...&urlY=...) sends no such header and
   works as a genuine pannable/zoomable embed with no API key needed — found
   by geocoding each address in Kakao Map's search, confirming it correctly
   resolves to this company's real listing, and reading the resulting urlX/
   urlY (Korean TM / WCONGNAMUL coordinates) off Kakao's own network request
   for that search. KO now iframes that directly, same as EN's Google embed. */

/* Darkened facility photo with the address/facilities text overlaid on top */
.loc-photo{ position:relative; flex:0 0 50%; height:100%; overflow:hidden; background:#2c2a28; }
.loc-photo img{ width:100%; height:100%; object-fit:cover; filter:brightness(.78) saturate(.9); }
.loc-photo::after{
  content:""; position:absolute; inset:0;
  background:linear-gradient(180deg, rgba(10,9,8,.08) 0%, rgba(10,9,8,.28) 45%, rgba(10,9,8,.68) 100%);
}
.loc-photo.no-photo{ background:#2c2a28; }
.loc-photo-overlay{
  position:absolute; inset:0; z-index:2; padding:24px; display:flex; flex-direction:column; justify-content:space-between;
}
.loc-photo-overlay h3{
  font-size:17px; font-weight:700; margin-bottom:8px; display:flex; align-items:center; gap:8px;
  color:#fff; text-shadow:0 1px 8px rgba(0,0,0,.6);
}
.loc-photo-overlay .addr{ font-size:14px; color:rgba(255,255,255,.88); margin-bottom:14px; text-shadow:0 1px 6px rgba(0,0,0,.6); }
.loc-photo-overlay .facilities{ display:flex; flex-wrap:wrap; gap:8px; }
.loc-photo-overlay .note{ margin-top:12px; font-size:12.5px; color:rgba(255,255,255,.7); text-shadow:0 1px 6px rgba(0,0,0,.6); }
.loc-photo-overlay .tag, .loc-photo-overlay .tag.tag--red{
  background:rgba(255,255,255,.16); backdrop-filter:blur(6px); color:#fff; border:1px solid rgba(255,255,255,.30);
}

@media (max-width:760px){
  .location-card{ flex-direction:column; height:auto; }
  .location-card .loc-photo, .location-card .map-frame{ flex:none; width:100%; height:auto; aspect-ratio:16/10; }
  .location-card .map-badge{ display:none; }
  /* Mobile: keep only the title + address, drop the facility pill tags
     (and any trailing distance note) so the photo overlay isn't crowded. */
  .location-card .loc-bottom{ display:none; }
}

/* Process line (home page one-stop teaser) */
.process-line{ display:flex; align-items:flex-start; position:relative; gap:12px; }
.process-line::before{
  content:""; position:absolute; left:0; right:0; top:50%; height:2px; background:var(--red);
  transform:translateY(-50%); z-index:0;
}
.process-card{
  flex:1; min-width:0; aspect-ratio:1/1; background:var(--icon-bg); border-radius:var(--radius-lg);
  padding:16px; text-align:center; position:relative; z-index:1;
  display:flex; flex-direction:column; align-items:center; justify-content:center;
}
/* Outline fade on hover: same color/thickness as the .process-line connector
   (var(--red), 2px), traced right at the card's own edge and faded in via
   opacity rather than toggled instantly. */
.process-card::after{
  content:""; position:absolute; inset:0; border-radius:inherit;
  border:2px solid var(--red); opacity:0; transition:opacity .25s var(--ease); pointer-events:none;
}
.process-card:hover::after{ opacity:1; }
.process-card .pc-label{ font-size:13.5px; font-weight:700; white-space:nowrap; color:var(--text); margin:0 0 14px; }
.process-card .pc-label .pc-num{ color:var(--red-soft); margin-right:3px; }
.process-card .pc-icon{
  width:72px; height:72px; margin:0 auto; border-radius:16px; overflow:hidden; background:var(--icon-bg);
  display:flex; align-items:center; justify-content:center;
}
.process-card .pc-icon img{ width:100%; height:100%; object-fit:cover; display:block; transition:transform .3s var(--ease); }
.process-card:hover .pc-icon img{ transform:scale(1.08); }
@media (max-width:900px){
  .process-line{ flex-wrap:wrap; row-gap:16px; }
  .process-line::before{ display:none; }
  .process-card{ flex:0 0 calc(33.33% - 8px); }
}
@media (max-width:560px){
  .process-card{ flex:0 0 calc(50% - 6px); }
}

/* Process flow row */
.flow-row{ display:flex; align-items:flex-start; gap:0; position:relative; }
.flow-node{ flex:1; display:flex; flex-direction:column; align-items:center; text-align:center; position:relative; padding:0 6px; }
.flow-node .fn-circle{
  width:64px; height:64px; border-radius:50%; background:var(--surface-2); color:var(--text);
  display:flex; align-items:center; justify-content:center; font-weight:700; font-size:19px; margin-bottom:14px;
  position:relative; z-index:2; border:3px solid var(--bg); box-shadow:0 0 0 1px var(--line);
  transition:background .2s var(--ease), transform .2s var(--ease);
}
.flow-node:hover .fn-circle{ background:var(--red); color:#fff; transform:scale(1.06); }
.flow-node .fn-label{ font-size:13.5px; font-weight:600; color:var(--text); }
.flow-node .fn-sub{ font-size:11.5px; color:var(--text-dim); margin-top:2px; }
.flow-row::before{
  content:""; position:absolute; top:32px; left:8%; right:8%; height:2px; background:var(--line); z-index:1;
}
@media (max-width:900px){
  .flow-row{ flex-wrap:wrap; row-gap:32px; }
  .flow-row::before{ display:none; }
  .flow-node{ flex:0 0 33.33%; }
}
@media (max-width:560px){
  .flow-node{ flex:0 0 50%; }
}

/* Diagram wrapper */
.diagram{ background:var(--surface); border:1px solid var(--line); border-radius:var(--radius-lg); padding:36px; }
.diagram svg{ width:100%; height:auto; display:block; }
.diagram-caption{ margin-top:16px; font-size:13px; color:var(--text-dim); text-align:center; }

/* Carbon-neutrality roadmap graph: the trend line "draws" left-to-right via
   stroke-dashoffset (dasharray/offset = the polyline's own total length —
   485.91, i.e. the three segments 20,30->180,42->180,42->340,80->340,80-
   >500,98 summed — rounded to 486), while each point marker and value
   callout fades/scales in with a transition-delay matched to how far along
   that 486-unit length the line has to travel to reach it (0s / .46s / .94s
   / 1.4s for the 4 points), so they visually "light up" right as the drawing
   line arrives instead of all appearing at once or independently timed. */
.roadmap-line{
  stroke-dasharray:486; stroke-dashoffset:486;
  transition:stroke-dashoffset 1.4s cubic-bezier(.16,1,.3,1);
}
.roadmap-graph.is-visible .roadmap-line{ stroke-dashoffset:0; }
.roadmap-point{
  opacity:0; transform:scale(.4); transform-box:fill-box; transform-origin:center;
  transition:opacity .3s ease, transform .3s ease;
}
.roadmap-graph.is-visible .roadmap-point{ opacity:1; transform:scale(1); }
.roadmap-graph.is-visible .roadmap-point:nth-of-type(1){ transition-delay:0s; }
.roadmap-graph.is-visible .roadmap-point:nth-of-type(2){ transition-delay:.46s; }
.roadmap-graph.is-visible .roadmap-point:nth-of-type(3){ transition-delay:.94s; }
.roadmap-graph.is-visible .roadmap-point:nth-of-type(4){ transition-delay:1.4s; }
.roadmap-value{ opacity:0; transition:opacity .4s ease; }
.roadmap-graph.is-visible .roadmap-value{ opacity:1; }
.roadmap-graph.is-visible .roadmap-value--start{ transition-delay:0s; }
.roadmap-graph.is-visible .roadmap-value--end{ transition-delay:1.3s; }
/* POP SYSTEM diagram: the desktop SVG's 900px-wide, 4-across viewBox scales
   its 13px text down to near-illegibility once squeezed into a phone's
   width, so mobile gets a separate vertical redraw (.diagram-mobile,
   defined alongside .diagram-desktop in production-process.html) instead
   of the same layout just scaled smaller. Selectors here match `.diagram
   svg`'s own specificity (class+type) — a plain `.diagram-mobile{...}` rule
   loses to `.diagram svg{display:block}` above regardless of source order. */
.diagram svg.diagram-mobile{ display:none; }
@media (max-width:760px){
  .diagram{ padding:20px; }
  .diagram svg.diagram-desktop{ display:none; }
  .diagram svg.diagram-mobile{ display:block; }
}

/* One-Stop process loop: 7 pill cards arranged evenly around a circle, sitting on
   top of (and breaking up) a single dashed ring. The pills themselves
   (.loop-item/.loop-icon/.loop-label) are unchanged. Circulation is expressed by the
   ring's dash color pulsing gray->orange->gray on an infinite loop. */
.loop-diagram{ position:relative; width:100%; max-width:960px; margin:0 auto; }
/* 624px = 480px * 1.3. The 7 pills sit at fixed % positions around this
   circle, so their center-to-center spacing scales up right along with the
   circle — enlarging the pills (below) without enlarging this would collide
   them into their neighbors (see the .loop-item comment on why spacing is
   tight by design). */
.loop-diagram--circle{ max-width:624px; aspect-ratio:1/1; }
.loop-diagram-lines{ position:absolute; inset:0; width:100%; height:100%; z-index:1; }
/* Gray track stays put; the orange ring grows on top of it, starting at 01 (rotated
   -90deg so the path's default 3-o'clock start becomes 12-o'clock) and sweeping
   clockwise through 02..07 before snapping back to empty and looping. */
.circle-track{ fill:none; stroke-width:2.5; stroke-dasharray:14 12; stroke-linecap:round; stroke:var(--text-dim); }
.circle-fill{
  fill:none; stroke-width:2.5; stroke-linecap:round; stroke:var(--red);
  stroke-dasharray:1885; stroke-dashoffset:1885;
  animation:circleFill 8s linear infinite;
}
@keyframes circleFill{
  0%{ stroke-dashoffset:1885; }
  85%{ stroke-dashoffset:0; }
  100%{ stroke-dashoffset:0; }
}

/* Each pill's outline lights up orange the instant the sweep above passes its
   position (7 separate keyframes since each pill flips at a different point in the
   shared 8s cycle — the circumference is split into 7 equal 1/7 spans, matching
   01..07), then reverts to gray in sync with the ring's own reset. */
.loop-diagram--circle .loop-node:nth-of-type(1) .loop-item{ animation:pillLit1 8s linear infinite; }
.loop-diagram--circle .loop-node:nth-of-type(2) .loop-item{ animation:pillLit2 8s linear infinite; }
.loop-diagram--circle .loop-node:nth-of-type(3) .loop-item{ animation:pillLit3 8s linear infinite; }
.loop-diagram--circle .loop-node:nth-of-type(4) .loop-item{ animation:pillLit4 8s linear infinite; }
.loop-diagram--circle .loop-node:nth-of-type(5) .loop-item{ animation:pillLit5 8s linear infinite; }
.loop-diagram--circle .loop-node:nth-of-type(6) .loop-item{ animation:pillLit6 8s linear infinite; }
.loop-diagram--circle .loop-node:nth-of-type(7) .loop-item{ animation:pillLit7 8s linear infinite; }
@keyframes pillLit1{ 0%,1%{ border-color:var(--text-dim); } 1.01%,99.9%{ border-color:var(--red); } 100%{ border-color:var(--text-dim); } }
@keyframes pillLit2{ 0%,12.14%{ border-color:var(--text-dim); } 12.15%,99.9%{ border-color:var(--red); } 100%{ border-color:var(--text-dim); } }
@keyframes pillLit3{ 0%,24.29%{ border-color:var(--text-dim); } 24.3%,99.9%{ border-color:var(--red); } 100%{ border-color:var(--text-dim); } }
@keyframes pillLit4{ 0%,36.43%{ border-color:var(--text-dim); } 36.44%,99.9%{ border-color:var(--red); } 100%{ border-color:var(--text-dim); } }
@keyframes pillLit5{ 0%,48.57%{ border-color:var(--text-dim); } 48.58%,99.9%{ border-color:var(--red); } 100%{ border-color:var(--text-dim); } }
@keyframes pillLit6{ 0%,60.71%{ border-color:var(--text-dim); } 60.72%,99.9%{ border-color:var(--red); } 100%{ border-color:var(--text-dim); } }
@keyframes pillLit7{ 0%,72.86%{ border-color:var(--text-dim); } 72.87%,99.9%{ border-color:var(--red); } 100%{ border-color:var(--text-dim); } }
@media (prefers-reduced-motion: reduce){
  .circle-fill{ animation:none; stroke-dashoffset:1885; }
  .loop-diagram--circle .loop-item{ animation:none; }
}
.loop-node{ position:absolute; z-index:2; transform:translate(-50%,-50%); }
/* 7 points evenly spaced on a circle always have IDENTICAL center-to-center spacing
   (2*R*sin(pi/7)) — so a pill only overlaps its neighbor if its own width grows past
   that shared gap. Number + label stay on one line (no wrap), so the pills are kept
   compact (small icon, tight padding, single-digit numbers) to fit every language's
   longest label within that shared gap without colliding.
   Sizes below are the originals * 1.3 (36->47, 6/12->8/16, 7->9, 11->14) — the
   circle above was scaled by the same 1.3 so the gap these pills need to fit
   inside grew by exactly as much. */
.loop-item{
  display:flex; align-items:center; gap:9px;
  /* border-width matched to the ring's rendered stroke-width: the SVG's
     stroke-width:2.5 is in viewBox units (0 0 800 800), which at this
     diagram's ~624px rendered size scales to ~1.95px on screen — 2px is the
     closest whole-pixel border-width to that. */
  padding:8px 16px 8px 8px; border-radius:999px; background:var(--surface); border:2px solid var(--text-dim);
  white-space:nowrap;
}
.loop-icon{
  width:47px; height:47px; border-radius:50%; overflow:hidden; background:var(--icon-bg);
  flex-shrink:0; display:flex; align-items:center; justify-content:center;
}
.loop-icon img{ width:100%; height:100%; object-fit:cover; }
.loop-icon--inspect svg{ width:26px; height:26px; }
.loop-label{ font-size:14px; font-weight:700; color:var(--text); white-space:nowrap; }
@media (max-width:760px){
  .loop-item{ padding:5px 10px 5px 5px; gap:5px; }
  .loop-icon{ width:26px; height:26px; }
  .loop-label{ font-size:9px; }
}
@media (max-width:560px){
  .loop-diagram--circle{ max-width:420px; }
  .loop-label{ font-size:8px; }
}

/* Triangular cyclic process diagram (Development card) — big circles with the
   number + label set inside. The 3 nodes sit 120deg apart on a shared circle
   (a true equilateral layout), and each connecting arc is a real segment of that
   same circle (SVG "A" command, constant radius) — not an approximated curve —
   so the lines read as perfectly round, not distorted.
   Line effect modeled on taxo.ai/solutions' step-04 connector lines: a static
   dim track (.tri-track, one path, unanimated) plus a short bright "comet" dash
   (stroke-dasharray:"40 100" against a pathLength-normalized 0-100 path, same
   ratio taxo.ai uses) that sweeps once from just-before-the-start to
   just-past-the-end. Each arc is now its OWN <path> (tri-fill-1/2/3, each with
   pathLength="100") rather than one path with 3 M-separated subpaths sharing a
   single dasharray/dashoffset — that older single-path version looked like all
   3 arcs recolored simultaneously because stroke-dasharray/dashoffset restarts
   at every subpath's own moveto, so all three subpaths animated in lockstep off
   the same shared offset instead of running in sequence. Giving each arc its
   own path + its own keyframes (each spanning the full 4.5s loop but with the
   sweep positioned in only one third of it) is what makes 1->2, 2->3, 3->1
   actually happen one after another instead of all at once. */
.tri-cycle{ position:relative; width:100%; max-width:340px; aspect-ratio:1/1; margin:20px auto 0; }
.tri-cycle-lines{ position:absolute; inset:0; width:100%; height:100%; }
.tri-track{ fill:none; stroke-width:3; stroke-linecap:round; stroke:#9a968f; stroke-dasharray:10 8; }
.tri-fill{ fill:none; stroke-width:3; stroke-linecap:round; stroke:var(--red); stroke-dasharray:40 100; }
.tri-fill-1{ animation:triSweep1 4.5s linear infinite; }
.tri-fill-2{ animation:triSweep2 4.5s linear infinite; }
.tri-fill-3{ animation:triSweep3 4.5s linear infinite; }
/* Each arc's own local timeline: dashoffset 40 parks the comet just before the
   arc's start (hidden); -100 (40 minus the 140 dash+gap period) parks it just
   past the end (also hidden) — the same "offset = dash length" / "offset =
   dash length − period" pair taxo.ai's own lines use. Holding at 40 before a
   segment's turn and at -100 after it is what keeps the other two arcs blank
   while one is mid-sweep. */
@keyframes triSweep1{
  0%{ stroke-dashoffset:40; }
  33.33%{ stroke-dashoffset:-100; }
  100%{ stroke-dashoffset:-100; }
}
@keyframes triSweep2{
  0%, 33.33%{ stroke-dashoffset:40; }
  66.67%{ stroke-dashoffset:-100; }
  100%{ stroke-dashoffset:-100; }
}
@keyframes triSweep3{
  0%, 66.67%{ stroke-dashoffset:40; }
  100%{ stroke-dashoffset:-100; }
}
/* Sized as % of .tri-cycle (a definite width) rather than fixed px, so the circles
   always scale with the container instead of overflowing/overlapping in narrower
   card widths (e.g. the 2-up grid before it collapses to 1 column). */
.tri-node{ position:absolute; transform:translate(-50%,-50%); z-index:2; width:34.21%; }
.tri-circle{
  width:100%; aspect-ratio:1/1; border-radius:50%; background:var(--surface-2); border:2px solid var(--text-dim);
  display:flex; flex-direction:column; align-items:center; justify-content:center; text-align:center; gap:4px;
}
.tri-node:nth-of-type(1) .tri-circle{ animation:triNodeLit1 4.5s linear infinite; }
.tri-node:nth-of-type(2) .tri-circle{ animation:triNodeLit2 4.5s linear infinite; }
.tri-node:nth-of-type(3) .tri-circle{ animation:triNodeLit3 4.5s linear infinite; }
@keyframes triNodeLit1{ 0%,1%{ border-color:var(--text-dim); } 1.01%,99.9%{ border-color:var(--red); } 100%{ border-color:var(--text-dim); } }
@keyframes triNodeLit2{ 0%,33.33%{ border-color:var(--text-dim); } 33.34%,99.9%{ border-color:var(--red); } 100%{ border-color:var(--text-dim); } }
@keyframes triNodeLit3{ 0%,66.67%{ border-color:var(--text-dim); } 66.68%,99.9%{ border-color:var(--red); } 100%{ border-color:var(--text-dim); } }
@media (prefers-reduced-motion: reduce){
  .tri-fill-1, .tri-fill-2, .tri-fill-3{ animation:none; opacity:0; }
  .tri-circle{ animation:none; }
}
.tri-circle .tri-num{ font-size:13px; font-weight:600; color:var(--text); }
.tri-circle .tri-text{ font-size:11px; font-weight:500; color:var(--text); line-height:1.35; padding:0 10%; }
@media (min-width:900px){
  .tri-circle .tri-num{ font-size:16px; }
  .tri-circle .tri-text{ font-size:13.5px; }
}

/* Equipment / spec list */
.spec-list{ display:grid; grid-template-columns:repeat(2,1fr); gap:14px; }
.spec-item{ display:flex; gap:12px; align-items:center; padding:16px 18px; background:var(--surface); border-radius:var(--radius-md); border:1px solid var(--line); }
.spec-item .dot{ width:8px; height:8px; border-radius:50%; background:var(--red-soft); flex-shrink:0; }
.spec-item p{ font-size:14px; color:var(--text-mid); }

/* R&D "Laboratory & Testing" cards: point image + text, 3-up, filling the section width */
.lab-card{ text-align:center; }
.lab-card-img{
  aspect-ratio:4/3; border-radius:var(--radius-md); overflow:hidden; background:var(--bg-2);
  margin-bottom:18px;
}
.lab-card-img img{ width:100%; height:100%; object-fit:cover; }
.lab-card p{ font-size:14px; color:var(--text-mid); text-align:left; }

/* Test-equipment inventory: a compact multi-column grid (not a tall single list) so
   17 items stay a short, wide block instead of a long scroll. Sits on a true dark
   section (.section--dark-accent) alongside the photo carousel below it, so the
   section-head text needs its own light-on-dark colors — the equip-row cards keep
   their own white background regardless, so they don't need any override. */
.section--dark-accent .section-kicker{ color:var(--amber); }
.section--dark-accent .section-kicker::before{ background:var(--amber); }
.section--dark-accent .section-title{ color:#f7f4f0; }
.section--dark-accent .section-desc{ color:rgba(255,255,255,.65); }
.equip-grid{ display:grid; grid-template-columns:repeat(4,1fr); gap:10px; }
/* Matches the home page's 4-up stat cards (.stat-figure/.stat-lead on the dark
   .section--dark-accent band) in structure and palette, but with the emphasis
   inverted: the number is the small label here, the equipment name is the content
   that needs to read clearly, so it gets the size and weight instead. */
.equip-row{
  display:flex; flex-direction:column; gap:8px; padding:20px 18px;
  background:transparent; border:1px solid rgba(255,255,255,.14); border-radius:var(--radius-md);
}
.equip-row .num{ font-size:11px; font-weight:700; color:var(--red); letter-spacing:.04em; }
.equip-row .name{ font-size:16px; font-weight:600; color:#f7f4f0; line-height:1.4; }
@media (max-width:900px){ .equip-grid{ grid-template-columns:repeat(2,1fr); } }
@media (max-width:560px){ .equip-grid{ grid-template-columns:1fr; } }

/* Peek carousel: center card full width, neighbors cropped at the edges, auto-advancing
   and looping (see main.js). Side padding = (100% - card width)/2 so scroll-snap-align:
   center lands each card dead-center with equal peek on both sides. A mask-image fades
   the side cards out at the edges instead of the hard clip a plain overflow:hidden gives —
   the crop is still there (mask still needs a box to fade within), it just now dissolves
   into the section background instead of cutting sharply. */
.peek-carousel{
  overflow:hidden;
  -webkit-mask-image:linear-gradient(90deg, transparent, #000 18%, #000 82%, transparent);
  mask-image:linear-gradient(90deg, transparent, #000 18%, #000 82%, transparent);
}
.peek-track{
  display:flex; gap:20px; overflow-x:auto; scroll-snap-type:x mandatory; scroll-behavior:smooth;
  padding:4px 20% 14px;
  scrollbar-width:none; -ms-overflow-style:none; /* the custom .peek-nav bar below replaces this */
}
.peek-track::-webkit-scrollbar{ display:none; }
.peek-card{
  position:relative; flex:0 0 60%; scroll-snap-align:center; margin:0; aspect-ratio:4/3;
  border-radius:var(--radius-lg); overflow:hidden; background:var(--bg-2);
}
.peek-card img{ width:100%; height:100%; object-fit:cover; }
.peek-card::after{ content:""; position:absolute; inset:0; background:rgba(0,0,0,.14); pointer-events:none; }
@media (max-width:760px){
  .peek-track{ padding:4px 12% 14px; gap:14px; }
  .peek-card{ flex-basis:76%; }
}

/* Carousel progress nav: thin light-gray track with a short, noticeably *thicker*
   orange pill riding on top of it (not just filling the track's own height — the
   track stays visible above/below the pill). The visual line is only 3px, so .peek-nav
   itself is a much taller invisible hit box (drawn via ::before instead of its own
   background) — otherwise the click/drag target would be a near-impossible 3px sliver.
   Width set in JS = 1/cardCount of the track. */
.peek-nav{ position:relative; max-width:260px; height:24px; margin:16px auto 0; cursor:pointer; touch-action:none; }
.peek-nav::before{
  content:""; position:absolute; top:50%; left:0; right:0; height:3px; transform:translateY(-50%);
  border-radius:999px; background:rgba(255,255,255,.22); pointer-events:none;
}
.peek-nav-thumb{
  position:absolute; top:50%; left:0; height:7px; transform:translateY(-50%);
  background:var(--red); border-radius:999px; pointer-events:none;
  transition:left .5s cubic-bezier(.2,.7,.15,1);
}

/* Chips (grievance-type tags on the Sustainable Management page). Explicit
   rows rather than one flex-wrap flowing by width — that would reflow to
   whatever fits per line at a given viewport width instead of a fixed
   grouping. KO and EN each get their own .chip-lang-group with its own row
   layout (2/3 for KO, 2/2/1 for EN) since a literal translation doesn't
   groups the same way in both languages; [lang] display:none (see the
   html.lang-ko/lang-en rules near the top of this file) hides the other. */
.chip-grid{ display:flex; flex-direction:column; }
.chip-lang-group{ display:flex; flex-direction:column; gap:12px; }
.chip-row{ display:flex; flex-wrap:wrap; gap:12px; }
.chip{ padding:14px 22px; border:1px solid var(--line); border-radius:10px; font-weight:600; font-size:14.5px; color:var(--text-mid); background:var(--surface); transition:.2s; }
.chip:hover{ border-color:var(--red-soft); background:rgba(255,90,30,.12); color:var(--text); }

/* Customer footprint map (Certifications page) — looping background video,
   replaces the old static customers.webp (which had the 8-countries/OEM/
   Tier-1 stats baked into the image itself; those are now the 3 real cards
   below, .footprint-stats, so they can be language-toggled and updated
   independently of the artwork). Tried nesting the cards inside the video's
   own frame on a background sampled from the video's edge pixels, but the
   video's bottom-edge color isn't uniform enough for any single background
   (flat color or edge-pixel strip) to read as a seamless match, so the cards
   are back outside the frame as a separate row below it. */
.customer-map{ margin:0; border-radius:var(--radius-lg); overflow:hidden; border:1px solid var(--line); background:#fff; }
.customer-map video{ width:100%; height:auto; display:block; }
.footprint-stats{ margin-top:20px; }
.footprint-stat{ display:flex; flex-direction:column; align-items:center; text-align:center; gap:12px; }
/* This is a purely informational stat readout, not a clickable/navigable
   card, so the sitewide .card lift+shadow hover (border-color change,
   translateY, box-shadow) is switched off here. */
.footprint-stat:hover{ box-shadow:none; transform:none; border-color:var(--line); }
.footprint-stat-icon{ width:34px; height:34px; stroke:var(--red); }
.footprint-stat-num{ font-size:20px; font-weight:700; color:var(--text); }
.footprint-stat-num b{ color:var(--red); font-weight:800; }

/* Customer logo marquee (Certifications page) — auto-scrolling strip of
   transparent-background partner/OEM logos between the lead sentence and the
   customer-footprint map, modeled on side-kick.se's "Hjältar vi jobbar med"
   logo carousel: a single flex row containing the logo set twice back-to-back,
   translated by exactly -50% of its own (self-sized) width on a linear-infinite
   loop — since the two halves are identical, the loop point is seamless with
   no visible jump or reset. Edge fade via mask-image (same technique already
   used for the products-page carousels) hides logos entering/leaving abruptly. */
.logo-marquee{
  position:relative; overflow:hidden; margin:65px 0;
  -webkit-mask-image:linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
  mask-image:linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
}
.logo-marquee-track{
  display:flex; align-items:center; gap:56px; width:max-content;
  animation:logo-marquee-scroll 50s linear infinite;
  cursor:grab; user-select:none; touch-action:pan-y;
}
.logo-marquee-track:active{ cursor:grabbing; }
.logo-marquee-item{ height:40px; width:auto; flex-shrink:0; object-fit:contain; -webkit-user-drag:none; }
/* Circular/badge-style logos (Vauxhall, Opel, Nissan) read as visibly smaller
   than the rest even at the same box height — a circle inscribed in a ~1:1
   box has far less visual mass than a wordmark that spans the full height —
   so they get a deliberate size boost. Rivian and Lucid are boosted by the
   same +30% per explicit request (their own thin/wide marks, left as-is
   otherwise per an earlier instruction not to re-crop them). */
.logo-marquee-item--boost{ height:52px; }
/* GM sized up +20%, KGM/SM sized down -20%, per explicit request. */
.logo-marquee-item--lg20{ height:48px; }
.logo-marquee-item--sm20{ height:32px; }
@keyframes logo-marquee-scroll{
  from{ transform:translateX(0); }
  to{ transform:translateX(-50%); }
}
@media (prefers-reduced-motion:reduce){
  .logo-marquee-track{ animation:none; }
}
@media (max-width:760px){
  .logo-marquee-track{ gap:36px; }
  .logo-marquee-item{ height:30px; }
  .logo-marquee-item--boost{ height:39px; }
  .logo-marquee-item--lg20{ height:36px; }
  .logo-marquee-item--sm20{ height:24px; }
}

/* Reusable process photo card: spec points overlaid directly on the image, text
   anchored to the bottom. Darkened image + gradient + a translucent backing on each
   chip triple up to keep the text legible regardless of what's underneath. Used by
   both the Plating and Assembly/Packaging sections. */
.overlay-photo{ position:relative; margin:0; border-radius:var(--radius-lg); overflow:hidden; aspect-ratio:4/5; background:#2c2a28; }
/* Assembly's source photos are landscape (3:2), unlike Plating's — use this modifier
   to match the card shape to the actual image instead of forcing a portrait crop. */
.overlay-photo--wide{ aspect-ratio:3/2; }
.overlay-photo img{ width:100%; height:100%; object-fit:cover; filter:brightness(.85) saturate(.95); }
/* Plating section only: slow Ken Burns-style pan, like a camera drifting left-
   right across the frame. The image is rendered wider than its frame (135%)
   and overflow:hidden on .overlay-photo (above) clips it, so translateX slides
   the visible window across the photo. The theoretical max shift before the
   image's own edge would show is (135-100)/135 = ~25.9% — the keyframes only
   travel from -4% to -16%, well inside that range on both ends, so easing
   overshoot or subpixel rounding never exposes a cropped/empty edge. The two
   photos pan opposite directions and off-tempo (different duration + negative
   delay) so they don't move in lockstep. */
#plating .overlay-photo img{
  /* The site-wide `img{max-width:100%}` reset (near the top of this file)
     was silently clamping this back to 100% no matter what width said here —
     the image was never actually oversized, so panning it just dragged its
     own exact-fit edge into view. max-width:none is the actual fix. */
  max-width:none; width:135%; height:100%;
  animation:photoPanLR 17s ease-in-out infinite alternate;
}
#plating .overlay-photo:nth-of-type(2) img{
  animation-name:photoPanRL; animation-duration:15s; animation-delay:-4s;
}
@keyframes photoPanLR{ from{ transform:translateX(-4%); } to{ transform:translateX(-16%); } }
@keyframes photoPanRL{ from{ transform:translateX(-16%); } to{ transform:translateX(-4%); } }
@media (prefers-reduced-motion:reduce){
  #plating .overlay-photo img{ animation:none; }
}

/* MES section image: same Ken Burns pan as Plating, applied to the single
   media-card photo. .media-card .media img already has its own hover-zoom
   transform (scale on :hover) — a running CSS animation on the same property
   takes precedence over that static rule per the cascade, so the pan simply
   supersedes the hover-zoom here rather than fighting it. */
#mes .media-card .media img{
  max-width:none; width:135%; height:100%;
  animation:photoPanLR 17s ease-in-out infinite alternate;
}
@media (prefers-reduced-motion:reduce){
  #mes .media-card .media img{ animation:none; }
}
.overlay-photo::after{
  content:""; position:absolute; inset:0; pointer-events:none;
  background:linear-gradient(180deg, rgba(10,9,8,.05) 0%, rgba(10,9,8,.15) 55%, rgba(10,9,8,.45) 100%);
}
.overlay-photo-text{
  position:absolute; inset:0; z-index:2; padding:28px; display:flex; flex-direction:column; justify-content:flex-end; gap:14px;
}
.overlay-photo-text .spec-item{
  background:rgba(10,9,8,.68); backdrop-filter:blur(6px); border:1px solid rgba(255,255,255,.16);
}
.overlay-photo-text .spec-item .dot{ background:var(--red); }
.overlay-photo-text .spec-item p{ color:#fff; text-shadow:0 1px 6px rgba(0,0,0,.4); }
@media (max-width:760px){
  .overlay-photo{ aspect-ratio:4/5.5; }
  .overlay-photo--wide{ aspect-ratio:3/2; }
  .overlay-photo-text{ padding:18px; gap:10px; }
}

/* Compliance / contact box */
.info-box{ background:var(--surface); border:1px solid var(--line); border-radius:var(--radius-lg); padding:26px 28px; }
.info-box h4{ font-weight:700; font-size:15px; margin-bottom:10px; color:var(--text); }
.info-box .row{ display:flex; justify-content:space-between; font-size:14px; padding:8px 0; border-bottom:1px dashed var(--line); }
.info-box .row:last-child{ border-bottom:none; }
.info-box .row .k{ color:var(--text-dim); }
.info-box .row .v{ font-weight:600; color:var(--text); }

/* ==========================================================================
   Footer
   ========================================================================== */
.site-footer{ background:var(--bg); color:var(--text-mid); padding:80px 0 28px; border-top:1px solid var(--line-soft); }
.footer-top{ display:grid; grid-template-columns:1.4fr 1fr 1fr 1fr; gap:40px; padding-bottom:40px; border-bottom:1px solid rgba(0,0,0,.08); }
.footer-brand .brand{ margin-bottom:14px; }
.footer-brand p{ font-size:13.5px; color:var(--text-dim); max-width:280px; }
.site-footer h5{ color:var(--text); font-size:12.5px; font-weight:700; letter-spacing:.08em; text-transform:uppercase; margin-bottom:16px; }
.site-footer .addr-block{ font-size:13.5px; margin-bottom:14px; }
.site-footer .addr-block .label{ color:var(--red-soft); font-weight:600; display:block; margin-bottom:2px; }
.footer-links li{ margin-bottom:10px; }
.footer-links a{ font-size:13.5px; color:var(--text-mid); transition:color .15s; }
.footer-links a:hover{ color:var(--text); }
.footer-bottom{ display:flex; justify-content:space-between; align-items:center; padding-top:24px; font-size:12.5px; color:var(--text-dim); flex-wrap:wrap; gap:12px; }
.footer-bottom .ethics{ color:var(--text-dim); }

/* ==========================================================================
   Responsive
   ========================================================================== */
@media (max-width: 1024px){
  .grid-4{ grid-template-columns:repeat(2,1fr); }
  .grid-3{ grid-template-columns:repeat(2,1fr); }
  .grid-6{ grid-template-columns:repeat(3,1fr); }
  .photo-strip{ grid-template-columns:repeat(2,1fr); }
  .footer-top{ grid-template-columns:1fr 1fr; }
  .nav{ display:none; }
  .menu-toggle{ display:flex; }
  .brand{ margin-right:0; }
  .site-header .container{ justify-content:space-between; }
}
@media (max-width: 680px){
  .container{ padding:0 18px; }
  .grid-2, .grid-3, .grid-4{ grid-template-columns:1fr; }
  .grid-6{ grid-template-columns:repeat(2,1fr); }
  /* Ethics/Human Rights declaration: two <ul> columns stack into one column
     here, but the outer .grid's 28px gap (meant for column-to-column
     spacing) then lands between the two lists' last/first <li> — a
     different, larger gap than the 12px used between items within each
     list. Match it to 12px so the six items read as one evenly-spaced list. */
  .ethics-lists{ gap:12px; }
  /* Same seam-gap mismatch as .ethics-lists above, on the Carbon Neutrality
     governance task list (2 <ul>s, 4+3 items) — the outer .grid's 28px gap
     otherwise lands only between item 4 and item 5 once it stacks to one
     column on mobile. */
  .governance-tasks{ gap:12px; }
  .section{ padding:64px 0; }
  .section-title{ font-size:26px; }
  .footer-top{ grid-template-columns:1fr; }
  .spec-list{ grid-template-columns:1fr; }
  .hero{ min-height:580px; }
}

/* Mobile nav drawer */
.mobile-nav{
  position:fixed; inset:0; z-index:300; background:var(--bg); transform:translateX(100%);
  transition:transform .25s var(--ease); overflow-y:auto;
}
.mobile-nav.open{ transform:translateX(0); }
.mobile-nav .mn-head{ display:flex; justify-content:space-between; align-items:center; padding:20px 24px; border-bottom:1px solid var(--line); }
.mobile-nav .mn-close{ width:36px; height:36px; border-radius:8px; border:1px solid var(--line); background:none; font-size:18px; color:var(--text); }
.mobile-nav ul{ padding:12px; }
.mobile-nav li > a, .mobile-nav li > button{ display:flex; width:100%; justify-content:space-between; align-items:center; padding:16px 12px; font-size:16px; font-weight:600; color:var(--text); border-bottom:1px solid var(--line-soft); background:none; border-top:none; border-left:none; border-right:none; text-align:left; }
.mobile-nav .sub{ padding-left:16px; display:none; }
.mobile-nav .sub.open{ display:block; }
.mobile-nav .sub a{ font-size:14.5px; font-weight:500; color:var(--text-mid); padding:12px; }
.mobile-nav .sub-title{ font-size:11px; font-weight:700; letter-spacing:.08em; text-transform:uppercase; color:var(--red-soft); padding:16px 12px 6px; }
.mobile-nav .sub-title:first-child{ padding-top:6px; }

/* ==========================================================================
   Custom cursor — small circle that replaces the native pointer and inverts
   whatever color is underneath it (mix-blend-mode:difference), matching the
   reference cursor at one-of-one-ai.com. Only enabled on fine-pointer (mouse/
   trackpad) devices via the .has-custom-cursor class added in main.js, so
   touchscreens keep their normal (cursor-less) behavior untouched.
   ========================================================================== */
.has-custom-cursor, .has-custom-cursor *{ cursor:none !important; }
.cursor-dot{
  position:fixed; top:0; left:0; width:20px; height:20px; border-radius:50%;
  background:#fff; mix-blend-mode:difference; pointer-events:none; z-index:9999;
  opacity:0; transition:opacity .2s var(--ease), background-color .2s var(--ease);
}
/* The difference-blend inverts whatever's under the dot pixel-for-pixel —
   over a photo or flat color that's a clean invert, but over small nav text
   it inverts each letter's own strokes as the dot passes over them, which
   reads as flickering vertical-line noise (the letters' own vertical
   strokes). The dot itself should stay a circle here (not swap to a native
   pointer) — main.js just tags it .over-header, and this drops the blend
   mode to a plain translucent white circle for that stretch instead. */
.cursor-dot.is-active.over-header{
  mix-blend-mode:normal; background:rgba(255,255,255,.55);
}
.cursor-dot.is-active{ opacity:1; }

/* Floating moplat.io cross-promo badge (home page only, desktop only for
   now — see .float-badge in main.js for the scroll-direction and close-
   button wiring). Hidden by default; only the min-width:761px query turns
   it on, so it stays off on mobile until a mobile version is asked for. */
.float-badge{
  display:none;
  position:fixed; right:32px; bottom:32px; z-index:400;
  width:140px; height:140px;
  transition:transform .35s var(--ease);
}
@media (min-width:761px){
  .float-badge{ display:block; }
}
/* Nudges a little further in whichever direction the page is currently
   scrolling — down on scroll-down, up on scroll-up — rather than tracking
   scroll position 1:1 (it's fixed-position, not part of document flow). */
.float-badge.scroll-down{ transform:translateY(16px); }
.float-badge.scroll-up{ transform:translateY(-8px); }
.float-badge.is-closed{ display:none !important; }

/* rgba stops (not a flat opacity on the whole element) so the 15%
   transparency only affects the card's own surface — the text/logo/"Learn
   more" stay fully opaque and legible regardless of what's behind it.
   Flat, no shadow — matches the "ghost button" treatment of moplat.io's own
   LiTQ/LitCon360 nav buttons (a.btn.btn-red-ghost, inspected live on the
   moplat.io landing page: 0.8px tinted border, subtle tinted background,
   box-shadow:none). Border-radius stays 50%/circular here — the reference
   buttons are small rounded-rects, but this card carries a logo + multiple
   lines of text, so keeping the circle shape and only borrowing the flat/
   no-shadow surface treatment reads better than a tiny rectangle would. */
.float-badge-link{
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:6px;
  width:100%; height:100%; border-radius:50%; box-sizing:border-box;
  background:linear-gradient(135deg, rgba(255,90,30,.85), rgba(224,73,26,.85));
  border:0.8px solid rgba(255,255,255,.4);
  text-align:center; padding:16px;
  transition:transform .2s var(--ease), border-color .2s var(--ease);
}
.float-badge-link:hover{ transform:scale(1.06); border-color:rgba(255,255,255,.7); }
.float-badge-text{ color:#fff; font-size:11px; font-weight:700; line-height:1.32; letter-spacing:.01em; }
.float-badge-logo{ width:88px; height:auto; }
.float-badge-more{ color:rgba(255,255,255,.85); font-size:9.5px; font-weight:600; letter-spacing:.05em; text-transform:uppercase; margin-top:8px; }

.float-badge-close{
  position:absolute; top:-2px; right:-2px; z-index:1;
  width:24px; height:24px; border-radius:50%; border:none; padding:0;
  background:#fff; color:var(--text-mid); box-shadow:var(--shadow-sm);
  display:flex; align-items:center; justify-content:center; cursor:pointer;
  transition:transform .15s var(--ease), background .15s var(--ease), color .15s var(--ease);
}
.float-badge-close:hover{ background:var(--surface-2); color:var(--text); transform:scale(1.12); }

/* Standalone policy/charter document pages — opened via target="_blank" from
   the 6 policy buttons on the Sustainable Management page. Presented
   bilingually in sequence (a Korean paragraph immediately followed by its
   English translation), matching how the source Word documents themselves
   are written, rather than the site's usual [lang] toggle — there's no
   single "current language" for a reference document opened in its own tab,
   and the source material doesn't separate KO/EN that way to begin with.
   Deliberately skips the site's nav/footer partials and JS (i18n.js/main.js)
   — this is a plain, self-contained reader page, not part of the main site
   chrome, so nothing here depends on scroll-reveal or the language toggle. */
.policy-page{ background:var(--bg); min-height:100vh; }
.policy-topbar{ padding:20px 24px; border-bottom:1px solid var(--line); background:var(--surface); }
.policy-topbar a{ font-weight:800; font-size:15px; color:var(--text); text-decoration:none; letter-spacing:-.01em; }
.policy-doc{ max-width:760px; margin:0 auto; padding:56px 24px 96px; }
.policy-doc h1{ font-size:28px; font-weight:800; color:var(--text); margin-bottom:8px; }
.policy-doc .policy-subtitle{ font-size:15px; color:var(--text-mid); margin-bottom:40px; }
.policy-doc h2{ font-size:19px; font-weight:800; color:var(--text); margin:44px 0 18px; padding-top:28px; border-top:1px solid var(--line); }
.policy-doc h2:first-of-type{ padding-top:0; border-top:none; }
.policy-doc h3{ font-size:15.5px; font-weight:700; color:var(--red-soft); margin:26px 0 10px; }
.policy-doc p{ font-size:14.5px; line-height:1.7; margin:0; }
.policy-doc p.doc-ko{ color:var(--text); margin-top:16px; }
.policy-doc p.doc-en{ color:var(--text-mid); margin-top:4px; }
.policy-doc table.data-table{ margin:6px 0 16px; }
.policy-placeholder{ text-align:center; padding:120px 24px; color:var(--text-dim); font-size:15px; }
