/* Theme tokens taken from the kiosk-media-solution dark theme
   (frontend/public/themes/dark.css) so the personal pages share one look. */
/* Theme resolution mirrors the locale: site.js stamps data-theme from the
   saved choice or the system preference; CSS only ever reads the attribute.
   (JS-off falls back to dark, same as JS-off i18n falls back to English.) */
:root {
  color-scheme: dark;
  --bg: #121212;
  /* Surface tiers: on a dark theme depth is carried by lightness, not shadow,
     so each elevation is a more-opaque white film over --bg. */
  --card: rgba(255, 255, 255, 0.06);
  --card-raised: rgba(255, 255, 255, 0.10);
  --inset: rgba(0, 0, 0, 0.2);
  --text: #e0e0e0;
  --muted: #aaa;
  --accent: #bb86fc;
  --accent-2: #d0a3ff;
  /* The accent as a bare triplet, for glow/shadow alphas: rgb(var(--accent-rgb) / .35). */
  --accent-rgb: 187 134 252;
  --on-accent: #000;
  --border: rgba(255, 255, 255, 0.12);
  /* Lit top edge (glass rim) stacked with a layered shadow ramp — offset and
     blur double each step at low alpha, which reads as real depth vs one flat
     shadow. */
  --rim: inset 0 1px 0 rgba(255, 255, 255, 0.10);
  --shadow:
    var(--rim),
    0 1px 2px rgba(0, 0, 0, 0.18),
    0 2px 4px rgba(0, 0, 0, 0.16),
    0 4px 8px rgba(0, 0, 0, 0.14),
    0 8px 16px rgba(0, 0, 0, 0.12),
    0 16px 32px rgba(0, 0, 0, 0.10);
  --shadow-raised:
    var(--rim),
    0 2px 4px rgba(0, 0, 0, 0.20),
    0 6px 12px rgba(0, 0, 0, 0.18),
    0 12px 24px rgba(0, 0, 0, 0.16),
    0 24px 48px rgba(0, 0, 0, 0.14);
  --card-opaque: rgb(24, 24, 28);
}

/* Light theme: same glass architecture, depth flipped - on light the film is
   white-on-white, so borders and shadows carry the depth instead of lightness.
   The accent deepens (the dark theme's #bb86fc washes out on white). */
:root[data-theme="light"] {
  color-scheme: light;
  --bg: #f4f2f7;
  --card: rgba(255, 255, 255, 0.55);
  --card-raised: rgba(255, 255, 255, 0.82);
  --inset: rgba(0, 0, 0, 0.06);
  --text: #201d26;
  --muted: #5b5765;
  --accent: #6b3fd4;
  --accent-2: #7d54dd;
  --accent-rgb: 107 63 212;
  --on-accent: #fff;
  --border: rgba(32, 29, 38, 0.14);
  --rim: inset 0 1px 0 rgba(255, 255, 255, 0.85);
  --card-opaque: #ffffff;
}
* { box-sizing: border-box; margin: 0; }
body {
  /* Two low-alpha radial "aurora" tints give the flat backdrop a purple mood;
     they sit above the photo but below the dark overlay so text contrast (the
     0.86–0.94 gradient) is untouched. --bg doubles as the load-time fallback. */
  background:
    radial-gradient(at 18% 18%, hsl(265 60% 45% / 0.18) 0, transparent 50%),
    radial-gradient(at 82% 30%, hsl(280 55% 50% / 0.14) 0, transparent 55%),
    url("assets/bg.webp") center / cover no-repeat,
    var(--bg);
  background-attachment: fixed;
  min-height: 100vh;
  color: var(--text);
  font: 16px/1.6 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  display: flex;
  justify-content: center;
  padding: 40px 16px;
}
/* The veil over the photo, as two stacked layers - dark and light - that
   crossfade by opacity on a theme flip. A gradient itself can't transition;
   two opacities can, and the page stays live the whole time (no snapshots,
   unlike a view transition, which hides the page from hit-testing). */
body::before,
body::after {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  transition: opacity 0.5s cubic-bezier(0.33, 1, 0.68, 1);
}
body::before {
  background: linear-gradient(rgba(18, 18, 18, 0.86), rgba(18, 18, 18, 0.94));
  opacity: 1;
}
body::after {
  background: linear-gradient(rgba(244, 242, 247, 0.88), rgba(244, 242, 247, 0.95));
  opacity: 0;
}
:root[data-theme="light"] body::before { opacity: 0; }
:root[data-theme="light"] body::after { opacity: 1; }
main { width: 100%; max-width: 760px; position: relative; z-index: 1; }
.switches {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 8px;
  margin-bottom: 12px;
}
.theme-switch {
  background: none;
  border: 1px solid var(--border);
  color: var(--muted);
  border-radius: 6px;
  padding: 4px 10px;
  cursor: pointer;
  font-size: 14px;
  line-height: 1.45; /* match the lang buttons' text line box */
}
.theme-switch:hover { border-color: var(--accent); color: var(--text); }
.lang-switch button {
  background: none;
  border: 1px solid var(--border);
  color: var(--muted);
  border-radius: 6px;
  padding: 4px 10px;
  cursor: pointer;
  font-size: 14px;
}
.lang-switch button.active {
  color: var(--text);
  border-color: var(--accent);
}
.card {
  background: var(--card);
  /* Real frosted glass: blur what's behind the card, saturate to counter the
     muddiness blur adds. -webkit- must come first and take literal values
     (Safari rejects var() here). Fallbacks below for older/opt-out browsers. */
  -webkit-backdrop-filter: blur(16px) saturate(160%);
  backdrop-filter: blur(16px) saturate(160%);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: var(--shadow);
  padding: 28px;
  margin-bottom: 20px;
}
.hero { display: flex; gap: 24px; align-items: center; flex-wrap: wrap; }
.avatar {
  width: 112px; height: 112px;
  border-radius: 50%;
  background: var(--accent);
  color: var(--on-accent);
  display: flex; align-items: center; justify-content: center;
  font-size: 40px; font-weight: 700;
  flex: 0 0 auto;
  overflow: hidden;
  /* It's a button now (opens the enlarged photo) — strip the native chrome. */
  padding: 0; border: none; cursor: zoom-in;
  box-shadow: var(--rim);
  transition: transform 200ms ease, box-shadow 200ms ease;
}
.avatar:hover { transform: scale(1.04); box-shadow: 0 8px 24px rgb(var(--accent-rgb) / 0.35); }
.avatar img { width: 100%; height: 100%; object-fit: cover; }

/* Enlarged-photo lightbox — native <dialog>, so Esc and focus-trap are free. */
.photo-modal {
  border: none; padding: 0; background: transparent;
  max-width: 90vw; max-height: 90vh;
  /* The global * { margin: 0 } reset wipes the UA margin: auto that centers
     a dialog - restore it or the photo pins to the top-left. */
  margin: auto;
}
.photo-modal::backdrop { background: rgba(0, 0, 0, 0.8); backdrop-filter: blur(4px); }
/* The enlarged photo grows out of the avatar circle and shrinks back into
   it. The img carries the name statically (it only renders while the modal
   is open); the avatar gets it from JS just for the morph - a permanent
   name would drag it into every language transition and leave it
   unclickable for the whole wave, and two live elements with one name
   would abort the transition. Plain crossfade while the group morphs
   bounds - the ink melt would smear a large photo. */
.photo-modal img { view-transition-name: photo; }
::view-transition-group(photo) {
  animation-duration: 0.3s;
  animation-timing-function: cubic-bezier(0.33, 1, 0.68, 1);
}
::view-transition-old(photo),
::view-transition-new(photo) {
  animation-duration: 0.3s;
  animation-timing-function: cubic-bezier(0.33, 1, 0.68, 1);
  height: 100%;
}
::view-transition-old(photo) { animation-name: photo-cross-out; }
::view-transition-new(photo) { animation-name: photo-cross-in; }
@keyframes photo-cross-out {
  to { opacity: 0; }
}
@keyframes photo-cross-in {
  from { opacity: 0; }
}
.photo-modal[open]::backdrop {
  animation: backdrop-fade-in 0.4s ease-out;
}
@keyframes backdrop-fade-in {
  from { opacity: 0; }
}
.photo-modal img {
  display: block; max-width: 90vw; max-height: 90vh;
  /* The width/height attributes are presentational hints (width: 900px);
     when max-height alone bites, that fixed width squashes the photo.
     Auto restores proportional scaling. */
  width: auto; height: auto;
  border-radius: 12px; cursor: zoom-out;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
}
.hero h1 {
  font-size: 28px; line-height: 1.2;
  /* One focal gradient headline. --text is the fallback colour; text-fill keeps
     the letters visible where background-clip:text is unsupported. */
  color: var(--text);
  background-image: linear-gradient(45deg, var(--text), var(--accent-2));
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}
.hero .title { color: var(--accent); font-weight: 600; margin-top: 4px; }
.hero p.about { color: var(--muted); margin-top: 10px; }
.actions { margin-top: 16px; display: flex; gap: 12px; flex-wrap: wrap; }
.btn {
  position: relative;
  display: inline-block;
  padding: 12px 16px;
  border-radius: 6px;
  text-decoration: none;
  font-weight: bold;
  border: 1px solid var(--accent);
  overflow: hidden;
  /* Asymmetric easing: slow rest→hover, fast settle. Only transform/box-shadow
     animate (GPU-friendly). */
  transition: transform 400ms ease, box-shadow 400ms ease;
}
.btn.primary {
  background: var(--accent);
  color: var(--on-accent);
  box-shadow: 0 4px 14px rgb(var(--accent-rgb) / 0.35);
}
.btn.primary:hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 22px rgb(var(--accent-rgb) / 0.45);
  transition-duration: 150ms;
}
/* Sheen sweep: a skewed highlight parked off-screen slides across on hover. */
.btn.primary::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(100deg, transparent 20%, rgba(255, 255, 255, 0.35) 50%, transparent 80%);
  transform: translateX(-120%);
  transition: transform 600ms ease;
}
.btn.primary:hover::before { transform: translateX(120%); }
h2 {
  font-size: 15px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  margin-bottom: 14px;
}
ul.links { list-style: none; display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 10px; }
ul.links a {
  display: flex; gap: 10px; align-items: center;
  color: var(--text); text-decoration: none;
  background: var(--card-raised);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 10px 14px;
  box-shadow: var(--rim);
  transition: transform 400ms ease, box-shadow 400ms ease, border-color 400ms ease;
}
ul.links a:hover {
  border-color: var(--accent);
  transform: translateY(-4px) scale(1.02);
  box-shadow: var(--shadow-raised), 0 0 0 1px rgb(var(--accent-rgb) / 0.4), 0 10px 30px rgb(var(--accent-rgb) / 0.22);
  transition-duration: 150ms;
}
ul.links .k { color: var(--muted); font-size: 13px; }

/* Certification tile — same interactive card as a link. */
.badge {
  display: flex; gap: 14px; align-items: center;
  text-decoration: none; color: var(--text);
  background: var(--card-raised);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 14px 16px;
  box-shadow: var(--rim);
  transition: transform 400ms ease, box-shadow 400ms ease, border-color 400ms ease;
}
.badge:hover {
  border-color: var(--accent);
  transform: translateY(-4px) scale(1.02);
  box-shadow: var(--shadow-raised), 0 0 0 1px rgb(var(--accent-rgb) / 0.4), 0 10px 30px rgb(var(--accent-rgb) / 0.22);
  transition-duration: 150ms;
}
.badge img {
  width: 72px; height: 72px; flex: 0 0 auto;
  /* Certificate scans are landscape; crop to the tile instead of squashing. */
  object-fit: cover; border-radius: 6px;
}
/* Breathing room between stacked certificate tiles. */
.badge + .badge { margin-top: 10px; }
.badge .name { display: block; color: var(--accent); font-weight: 600; }
.badge .desc { display: block; color: var(--muted); font-size: 15px; margin-top: 2px; }
.ai-intro { color: var(--muted); margin-bottom: 14px; }
ul.chips { list-style: none; display: flex; flex-wrap: wrap; gap: 8px; }
ul.chips li {
  background: var(--card-raised);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 6px 14px;
  font-size: 14px;
  color: var(--text);
  box-shadow: var(--rim);
}
/* Project tiles — the whole card is the link, same lift+glow as links/badge. */
.projects .item {
  display: block;
  text-decoration: none;
  background: var(--card-raised);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 14px 16px;
  margin-top: 10px;
  box-shadow: var(--rim);
  transition: transform 400ms ease, box-shadow 400ms ease, border-color 400ms ease;
}
.projects .item:first-of-type { margin-top: 0; }
.projects .item:hover {
  border-color: var(--accent);
  transform: translateY(-4px) scale(1.02);
  box-shadow: var(--shadow-raised), 0 0 0 1px rgb(var(--accent-rgb) / 0.4), 0 10px 30px rgb(var(--accent-rgb) / 0.22);
  transition-duration: 150ms;
}
.projects .name { display: block; color: var(--accent); font-weight: 600; }
.projects .desc { display: block; color: var(--muted); font-size: 15px; margin-top: 4px; }
footer { text-align: center; color: var(--muted); font-size: 13px; margin-top: 8px; }
/* Quiet version tag — deemphasized so it draws no attention; links to Git history. */
/* Inline label inside a tile name (e.g. "· AWS Marketplace") stays muted. */
.projects .name .k { color: var(--muted); font-weight: 400; font-size: 13px; }

/* Keyboard focus ring — follows border-radius, doesn't shift layout. */
a:focus-visible,
button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

/* Keep the page alive during the wave. Capturing the root would hide the
   whole live page for the duration (hover and clicks die for ~2s), so the
   root opts out and only the translated nodes take part; their snapshot
   boxes also let the mouse through. A click mid-wave then skips the
   running transition and starts a fresh one. */
:root {
  view-transition-name: none;
}
::view-transition,
::view-transition-group(*),
::view-transition-image-pair(*),
::view-transition-old(*),
::view-transition-new(*) {
  pointer-events: none;
}

/* Language switch (View Transitions API). Each translated node is its own
   transition layer (JS assigns view-transition-name), so only the copy
   melts; the page frame swaps as-is, because identical pixels animating
   full-page read as a blink. JS skips all of it under
   prefers-reduced-motion (and the opt-out block below flattens it too). */
/* Old ink sinks out in 0.5s; new ink floats up over 1.2s and dries. Exits
   stay shorter than entries so attention lands on the new text. Fill-mode
   "both" holds each node's start state through its stagger delay (set by
   JS) so the wave reads cleanly. */
::view-transition-group(*) {
  animation-duration: 1.2s;
  animation-timing-function: cubic-bezier(0.33, 1, 0.68, 1);
  animation-fill-mode: both;
}
::view-transition-old(*) {
  animation-name: lang-melt-out;
  animation-duration: 0.5s;
  animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1);
  animation-fill-mode: both;
}
::view-transition-new(*) {
  animation-name: lang-melt-in;
  animation-duration: 1.2s;
  animation-timing-function: cubic-bezier(0.33, 1, 0.68, 1);
  animation-fill-mode: both;
}
@keyframes lang-melt-out {
  to { opacity: 0; transform: translateY(3px); filter: blur(5px); }
}
/* New ink: floats up out of focus, wet and bright (glow, saturation), and
   dries to the normal text colour as it settles to true size. */
@keyframes lang-melt-in {
  from {
    opacity: 0;
    transform: translateY(6px) scale(1.015);
    filter: blur(9px) saturate(135%) brightness(1.08)
      drop-shadow(0 0 10px rgb(var(--accent-rgb) / 0.6));
  }
  60% {
    opacity: 1;
    filter: blur(1.5px) saturate(112%) brightness(1.03)
      drop-shadow(0 0 6px rgb(var(--accent-rgb) / 0.3));
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
    filter: blur(0) saturate(100%) brightness(1)
      drop-shadow(0 0 0 rgb(var(--accent-rgb) / 0));
  }
}

/* Fallback fade for browsers without view transitions: dissolve out, swap,
   dissolve back in. Opacity only, since many i18n targets are inline spans. */
[data-i18n] {
  transition: opacity 0.2s cubic-bezier(0.25, 0.1, 0.25, 1);
}
main.lang-fading [data-i18n] {
  opacity: 0;
}

/* Send-off ink: an outbound click stamps the clicked tile - a brief press
   with an accent glow that dries, echoing the language-switch ink. The new
   tab opens natively in parallel; nothing is delayed. */
a.inking {
  animation: link-ink 0.45s cubic-bezier(0.33, 1, 0.68, 1);
}
@keyframes link-ink {
  0% { transform: scale(1); }
  30% {
    transform: scale(0.985);
    box-shadow: 0 0 0 1px rgb(var(--accent-rgb) / 0.5), 0 0 24px rgb(var(--accent-rgb) / 0.35);
  }
  100% { transform: scale(1); }
}

/* During a theme flip (site.js adds the class for ~0.5s) every color
   property eases instead of snapping. !important is deliberate: it must
   momentarily beat the per-element transition lists (hover lift etc.),
   and it unwinds when the class drops. */
.theme-fading,
.theme-fading * {
  transition: background-color 0.5s cubic-bezier(0.33, 1, 0.68, 1),
    color 0.5s cubic-bezier(0.33, 1, 0.68, 1),
    border-color 0.5s cubic-bezier(0.33, 1, 0.68, 1),
    box-shadow 0.5s cubic-bezier(0.33, 1, 0.68, 1),
    opacity 0.5s cubic-bezier(0.33, 1, 0.68, 1) !important;
}

/* Opt-out of glass: swap the blur for an opaque surface. */
@media (prefers-reduced-transparency: reduce) {
  .card {
    background: var(--card-opaque);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}

/* Opt-out of motion: keep transitions instant (1ms so events still fire). */
@media (prefers-reduced-motion: reduce) {
  * { transition-duration: 1ms !important; animation-duration: 1ms !important; }
  /* The universal selector skips pseudo-elements, so flatten these too. */
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*),
  .photo-modal[open]::backdrop {
    animation-duration: 1ms !important;
  }
  /* Higher specificity than the * rule above, so the theme flip needs its
     own flattening - the JS gate is the first belt, this is the second. */
  .theme-fading, .theme-fading * { transition-duration: 1ms !important; }
  .btn.primary::before { display: none; }
  /* Drop the lift/scale entirely; keep only the accent border on hover. */
  ul.links a:hover, .badge:hover, .projects .item:hover, .btn.primary:hover {
    transform: none;
  }
}
