/* ===========================================================
   IMVMVD — Official Site
   ---------------------------------------------------------
   Rewritten to match the Django templates. The previous
   stylesheet targeted the legacy single-page PHP markup
   (.nav-links, .hero-title, .about-portrait img) and most of
   its rules matched nothing, which is why the nav had no
   spacing and images sized wrongly.

   Fonts are self-hosted rather than pulled from a CDN: a
   Google Fonts request is unreliable from Iran, which is
   exactly where a large share of the readers are.
=========================================================== */

/* ============================================================
   1. FONTS
   Variable fonts — one file covers every weight.
   Latin  → Space Grotesk (display) + Inter (text)
   fa/ar  → Vazirmatn, which covers both scripts
   zh     → system CJK. A Chinese webfont runs to megabytes;
            the installed system faces are excellent.
============================================================ */

@font-face{
  font-family:'Space Grotesk';
  src:url("../fonts/space-grotesk-latin.87c506d88b9f.woff2") format('woff2-variations');
  font-weight:300 700;
  font-display:swap;
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215;
}

@font-face{
  font-family:'Inter';
  src:url("../fonts/inter-latin.260c81a4759b.woff2") format('woff2-variations');
  font-weight:100 900;
  font-display:swap;
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215;
}
@font-face{
  font-family:'Inter';
  src:url("../fonts/inter-latin-ext.1ad231aac0a8.woff2") format('woff2-variations');
  font-weight:100 900;
  font-display:swap;
  unicode-range:U+0100-02AF,U+0304,U+0308,U+0329,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF;
}

@font-face{
  font-family:'Vazirmatn';
  src:url("../fonts/vazirmatn-arabic.05d9ce2a23d3.woff2") format('woff2-variations');
  font-weight:100 900;
  font-display:swap;
  unicode-range:U+0600-06FF,U+0750-077F,U+0870-088E,U+08A0-08FF,U+200C-200E,U+2010-2011,U+FB50-FDFF,U+FE70-FEFF;
}
@font-face{
  font-family:'Vazirmatn';
  src:url("../fonts/vazirmatn-latin.e7eada615dc3.woff2") format('woff2-variations');
  font-weight:100 900;
  font-display:swap;
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+2000-206F,U+20AC,U+2122;
}

/* ============================================================
   2. TOKENS
============================================================ */

:root{
  /* Brand */
  --primary:#3C8CFF;
  --secondary:#783CFF;
  --primary-glow:rgba(60,140,255,.40);

  /* Surfaces */
  --bg:#050505;
  --bg-raised:#0A0A0C;
  --glass:rgba(255,255,255,.035);
  --glass-hover:rgba(255,255,255,.062);
  --line:rgba(245,241,236,.10);
  --line-strong:rgba(245,241,236,.18);

  /* Ink */
  --ink:#F5F1EC;
  --ink-muted:rgba(245,241,236,.62);
  --ink-faint:rgba(245,241,236,.40);
  --danger:#FF6B5A;
  --success:#2DD4BF;

  /* Type */
  --font-display:'Space Grotesk','Vazirmatn',ui-sans-serif,system-ui,sans-serif;
  --font-text:'Inter','Vazirmatn',ui-sans-serif,system-ui,-apple-system,sans-serif;
  --font-cjk:'PingFang SC','Hiragino Sans GB','Microsoft YaHei','Noto Sans SC',sans-serif;
  --font-mono:ui-monospace,'SFMono-Regular',Menlo,Consolas,monospace;

  /* Fluid type scale */
  --t-hero:clamp(2.6rem,8vw,5.5rem);
  --t-h1:clamp(2rem,5vw,3.2rem);
  --t-h2:clamp(1.4rem,3vw,2rem);
  --t-h3:1.05rem;
  --t-body:1rem;
  --t-label:.72rem;

  /* Rhythm */
  --space-section:clamp(3.5rem,8vw,6rem);
  --radius:18px;
  --radius-sm:12px;
  --header-h:76px;

  --ease:cubic-bezier(.16,.84,.44,1);
}

/* Chinese takes the CJK stack ahead of the Latin faces. */
html[lang^="zh"]{
  --font-display:var(--font-cjk);
  --font-text:var(--font-cjk);
}

/* ---------- light theme ---------- */
[data-theme="light"]{
  --bg:#FAF8F5;
  --bg-raised:#FFFFFF;
  --glass:rgba(10,10,12,.030);
  --glass-hover:rgba(10,10,12,.055);
  --line:rgba(10,10,12,.10);
  --line-strong:rgba(10,10,12,.18);

  --ink:#12100F;
  --ink-muted:rgba(18,16,15,.66);
  --ink-faint:rgba(18,16,15,.45);
  --primary:#0B62E4;
  --primary-glow:rgba(11,98,228,.20);
}

/* ============================================================
   3. RESET & BASE
============================================================ */

*,*::before,*::after{ margin:0; padding:0; box-sizing:border-box; }

html{ scroll-behavior:smooth; -webkit-text-size-adjust:100%; }

body{
  background:var(--bg);
  color:var(--ink);
  font-family:var(--font-text);
  font-size:var(--t-body);
  line-height:1.65;
  /* dvh, not vh: on mobile browsers vh counts the space behind the address
     bar, which puts the footer just off the bottom of a short page. */
  min-height:100dvh;
  /* A column with a growing <main> is what keeps the footer against the
     bottom of the window when a page has little on it, instead of leaving it
     floating halfway up with dead space underneath. */
  display:flex;
  flex-direction:column;
  overflow-x:hidden;
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
  transition:background .4s var(--ease),color .4s var(--ease);
}

a{ color:inherit; text-decoration:none; }
button{ font-family:inherit; color:inherit; }
img{ max-width:100%; display:block; }
ul,ol{ list-style:none; }

::selection{ background:var(--primary); color:#fff; }

:focus-visible{
  outline:2px solid var(--primary);
  outline-offset:3px;
  border-radius:4px;
}

.sr-only{
  position:absolute;
  width:1px; height:1px;
  overflow:hidden;
  clip-path:inset(50%);
  white-space:nowrap;
}

/* ============================================================
   4. TYPOGRAPHY
============================================================ */

h1,h2,h3,h4{
  font-family:var(--font-display);
  font-weight:700;
  line-height:1.12;
  letter-spacing:-.01em;
  /* Stops a heading from leaving one orphaned word on its own line. */
  text-wrap:balance;
}

/* Same idea for body copy, but cheaper — the browser only fixes the last
   line rather than rebalancing the whole block. */
p{ text-wrap:pretty; }

main h1{ font-size:var(--t-h1); margin-bottom:.6em; }
main h3{ font-size:var(--t-h3); }

/* Section headings get a short accent rule so a long page reads as a
   sequence of parts rather than one undifferentiated column. */
main h2{
  position:relative;
  font-size:var(--t-h2);
  margin-bottom:.7em;
  padding-top:1.1rem;
}
main h2::before{
  content:'';
  position:absolute;
  top:0;
  inset-inline-start:0;
  width:34px; height:2px;
  border-radius:2px;
  background:linear-gradient(90deg,var(--primary),var(--secondary));
}
/* Centred headings (project hero) and card headings keep no rule. */
.project-hero h2::before,
.teaser-card h3::before,
.player h2::before{ content:none; }

p{ max-width:70ch; }

/* Persian and Arabic need a little more height than Latin to stay
   comfortable at the same nominal size. */
html[dir="rtl"] body{ font-size:1.04rem; line-height:1.85; }

/* On an RTL page, a run of English would have its trailing full stop pushed
   to the front by the bidi algorithm (".like this"). `plaintext` gives each
   block its own direction, taken from its first strong character, so English
   prose reads LTR and Persian reads RTL side by side. */
html[dir="rtl"] p,
html[dir="rtl"] li,
html[dir="rtl"] td{
  unicode-bidi:plaintext;
}

/* Headings are the exception, because most of them are brand names — SAMYAR
   BEATZ, CHANG CAST, MVMVD — which are Latin permanently and by choice, not
   because a translation is missing. Under `plaintext` each one claimed its
   own direction and jumped to the far side of its card, leaving the Arabic
   label above it stranded on the other. `isolate` keeps the heading on the
   page's side while the name inside it still reads left to right. */
html[dir="rtl"] h1,
html[dir="rtl"] h2,
html[dir="rtl"] h3{
  unicode-bidi:isolate;
}

/* ============================================================
   5. LAYOUT
============================================================ */

.container{
  width:100%;
  max-width:1180px;
  margin-inline:auto;
  padding-inline:1.5rem;
}

main > section{ padding-block:var(--space-section) 0; }
main > section:first-child{ padding-top:calc(var(--header-h) + var(--space-section)); }
main > section:last-child{ padding-bottom:var(--space-section); }

/* The project detail page wraps its sections in an <article>. Its first child
   is a <header>, not a <section>, so a rule scoped to `section:first-child`
   never matched it and the project's own name rendered underneath the fixed
   header — the one element on the page nobody could afford to lose. */
main > article > section{ padding-block:var(--space-section) 0; }
main > article > :first-child{ padding-top:calc(var(--header-h) + var(--space-section)); }
main > article > :last-child{ padding-bottom:var(--space-section); }

.section-intro{
  color:var(--ink-muted);
  font-size:1.05rem;
  margin-top:-.3em;
}

/* ============================================================
   6. BACKGROUND
============================================================ */

.bg-layer{
  position:fixed;
  inset:0;
  z-index:0;
  overflow:hidden;
  pointer-events:none;
  background:
    radial-gradient(ellipse 60% 40% at 50% -10%,var(--primary-glow) 0%,transparent 62%),
    radial-gradient(ellipse 50% 40% at 100% 100%,rgba(120,60,255,.10) 0%,transparent 60%),
    var(--bg);
}
[data-theme="light"] .bg-layer{
  background:
    radial-gradient(ellipse 60% 40% at 50% -10%,var(--primary-glow) 0%,transparent 62%),
    var(--bg);
}

.meteor{
  position:absolute;
  top:-10%;
  width:2px; height:120px;
  background:linear-gradient(180deg,rgba(245,241,236,.85),var(--primary) 55%,transparent);
  filter:drop-shadow(0 0 6px var(--primary-glow));
  transform:rotate(215deg);
  animation:meteorFall linear infinite;
  opacity:0;
}
@keyframes meteorFall{
  0%{ transform:translate(0,0) rotate(215deg); opacity:0; }
  8%{ opacity:1; }
  80%{ opacity:.7; }
  100%{ transform:translate(-620px,780px) rotate(215deg); opacity:0; }
}
.particle{
  position:absolute;
  border-radius:50%;
  background:rgba(245,241,236,.5);
  animation:floatUp linear infinite;
  opacity:0;
}
@keyframes floatUp{
  0%{ transform:translateY(0); opacity:0; }
  10%{ opacity:.5; }
  90%{ opacity:.35; }
  100%{ transform:translateY(-100vh) translateX(20px); opacity:0; }
}
[data-theme="light"] .meteor,
[data-theme="light"] .particle{ display:none; }

main,.site-header,.site-footer{ position:relative; z-index:1; }

/* Takes up whatever is left, so the footer below it lands on the bottom edge
   of a short page rather than partway up it. flex-basis stays auto so a long
   page is unaffected. */
main{ flex:1 0 auto; }

/* ============================================================
   7. HEADER & NAV
============================================================ */

.site-header{
  position:fixed;
  top:0; inset-inline:0;
  z-index:100;
  height:var(--header-h);
  display:flex;
  align-items:center;
  background:color-mix(in srgb,var(--bg) 72%,transparent);
  backdrop-filter:blur(18px) saturate(140%);
  -webkit-backdrop-filter:blur(18px) saturate(140%);
  border-bottom:1px solid var(--line);
  transition:background .4s var(--ease),border-color .4s var(--ease);
}

.header-inner{
  display:flex;
  align-items:center;
  gap:1.25rem;
}

.brand{
  display:flex;
  align-items:center;
  font-family:var(--font-display);
  font-weight:700;
  font-size:1.05rem;
  letter-spacing:.16em;
  white-space:nowrap;
  transition:color .3s var(--ease);
}
.brand:hover{ color:var(--primary); }

/* The logo is a white monogram on transparency, so it is painted as a mask
   over currentColor rather than dropped in with <img>. That way it inherits
   the theme's ink — white artwork as an image would disappear against the
   light theme's near-white background — and the existing hover colour keeps
   working on it for free.

   Where masking is unavailable the wordmark below stays visible instead, so
   the header is never an empty link. */
.brand__mark{ display:none; }

@supports (-webkit-mask-image:url("")) or (mask-image:url("")){
  .brand__mark{
    display:block;
    width:46px;
    height:28px;
    background:currentColor;
    /* --brand-mark is set inline from SiteSettings when a logo has been
       uploaded through the panel. The fallback is the file that ships with
       the site, so an untouched install needs no database row. */
    -webkit-mask:var(--brand-mark,url("../img/head-logo.bee0e853434c.png")) no-repeat center / contain;
    mask:var(--brand-mark,url("../img/head-logo.bee0e853434c.png")) no-repeat center / contain;
  }
  .brand__name{
    position:absolute;
    width:1px; height:1px;
    padding:0; margin:-1px;
    overflow:hidden;
    clip-path:inset(50%);
    white-space:nowrap;
    border:0;
  }
}

.nav{
  display:flex;
  align-items:center;
  gap:1.75rem;
  margin-inline:auto;
}
.nav a{
  position:relative;
  font-size:.8rem;
  letter-spacing:.08em;
  text-transform:uppercase;
  color:var(--ink-muted);
  padding-block:.4rem;
  white-space:nowrap;
  transition:color .3s var(--ease);
}
.nav a::after{
  content:'';
  position:absolute;
  inset-inline-start:0;
  bottom:0;
  width:0; height:1px;
  background:var(--primary);
  transition:width .35s var(--ease);
}
.nav a:hover{ color:var(--ink); }
.nav a:hover::after{ width:100%; }

.util-switchers{
  display:flex;
  align-items:center;
  gap:.55rem;
}
.switch-btn{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  min-width:38px; height:38px;
  padding-inline:.65rem;
  border-radius:999px;
  border:1px solid var(--line);
  background:var(--glass);
  font-size:.78rem;
  line-height:1;
  cursor:pointer;
  transition:border-color .3s var(--ease),background .3s var(--ease);
}
.switch-btn:hover{ border-color:var(--primary); background:var(--glass-hover); }

/* ---------- language flags ---------- */

.lang-switch{
  display:flex;
  align-items:center;
  gap:.3rem;
  padding:.22rem;
  border-radius:999px;
  border:1px solid var(--line);
  background:var(--glass);
}
.lang-flag{
  display:grid;
  place-items:center;
  width:30px; height:30px;
  border-radius:999px;
  opacity:.5;
  filter:saturate(.55);
  transition:opacity .3s var(--ease),filter .3s var(--ease),transform .3s var(--ease);
}
.lang-flag:hover{ opacity:.9; filter:saturate(1); transform:translateY(-1px); }
.lang-flag.is-current{
  opacity:1;
  filter:none;
  box-shadow:0 0 0 1px var(--line-strong);
}
.lang-flag .flag{
  width:20px; height:14px;
  border-radius:2px;
  display:block;
}

.nav-toggle{
  display:none;
  width:38px; height:38px;
  border-radius:999px;
  border:1px solid var(--line);
  background:var(--glass);
  cursor:pointer;
  align-items:center;
  justify-content:center;
}

@media (max-width:900px){
  .nav{
    position:fixed;
    top:var(--header-h);
    inset-inline:0;
    flex-direction:column;
    align-items:flex-start;
    gap:1.1rem;
    margin:0;
    padding:1.6rem 1.5rem 2rem;
    background:var(--bg-raised);
    border-bottom:1px solid var(--line);
    transform:translateY(-140%);
    transition:transform .42s var(--ease);
  }
  .nav.is-open{ transform:translateY(0); }
  .nav-toggle{ display:inline-flex; }
  .header-inner{ justify-content:space-between; }
}

/* ============================================================
   8. HERO
============================================================ */

.hero{
  display:flex;
  flex-direction:column;
  align-items:center;
  text-align:center;
}

/* The hero animates on load rather than on scroll — it is already in view,
   so the scroll observer would never fire for it. Each piece is staggered
   behind the one above it. */
.hero > *{ animation:heroRise .9s var(--ease) both; }
.hero > :nth-child(1){ animation-delay:.05s; }
.hero > :nth-child(2){ animation-delay:.15s; }
.hero > :nth-child(3){ animation-delay:.25s; }
.hero > :nth-child(4){ animation-delay:.33s; }
.hero > :nth-child(5){ animation-delay:.4s; }
@keyframes heroRise{
  from{ opacity:0; transform:translateY(24px); }
  to{ opacity:1; transform:translateY(0); }
}

img.hero-avatar{
  width:136px; height:136px;
  border-radius:50%;
  object-fit:cover;
  border:1px solid var(--line-strong);
  box-shadow:0 0 70px var(--primary-glow);
  margin-bottom:2rem;
}

/* The name is the loudest thing on the site, so it is worth the trouble.

   Two gradients are clipped to the letterforms at once. The lower one is a
   fixed vertical fade that gives the glyphs some dimension instead of a flat
   fill. The upper one is the sheen, wider than the text, and its position is
   what animates — so nothing reflows and nothing relayouts.

   The old sheen was white travelling through near-white letters, which is
   barely a change; the light theme only ever looked better because its band
   happened to be a different hue. So the band carries colour in both themes
   now: violet in, hot white core, blue out. */
.hero h1{
  position:relative;
  font-size:var(--t-hero);
  letter-spacing:.02em;
  line-height:.98;
  margin-bottom:0;

  --sheen-glow:rgba(60,140,255,.16);
  --sheen-glow-wide:rgba(120,60,255,.10);
  --sheen-glow-peak:rgba(120,60,255,.42);
  --sheen-glow-wide-peak:rgba(60,140,255,.24);

  background:
    linear-gradient(
      100deg,
      color-mix(in srgb,var(--ink) 42%,transparent) 0%,
      var(--ink) 26%,
      var(--secondary) 41%,
      #FFFFFF 50%,
      var(--primary) 59%,
      var(--ink) 74%,
      color-mix(in srgb,var(--ink) 42%,transparent) 100%
    ),
    linear-gradient(
      180deg,
      #FFFFFF 0%,
      color-mix(in srgb,var(--ink) 78%,transparent) 100%
    );
  background-size:280% 100%,100% 100%;
  /* Where the sheen rests when the animation is off — mid-glyph, not
     parked off to one side. */
  background-position:50% 0,0 0;
  -webkit-background-clip:text;
  background-clip:text;
  color:transparent;

  /* The glow lives on ::before, not here. A filter on this element blurs a
     rectangle rather than the letters: Chromium applies the filter before the
     background is clipped to the text, so drop-shadow sees the full box and
     leaves a smudge behind the name. */
  isolation:isolate;

  animation:heroRise .9s var(--ease) both, heroSheen 9s linear 1.2s infinite;
}

/* The halo. A copy of the name with transparent glyphs, so all that renders is
   its text-shadow — which follows the letterforms exactly and costs a great
   deal less than an animated blur. Only its opacity animates, which the
   compositor can do without repainting anything. */
.hero h1::before{
  content:attr(data-text);
  position:absolute;
  inset:0;
  z-index:-1;
  color:transparent;
  text-shadow:
    0 0 13px var(--sheen-glow-peak),
    0 0 38px var(--sheen-glow-wide-peak);
  opacity:.3;
  pointer-events:none;
  animation:heroGlow 9s ease-in-out 1.2s infinite;
}

/* The halo swells as the bright core crosses the letters and settles once it
   has passed, so it reads as a reflection rather than as a lamp someone left
   switched on. */
@keyframes heroGlow{
  0%,100%{ opacity:.3; }
  50%{ opacity:1; }
}
@keyframes heroSheen{
  0%{ background-position:180% 0,0 0; }
  100%{ background-position:-80% 0,0 0; }
}

/* Light theme keeps the blue pass that already worked, over dark letterforms
   rather than pale ones, and its halo is pulled right down — a violet bloom
   that reads as neon on black turns to mud on cream. */
[data-theme="light"] .hero h1{
  --sheen-glow:rgba(11,98,228,.10);
  --sheen-glow-wide:rgba(120,60,255,.05);
  --sheen-glow-peak:rgba(11,98,228,.22);
  --sheen-glow-wide-peak:rgba(120,60,255,.12);

  background:
    linear-gradient(
      100deg,
      color-mix(in srgb,var(--ink) 50%,transparent) 0%,
      var(--ink) 30%,
      var(--secondary) 42%,
      var(--primary) 50%,
      var(--ink) 64%,
      color-mix(in srgb,var(--ink) 50%,transparent) 100%
    ),
    linear-gradient(
      180deg,
      var(--ink) 0%,
      color-mix(in srgb,var(--ink) 72%,transparent) 100%
    );
  background-size:280% 100%,100% 100%;
  background-position:50% 0,0 0;
  -webkit-background-clip:text;
  background-clip:text;
}

.tagline{
  margin-top:1.5rem;
  max-width:46ch;
  font-size:1.08rem;
}
.bio-short{
  margin-top:.7rem;
  max-width:58ch;
  font-size:.94rem;
  color:var(--ink-muted);
}
.warning-text{
  margin-top:1.7rem;
  padding:.55rem 1.2rem;
  border-radius:999px;
  border:1px solid var(--line);
  background:var(--glass);
  font-size:.82rem;
  color:var(--ink-faint);
}

.hero-actions{
  display:flex;
  flex-wrap:wrap;
  gap:.8rem;
  justify-content:center;
  margin-top:2.2rem;
}

/* A quiet nudge that there is more below — the hero fills the screen, and
   without this the fold reads as the end of the page. */
.scroll-cue{
  display:block;
  width:1px;
  height:52px;
  margin-top:3rem;
  background:linear-gradient(180deg,transparent,var(--ink-faint),transparent);
  background-size:100% 200%;
  animation:cueSlide 2.6s ease-in-out infinite;
}
@keyframes cueSlide{
  0%,100%{ background-position:0 -100%; opacity:.35; }
  50%{ background-position:0 100%; opacity:1; }
}

/* ============================================================
   9. BUTTONS
============================================================ */

.btn-solid,.btn-ghost{
  display:inline-block;
  padding:.85rem 1.8rem;
  border-radius:999px;
  border:1px solid transparent;
  font-family:var(--font-display);
  font-size:.8rem;
  font-weight:600;
  letter-spacing:.09em;
  text-transform:uppercase;
  cursor:pointer;
  transition:transform .3s var(--ease),box-shadow .3s var(--ease),border-color .3s var(--ease);
}
.btn-solid{
  background:linear-gradient(135deg,var(--primary),var(--secondary));
  color:#fff;
}
.btn-solid:hover{ transform:translateY(-2px); box-shadow:0 10px 32px var(--primary-glow); }
.btn-ghost{ border-color:var(--line-strong); }
.btn-ghost:hover{ border-color:var(--primary); }

.back-link{
  display:inline-block;
  font-size:.85rem;
  color:var(--ink-muted);
  transition:color .3s var(--ease);
}
.back-link:hover{ color:var(--ink); }

/* ============================================================
   10. CARDS
============================================================ */

.teaser-grid,.project-grid{
  display:grid;
  grid-template-columns:repeat(auto-fit,minmax(250px,1fr));
  gap:1.1rem;
  margin-top:1.8rem;
}

.teaser-card,.project-card,.work-card{
  display:block;
  border-radius:var(--radius);
  border:1px solid var(--line);
  background:var(--glass);
  transition:border-color .35s var(--ease),background .35s var(--ease),transform .35s var(--ease);
}
.teaser-card:hover{
  border-color:var(--line-strong);
  background:var(--glass-hover);
  transform:translateY(-3px);
}

.teaser-card{ padding:1.6rem 1.4rem; }
.teaser-card h3{
  font-size:.9rem;
  letter-spacing:.12em;
  text-transform:uppercase;
  margin-bottom:.65rem;
}
.teaser-card p{ color:var(--ink-muted); font-size:.9rem; }

/* ---------- project card ---------- */

.project-card{
  --project-accent:var(--primary);
  position:relative;
  padding:1.7rem 1.5rem;
  overflow:hidden;
  isolation:isolate;
}
.project-card::before{
  content:'';
  position:absolute;
  inset-block:0;
  inset-inline-start:0;
  width:3px;
  background:var(--project-accent);
  opacity:.8;
  transition:width .35s var(--ease),opacity .35s var(--ease);
}
/* The brand's own colour blooms from the corner on hover, so each card
   lights up in its own hue rather than a shared generic highlight. */
.project-card::after{
  content:'';
  position:absolute;
  inset:0;
  z-index:-1;
  background:radial-gradient(130% 100% at 0% 0%,var(--project-accent) 0%,transparent 58%);
  opacity:0;
  transition:opacity .45s var(--ease);
}
html[dir="rtl"] .project-card::after{
  background:radial-gradient(130% 100% at 100% 0%,var(--project-accent) 0%,transparent 58%);
}
.project-card:hover{
  border-color:color-mix(in srgb,var(--project-accent) 50%,transparent);
  background:var(--glass-hover);
  transform:translateY(-5px);
  box-shadow:0 18px 45px rgba(0,0,0,.35);
}
.project-card:hover::before{ width:5px; opacity:1; }
.project-card:hover::after{ opacity:.14; }

/* The name slides just enough to feel like it responded. */
.project-card__name{ transition:transform .35s var(--ease); }
.project-card:hover .project-card__name{ transform:translateX(3px); }
html[dir="rtl"] .project-card:hover .project-card__name{ transform:translateX(-3px); }

.project-card__logo{
  width:46px; height:46px;
  object-fit:contain;
  margin-bottom:1rem;
}
.project-card__name{
  font-size:1.14rem;
  letter-spacing:.07em;
}
.project-card__kind{
  margin-top:.35rem;
  font-size:var(--t-label);
  letter-spacing:.2em;
  text-transform:uppercase;
  color:var(--project-accent);
}
.project-card__tagline{
  margin-top:.85rem;
  font-size:.9rem;
  color:var(--ink-muted);
}
.project-card__external{
  display:inline-block;
  margin-top:1rem;
  font-size:.76rem;
  letter-spacing:.08em;
  color:var(--project-accent);
}

/* ---------- card background image ----------
   The photo is atmosphere behind the name, not something the visitor is meant
   to look at — the way an artist header works on a streaming service. Three
   things do that job together: the image is desaturated and pulled far below
   the brightness of the text, a scrim thickens toward the bottom edge where
   the text sits, and the brand accent still bleeds over the top. Any one of
   them alone is not enough; CHANG CAST's artwork is red on bright blue, and
   dimming without a scrim just leaves a dark blue rectangle still shouting.

   Shared on purpose: .project-card is the grid on /projects/, .project-tile is
   the bento on the home page, and they should not drift apart. */

.project-card__bg,
.project-tile__bg{
  position:absolute;
  inset:0;
  z-index:-2;
  border-radius:inherit;
  overflow:hidden;
  pointer-events:none;
}
.project-card__bg img,
.project-tile__bg img{
  width:100%;
  height:100%;
  object-fit:cover;
  filter:grayscale(.28) brightness(.58) contrast(1.06);
  /* Slightly overscaled so the hover zoom never exposes an edge. */
  transform:scale(1.04);
  transition:filter .55s var(--ease),transform .7s var(--ease);
}

/* Dimming alone leaves bright patches wherever the photo had highlights, and
   text lands on them unpredictably. The scrim is what actually guarantees
   legibility, so it is the strongest at the bottom where every card writes. */
.project-card__bg::after,
.project-tile__bg::after{
  content:'';
  position:absolute;
  inset:0;
  background:linear-gradient(
    180deg,
    rgba(4,4,6,.20) 0%,
    rgba(4,4,6,.44) 42%,
    rgba(4,4,6,.80) 100%
  );
}

/* The two card types write at opposite edges. .project-tile stacks its text
   against the bottom, .project-card starts at the top, so the same scrim that
   protects one leaves the other's title sitting on whatever the photo happens
   to put there — KOTIZGOY's is a brightly lit hand. Weight it to match. */
.project-card__bg::after{
  background:linear-gradient(
    180deg,
    rgba(4,4,6,.72) 0%,
    rgba(4,4,6,.42) 62%,
    rgba(4,4,6,.60) 100%
  );
}

/* Enough of a lift to feel alive under the cursor, still nowhere near the
   brightness of the text. */
.project-card.has-cover:hover .project-card__bg img,
.project-tile.has-cover:hover .project-tile__bg img{
  filter:grayscale(.08) brightness(.76) contrast(1.06);
  transform:scale(1.09);
}

/* A translucent card surface behind a photo reads as haze, and shows through
   as a pale flash before the image decodes. Covered cards sit on solid ink and
   let the scrim be the surface instead. */
.project-card.has-cover,
.project-card.has-cover:hover,
.project-tile.has-cover,
.project-tile.has-cover:hover{ background-color:#0A0A0C; }

/* Light theme keeps the dark treatment rather than mirroring it.

   Bleaching the photo and turning the scrim white was the obvious move and it
   was wrong: washed to that degree the image stops being a background and
   becomes a haze over the card, and every one of the five ends up the same
   pale grey regardless of what it is a picture of.

   So a covered card stays a piece of dark media sitting on a light page —
   which is what a streaming service does with artwork too. Redefining the ink
   tokens on the card relights everything inside it, because the name, the
   tagline and the kind label all read those tokens already. */
[data-theme="light"] .project-card.has-cover,
[data-theme="light"] .project-tile.has-cover{
  --ink:#F5F1EC;
  --ink-muted:rgba(245,241,236,.66);
  color:var(--ink);
  border-color:rgba(10,10,12,.12);
  box-shadow:0 10px 30px rgba(10,10,12,.13);
}
[data-theme="light"] .project-card.has-cover:hover,
[data-theme="light"] .project-tile.has-cover:hover{
  box-shadow:0 18px 44px rgba(10,10,12,.22);
}

/* ---------- project detail ---------- */

/* Set inline from the model in projects/detail.html, so everything below can
   read it without knowing which project it is. This declaration is only the
   floor for a project that has no colour saved. */
.project-page{ --project-accent:var(--primary); }

/* Every button on a project's page wears that project's colour, including the
   two that are shared with the rest of the site. Without these, the "Send a
   request" button under KOTIZGOY's teal page came out in the site's blue. */
.project-page .btn-solid{
  background:linear-gradient(135deg,var(--project-accent),color-mix(in srgb,var(--project-accent) 55%,var(--secondary)));
}
.project-page .btn-solid:hover{
  box-shadow:0 10px 32px color-mix(in srgb,var(--project-accent) 45%,transparent);
}
.project-page .btn-ghost:hover{ border-color:var(--project-accent); }
/* The short rule above each section heading, in the project's colour rather
   than the site's — otherwise a teal page has a blue mark on every heading. */
.project-page h2::before{
  background:linear-gradient(90deg,var(--project-accent),color-mix(in srgb,var(--project-accent) 35%,transparent));
}
.project-page .back-link:hover{ color:var(--project-accent); }
.project-page .contact-project a:hover{ color:var(--project-accent); }

.project-hero{
  display:flex;
  flex-direction:column;
  align-items:center;
  text-align:center;
}
.project-hero__logo{
  width:84px; height:84px;
  object-fit:contain;
  margin-bottom:1.5rem;
}
.project-hero__name{
  font-size:var(--t-h1);
  letter-spacing:.05em;
}
.project-hero__kind{
  margin-top:.8rem;
  font-size:var(--t-label);
  letter-spacing:.3em;
  text-transform:uppercase;
  color:var(--project-accent);
}
.project-hero__tagline{
  margin-top:1.1rem;
  max-width:52ch;
  font-size:1.05rem;
  color:var(--ink-muted);
}
.project-body{ max-width:66ch; color:var(--ink-muted); }

/* ---------- work grid ---------- */

.work-grid{
  display:grid;
  grid-template-columns:repeat(auto-fill,minmax(210px,1fr));
  gap:1rem;
  margin-top:1.5rem;
}
.work-card{ padding:1.1rem; border-radius:var(--radius-sm); }
a.work-card:hover{
  border-color:color-mix(in srgb,var(--project-accent) 45%,transparent);
  background:var(--glass-hover);
  transform:translateY(-3px);
}
.work-card__cover{
  width:100%;
  aspect-ratio:1/1;
  object-fit:cover;
  border-radius:calc(var(--radius-sm) - 4px);
  margin-bottom:.9rem;
}
.work-card__title{ font-size:.98rem; }
.work-card__kind{
  margin-top:.3rem;
  font-size:.66rem;
  letter-spacing:.16em;
  text-transform:uppercase;
  color:var(--project-accent);
}
.work-card__desc{
  margin-top:.6rem;
  font-size:.85rem;
  color:var(--ink-muted);
}

/* ============================================================
   10b. HOME — bento, now playing, quick links
============================================================ */

.section-head{
  display:flex;
  align-items:flex-end;
  justify-content:space-between;
  gap:1rem;
  flex-wrap:wrap;
}
.section-head h2{ margin-bottom:0; }
.section-head__link{
  font-size:.8rem;
  letter-spacing:.06em;
  color:var(--ink-muted);
  transition:color .3s var(--ease);
}
.section-head__link:hover{ color:var(--primary); }

/* ---------- bento ----------
   Five equal boxes read as a list and gave the page no focal point. The
   lead tile is twice the size, so the eye lands somewhere first. */
.project-bento{
  display:grid;
  grid-template-columns:repeat(4,1fr);
  grid-auto-rows:minmax(148px,auto);
  gap:.9rem;
  margin-top:1.8rem;
}
.project-bento > :first-child{
  grid-column:span 2;
  grid-row:span 2;
}

.project-tile{
  --project-accent:var(--primary);
  position:relative;
  display:flex;
  flex-direction:column;
  justify-content:flex-end;
  gap:.15rem;
  padding:1.4rem;
  border-radius:var(--radius);
  border:1px solid var(--line);
  background:var(--glass);
  overflow:hidden;
  isolation:isolate;
  transition:border-color .4s var(--ease),transform .4s var(--ease),box-shadow .4s var(--ease);
}
.project-tile:hover{
  border-color:color-mix(in srgb,var(--project-accent) 55%,transparent);
  transform:translateY(-4px);
  box-shadow:0 20px 50px rgba(0,0,0,.4);
}

/* The accent bleeds up from the bottom edge, so each tile is coloured by its
   own brand rather than by a shared highlight. */
.project-tile__glow{
  position:absolute;
  inset:0;
  z-index:-1;
  background:linear-gradient(0deg,var(--project-accent) -40%,transparent 62%);
  opacity:.16;
  transition:opacity .45s var(--ease);
}
.project-tile:hover .project-tile__glow{ opacity:.38; }

.project-tile__logo{
  width:38px; height:38px;
  object-fit:contain;
  margin-bottom:auto;
}
.project-tile__kind{
  font-size:.64rem;
  letter-spacing:.2em;
  text-transform:uppercase;
  color:var(--project-accent);
}
.project-tile__name{
  font-family:var(--font-display);
  font-size:1.15rem;
  font-weight:700;
  letter-spacing:.06em;
  line-height:1.15;
  text-wrap:balance;
}
.project-tile--lead .project-tile__name{ font-size:clamp(1.6rem,3.4vw,2.5rem); }
.project-tile__tagline{
  margin-top:.5rem;
  max-width:34ch;
  font-size:.92rem;
  color:var(--ink-muted);
}
.project-tile__go{
  position:absolute;
  top:1.2rem;
  inset-inline-end:1.2rem;
  font-size:1.05rem;
  color:var(--project-accent);
  opacity:0;
  transform:translateX(-4px);
  transition:opacity .35s var(--ease),transform .35s var(--ease);
}
.project-tile:hover .project-tile__go{ opacity:1; transform:translateX(0); }

@media (max-width:900px){
  .project-bento{ grid-template-columns:repeat(2,1fr); }
  .project-bento > :first-child{ grid-column:span 2; grid-row:span 1; }
}
@media (max-width:520px){
  .project-bento{ grid-template-columns:1fr; }
  .project-bento > :first-child{ grid-column:span 1; }
}

/* ---------- now playing ---------- */

.now-playing{
  display:flex;
  align-items:center;
  gap:1.1rem;
  margin-top:1.8rem;
  padding:1rem 1.3rem;
  border-radius:var(--radius);
  border:1px solid var(--line);
  background:var(--glass);
  transition:border-color .35s var(--ease),background .35s var(--ease);
}
.now-playing:hover{ border-color:var(--primary); background:var(--glass-hover); }
.now-playing__cover{
  width:56px; height:56px;
  border-radius:var(--radius-sm);
  object-fit:cover;
  flex-shrink:0;
}
.now-playing__bars{
  display:flex;
  align-items:flex-end;
  gap:2px;
  height:18px;
}
.now-playing__bars i{
  width:2.5px;
  border-radius:2px;
  background:var(--primary);
  animation:eq 1.1s ease-in-out infinite;
}
.now-playing__bars i:nth-child(1){ height:40%; animation-delay:0s; }
.now-playing__bars i:nth-child(2){ height:85%; animation-delay:.18s; }
.now-playing__bars i:nth-child(3){ height:60%; animation-delay:.36s; }
.now-playing__bars i:nth-child(4){ height:95%; animation-delay:.5s; }
@keyframes eq{
  0%,100%{ transform:scaleY(.35); }
  50%{ transform:scaleY(1); }
}
.now-playing__meta{ min-width:0; }
.now-playing__title{
  display:block;
  font-family:var(--font-display);
  font-weight:700;
}
.now-playing__artist{
  display:block;
  font-size:.85rem;
  color:var(--ink-muted);
}
.now-playing__cta{
  margin-inline-start:auto;
  font-size:.8rem;
  letter-spacing:.06em;
  color:var(--primary);
  white-space:nowrap;
}

/* ---------- quick links ---------- */

.quick-links{
  display:flex;
  flex-wrap:wrap;
  gap:.6rem;
  margin-top:1.4rem;
}
.quick-link{
  display:inline-flex;
  align-items:center;
  gap:.55rem;
  padding:.65rem 1.2rem;
  border-radius:999px;
  border:1px solid var(--line);
  background:var(--glass);
  font-size:.88rem;
  color:var(--ink-muted);
  transition:all .3s var(--ease);
}
.quick-link:hover{
  color:var(--ink);
  border-color:var(--primary);
  background:var(--glass-hover);
  transform:translateY(-2px);
}

/* ============================================================
   11. ABOUT
============================================================ */

img.about-portrait{
  width:100%;
  max-width:340px;
  aspect-ratio:4/5;
  object-fit:cover;
  border-radius:var(--radius);
  border:1px solid var(--line);
  margin-bottom:2rem;
}
/* One large photo with the rest packed around it, the way an artist page
   handles a gallery. Deliberately the same idea as the project bento so the
   two sections look like they came from the same site — but on a six-column
   track, because photos are the content here rather than labels for
   somewhere else, and they want to be bigger.

   The lead spans three columns and two rows; everything after it takes one
   cell. With a single photo that still resolves to one large image, so the
   page is not waiting on a second upload to look finished. */
/* Clipped to a couple of rows until asked otherwise. The clip lives here and
   not on the grid, because a ::after on a grid becomes a grid item and the
   fade would be given a cell of its own. */
.photo-wall-wrap{ position:relative; }
.photo-wall-wrap.is-clipped{
  max-height:420px;
  overflow:hidden;
}
.photo-wall-wrap.is-clipped::after{
  content:'';
  position:absolute;
  inset-inline:0;
  bottom:0;
  height:150px;
  pointer-events:none;
  background:linear-gradient(0deg,var(--bg) 8%,transparent);
}

.photo-wall{
  display:grid;
  grid-template-columns:repeat(6,1fr);
  grid-auto-rows:124px;
  gap:.6rem;
  /* Photos of different proportions take different numbers of cells, which
     leaves holes wherever the next one does not fit. dense backfills them, so
     the wall stays solid instead of gap-toothed. */
  grid-auto-flow:dense;
}

/* Each photo takes the cell its own proportions ask for — a portrait two rows
   tall, a landscape two columns wide. A grid of equal squares would crop every
   portrait through the chin. */
.photo-wall__item--tall{ grid-row:span 2; }
.photo-wall__item--wide{ grid-column:span 2; }
.photo-wall__item{
  position:relative;
  overflow:hidden;
  border-radius:var(--radius-sm);
  border:1px solid var(--line);
  background:var(--glass);
}
/* Three rows rather than two: at two the lead cell is nearly two and a half
   times as wide as it is tall, and a square photograph put through that loses
   most of itself top and bottom. */
.photo-wall__item:first-child{
  grid-column:span 3;
  grid-row:span 3;
  border-radius:var(--radius);
}
.photo-wall__open{
  display:block;
  width:100%;
  height:100%;
  padding:0;
  border:0;
  background:none;
  cursor:zoom-in;
}
.photo-wall__item img{
  width:100%;
  height:100%;
  object-fit:cover;
  /* Biased above centre. The cell is rarely the photo's own proportions, and
     centring the crop cuts the top off a head — these are almost all
     portraits, where the face sits in the upper half. */
  object-position:center 30%;
  display:block;
  transition:transform .5s var(--ease);
}
.photo-wall__item:hover img{ transform:scale(1.05); }

.photo-wall__more{
  display:inline-flex;
  align-items:center;
  gap:.5rem;
  margin-top:1.1rem;
  padding:.6rem 1.3rem;
  border-radius:999px;
  border:1px solid var(--line-strong);
  background:var(--glass);
  font-size:.82rem;
  letter-spacing:.04em;
  cursor:pointer;
  transition:border-color .3s var(--ease),background .3s var(--ease);
}
.photo-wall__more:hover{ border-color:var(--primary); background:var(--glass-hover); }
.photo-wall__more svg{ transition:transform .3s var(--ease); }
.photo-wall__more[aria-expanded="true"] svg{ transform:rotate(180deg); }

.photo-wall figcaption{
  position:absolute;
  inset-inline:0;
  bottom:0;
  padding:1.6rem .8rem .6rem;
  font-size:.74rem;
  line-height:1.35;
  color:#F5F1EC;
  background:linear-gradient(0deg,rgba(4,4,6,.85),transparent);
  opacity:0;
  transition:opacity .35s var(--ease);
}
.photo-wall__item:hover figcaption{ opacity:1; }

@media (max-width:760px){
  .photo-wall{ grid-template-columns:repeat(4,1fr); grid-auto-rows:104px; }
  .photo-wall__item:first-child{ grid-column:span 4; grid-row:span 2; }
  .photo-wall-wrap.is-clipped{ max-height:330px; }
}

/* ---------- lightbox ---------- */

.lightbox{
  position:fixed;
  inset:0;
  z-index:300;
  display:flex;
  align-items:center;
  justify-content:center;
  gap:.5rem;
  padding:clamp(1rem,4vw,3rem);
  background:color-mix(in srgb,#050505 82%,transparent);
  backdrop-filter:blur(22px) saturate(140%);
  -webkit-backdrop-filter:blur(22px) saturate(140%);
  animation:overlayIn .28s var(--ease) both;
}
.lightbox[hidden]{ display:none; }

.lightbox__frame{
  display:flex;
  flex-direction:column;
  align-items:center;
  gap:.9rem;
  min-width:0;
}
.lightbox__image{
  max-width:min(100%,1100px);
  /* Room left under it for the caption line, so a tall photograph does not
     push the caption off the bottom of the screen. */
  max-height:calc(100dvh - 9rem);
  object-fit:contain;
  border-radius:var(--radius-sm);
  box-shadow:0 30px 80px rgba(0,0,0,.6);
  animation:modalIn .34s var(--ease) both;
}
.lightbox__meta{
  display:flex;
  align-items:center;
  gap:.9rem;
  font-size:.78rem;
  color:var(--ink-muted);
}
.lightbox__count{ font-family:var(--font-mono); color:var(--ink-faint); }

/* Left is previous in both directions. Mirroring these with the writing
   direction makes a photo viewer feel broken to anyone who has used one. */
.lightbox__nav,
.lightbox__close{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  flex:none;
  border-radius:999px;
  border:1px solid var(--line);
  background:color-mix(in srgb,var(--bg-raised) 70%,transparent);
  color:var(--ink-muted);
  cursor:pointer;
  transition:color .3s var(--ease),border-color .3s var(--ease),background .3s var(--ease);
}
.lightbox__nav{ width:46px; height:46px; }
.lightbox__nav:hover,
.lightbox__close:hover{ color:var(--ink); border-color:var(--line-strong); background:var(--glass-hover); }

.lightbox__close{
  position:absolute;
  top:1.1rem;
  right:1.2rem;
  width:40px; height:40px;
  font-size:1.5rem;
  line-height:1;
}

@media (max-width:640px){
  .lightbox{ gap:.2rem; padding:.8rem; }
  .lightbox__nav{ width:38px; height:38px; }
  .lightbox__image{ max-height:calc(100dvh - 8rem); }
}

.about-text p{ color:var(--ink-muted); font-size:1.05rem; }

/* "Core Projects & Subdivisions" — a list, not cards. The projects page is
   already built out of cards, and repeating that shape here would read as the
   same section printed twice. A rule down the start edge in each brand's own
   colour separates them with less furniture. */
.about-divisions__heading{
  margin-top:2.8rem;
  font-size:var(--t-label);
  letter-spacing:.16em;
  text-transform:uppercase;
  color:var(--ink-muted);
}
.about-divisions{ list-style:none; margin:1.3rem 0 0; padding:0; }
.about-division{
  --project-accent:var(--primary);
  margin-top:1.2rem;
  padding-inline-start:1.1rem;
  border-inline-start:2px solid var(--project-accent);
}
.about-division__head{
  display:flex;
  flex-wrap:wrap;
  align-items:baseline;
  gap:.7rem;
  margin:0;
}
.about-division__name{
  font-family:var(--font-display);
  font-weight:700;
  font-size:.98rem;
  letter-spacing:.1em;
}
.about-division__kind{
  font-size:.7rem;
  letter-spacing:.14em;
  text-transform:uppercase;
  color:var(--project-accent);
}
.about-division__text{ margin:.35rem 0 0; color:var(--ink-muted); font-size:.95rem; }

/* The motto closes the page, so it gets air above it and nothing after. */
.about-motto{ margin-top:2.8rem; }

/* Same reason as the contact address: the motto is Cyrillic, `plaintext`
   would hand its line an LTR direction of its own, and it would drift to the
   far side of the column away from the label introducing it. The dir="auto"
   in the template keeps the Russian reading correctly inside the line. */
html[dir="rtl"] .about-motto{ unicode-bidi:isolate; }
.about-motto__lead{
  display:block;
  font-size:var(--t-label);
  letter-spacing:.14em;
  text-transform:uppercase;
  color:var(--ink-muted);
  margin-bottom:.45rem;
}
.about-motto__text{
  font-family:var(--font-display);
  font-size:1.02rem;
  letter-spacing:.04em;
  color:var(--ink);
}

.skill-years{
  margin-top:.6rem;
  font-size:.72rem;
  letter-spacing:.14em;
  text-transform:uppercase;
  color:var(--primary);
}

/* ============================================================
   12. LINKS PAGE
============================================================ */

#linkSearch{
  width:100%;
  max-width:420px;
  padding:.8rem 1.15rem;
  margin-bottom:2rem;
  border-radius:999px;
  border:1px solid var(--line);
  background:var(--glass);
  color:var(--ink);
  font-family:inherit;
}
#linkSearch:focus{ outline:none; border-color:var(--primary); }

.category-tabs{
  display:flex;
  flex-wrap:wrap;
  gap:.55rem;
  margin-bottom:2.5rem;
}
.category-tab{
  padding:.5rem 1.1rem;
  border-radius:999px;
  border:1px solid var(--line);
  background:var(--glass);
  font-size:.8rem;
  cursor:pointer;
  transition:all .3s var(--ease);
}
.category-tab:hover{ border-color:var(--project-accent,var(--line-strong)); }
.category-tab.is-active{
  background:var(--project-accent,var(--primary));
  border-color:var(--project-accent,var(--primary));
  color:#fff;
}

.category-block{ margin-bottom:2.6rem; }
.category-block h2,
.category-block h3{
  font-size:1.08rem;
  margin-bottom:1rem;
  padding-bottom:.6rem;
  border-bottom:1px solid var(--line);
}

.links-grid{
  display:grid;
  grid-template-columns:repeat(auto-fill,minmax(200px,1fr));
  gap:.7rem;
}
.link-card{
  display:flex;
  align-items:center;
  gap:.8rem;
  padding:.85rem 1.1rem;
  border-radius:var(--radius-sm);
  border:1px solid var(--line);
  background:var(--glass);
  font-size:.92rem;
  transition:all .3s var(--ease);
}
/* Hovering a link says which brand it belongs to, not which company hosts it.
   The category's project colour comes first, so this page highlights exactly
   as a project's own page does: every link under SAMYAR glows SAMYAR's indigo
   whether it is Kick, Steam or GameUp.

   The platform's own colour (set inline per link from links/platforms.py) is
   only the fallback, for the categories that belong to no brand — Music
   Platforms, Social Media, Support — where there is no project colour to use
   and a single site accent would say nothing at all. The site accent is the
   last resort.

   None of the three is declared on this element: a declaration here would beat
   every inherited value and the whole arrangement would collapse to one
   colour. */
.link-card:hover{
  border-color:var(--project-accent,var(--link-accent,var(--primary)));
  background:var(--glass-hover);
  color:var(--project-accent,var(--link-accent,var(--primary)));
  box-shadow:0 8px 30px -16px var(--project-accent,var(--link-accent,var(--primary)));
  transform:translateX(3px);
}

/* Stands in for a mark the site is not allowed to ship. Sized and spaced to
   match .link-card__icon exactly so a row containing one does not visibly
   step out of line. */
.link-card__mono{
  display:grid;
  place-items:center;
  width:22px;
  height:22px;
  flex-shrink:0;
  border-radius:6px;
  border:1px solid currentColor;
  font-family:var(--font-display);
  font-size:.66rem;
  font-weight:700;
  line-height:1;
  opacity:.72;
  transition:opacity .3s var(--ease);
}
.link-card:hover .link-card__mono{ opacity:1; }
html[dir="rtl"] .link-card:hover{ transform:translateX(-3px); }

/* Every logo reduced to its silhouette in one ink. The files behind these are
   a mixture of flat-colour PNGs, gradients and SVGs; masking is what makes a
   row of them read as one set instead of as a ransom note. Slightly dimmed at
   rest so the labels lead, full strength on hover. */
.link-card__icon{
  width:22px;
  height:22px;
  flex-shrink:0;
  background:currentColor;
  opacity:.72;
  -webkit-mask:var(--icon) no-repeat center / contain;
  mask:var(--icon) no-repeat center / contain;
  transition:opacity .3s var(--ease);
}
.link-card:hover .link-card__icon{ opacity:1; }

/* ============================================================
   13. WALLETS
============================================================ */

.wallets-grid{
  display:grid;
  grid-template-columns:repeat(auto-fill,minmax(320px,1fr));
  gap:1rem;
  margin-top:1.8rem;
}
/* --coin is the card's own brand colour, set from the model. Everything
   inside reads it, so a coin is described once rather than in five rules, and
   a ticker the site does not know simply keeps the site accent.

   --coin-ink is the same colour for four coins out of five, and exists for the
   one where it cannot be: Ethereum's brand grey is #3D3E3F, which against this
   background is very nearly the background. On the dark theme its mark almost
   disappears and its Copy and QR labels fall below readable — the card reads
   as disabled rather than as hovered. So anything that has to be legible uses
   --coin-ink and the glow keeps the exact brand colour. On the light theme
   that grey is the right colour and is used as given. */
.wallet-card,.wallet-modal{
  --coin:var(--primary);
  --coin-ink:var(--coin);
}
.wallet-card[data-coin="ETH"],
.wallet-modal[data-coin="ETH"]{ --coin-ink:#9AA0A6; }
[data-theme="light"] .wallet-card[data-coin="ETH"],
[data-theme="light"] .wallet-modal[data-coin="ETH"]{ --coin-ink:var(--coin); }

.wallet-card{
  position:relative;
  padding:1.5rem;
  border-radius:var(--radius);
  border:1px solid var(--line);
  background:var(--glass);
  cursor:pointer;
  overflow:hidden;
  transition:border-color .35s var(--ease),background .35s var(--ease),transform .35s var(--ease);
}
.wallet-card::after{
  content:'';
  position:absolute;
  inset:0;
  border-radius:inherit;
  background:radial-gradient(120% 90% at 50% 0%,color-mix(in srgb,var(--coin) 42%,transparent) 0%,transparent 55%);
  opacity:0;
  pointer-events:none;
  transition:opacity .4s var(--ease);
}
.wallet-card:hover{
  border-color:color-mix(in srgb,var(--coin-ink) 60%,transparent);
  background:var(--glass-hover);
  transform:translateY(-3px);
}
.wallet-card:hover::after{ opacity:.5; }

.wallet-card__head{
  display:flex;
  align-items:center;
  gap:.7rem;
  margin-bottom:1rem;
}

/* The mark sits in the theme's ink until the card is hovered, then takes the
   coin's colour. Doing it that way round keeps the darker marks — Ethereum's
   is very nearly the background — legible while the card is at rest. */
.wallet-card__icon{
  width:26px;
  height:26px;
  flex:none;
  color:var(--ink);
  transition:color .3s var(--ease),transform .35s var(--ease);
}
.wallet-card:hover .wallet-card__icon{
  color:var(--coin-ink);
  transform:scale(1.09);
}

.wallet-card__coin{
  font-family:var(--font-display);
  font-size:1.15rem;
  font-weight:700;
  letter-spacing:.06em;
}
.wallet-card__name{
  font-size:.74rem;
  letter-spacing:.16em;
  text-transform:uppercase;
  color:var(--ink-faint);
}

.wallet-address{
  font-family:var(--font-mono);
  font-size:.72rem;
  line-height:1.5;
  color:var(--ink-muted);
  margin-bottom:1.1rem;
  padding:.65rem .8rem;
  border-radius:var(--radius-sm);
  background:rgba(0,0,0,.28);
  border:1px solid var(--line);
  direction:ltr;
  text-align:left;
  /* One line, cut at the end with an ellipsis. The full value is in the
     title attribute, on the clipboard, and in the QR — nobody types these. */
  white-space:nowrap;
  overflow:hidden;
  text-overflow:ellipsis;
}
[data-theme="light"] .wallet-address{ background:rgba(0,0,0,.04); }

.wallet-card__actions{ display:flex; gap:.5rem; }
.wa-copy,.wa-qr{
  display:inline-flex;
  align-items:center;
  gap:.4rem;
  padding:.5rem 1rem;
  border-radius:999px;
  border:1px solid var(--line-strong);
  background:transparent;
  font-size:.76rem;
  cursor:pointer;
  transition:all .3s var(--ease);
}
.wa-copy:hover,.wa-qr:hover{
  border-color:var(--coin-ink);
  color:var(--coin-ink);
  background:var(--glass-hover);
}
/* Copied stays green in every card: it reports what happened, and reporting it
   in the coin's colour would make success look like just another hover. */
.wa-copy.is-copied{ border-color:var(--success); color:var(--success); }

.wallet-modal__qr{ display:flex; justify-content:center; }
.wallet-modal__qr canvas,
.wallet-modal__qr img{
  background:#fff;
  padding:10px;
  border-radius:var(--radius-sm);
}

/* Frosted, not blacked out: the page stays visible behind the panel, just
   defocused. A flat black sheet reads as "the site went away". */
.wallet-modal-overlay{
  position:fixed;
  inset:0;
  z-index:200;
  display:none;
  align-items:center;
  justify-content:center;
  padding:1.5rem;
  background:color-mix(in srgb,var(--bg) 42%,transparent);
  backdrop-filter:blur(26px) saturate(150%);
  -webkit-backdrop-filter:blur(26px) saturate(150%);
}
.wallet-modal-overlay.is-open{
  display:flex;
  animation:overlayIn .32s var(--ease) both;
}
@keyframes overlayIn{
  from{ opacity:0; backdrop-filter:blur(0); }
  to{ opacity:1; }
}

.wallet-modal{
  position:relative;
  width:100%;
  max-width:360px;
  padding:2.2rem 2rem 2rem;
  text-align:center;
  border-radius:26px;
  /* The pale top edge is what makes a translucent panel read as glass
     rather than as a flat tinted rectangle. */
  border:1px solid rgba(255,255,255,.16);
  background:
    linear-gradient(155deg,rgba(255,255,255,.14),rgba(255,255,255,.04) 42%,transparent 70%),
    color-mix(in srgb,var(--bg-raised) 62%,transparent);
  backdrop-filter:blur(30px) saturate(180%);
  -webkit-backdrop-filter:blur(30px) saturate(180%);
  box-shadow:
    0 24px 70px rgba(0,0,0,.5),
    inset 0 1px 0 rgba(255,255,255,.18);
  animation:modalIn .38s var(--ease) both;
}
@keyframes modalIn{
  from{ opacity:0; transform:translateY(14px) scale(.96); }
  to{ opacity:1; transform:translateY(0) scale(1); }
}
[data-theme="light"] .wallet-modal{
  border-color:rgba(255,255,255,.7);
  background:
    linear-gradient(155deg,rgba(255,255,255,.75),rgba(255,255,255,.35) 45%,transparent 72%),
    color-mix(in srgb,#fff 55%,transparent);
  box-shadow:
    0 24px 70px rgba(18,16,15,.18),
    inset 0 1px 0 rgba(255,255,255,.9);
}

.wallet-modal h3{
  font-size:1.3rem;
  letter-spacing:.06em;
}

/* The mark stands in for the "$SOL" heading, so it has to carry the weight a
   heading did: large, centred, and given room rather than tucked under the
   close button. The disc behind it is what stops it reading as a stray glyph
   floating above the code. */
.wallet-modal__head{
  display:flex;
  align-items:center;
  justify-content:center;
  min-height:72px;
  margin-block:.2rem .4rem;
}
.wallet-modal__icon{
  display:none;
  width:52px;
  height:52px;
  color:var(--coin-ink);
  filter:drop-shadow(0 0 22px color-mix(in srgb,var(--coin) 42%,transparent));
}
.wallet-modal__head.has-icon .wallet-modal__icon{ display:block; }

/* With an icon present the ticker is still there for screen readers — it is
   what names the dialog — it just stops being drawn. */
.wallet-modal__head.has-icon .wallet-modal__ticker{
  position:absolute;
  width:1px; height:1px;
  padding:0; margin:-1px;
  overflow:hidden;
  clip-path:inset(50%);
  white-space:nowrap;
}
.wallet-modal canvas,
.wallet-modal img{ margin:1.4rem auto; border-radius:var(--radius-sm); }
.wallet-modal p{
  font-family:var(--font-mono);
  font-size:.7rem;
  word-break:break-all;
  color:var(--ink-muted);
  margin-bottom:1.2rem;
  direction:ltr;
}
/* Copy Address in the coin's colour too, but deepened toward black rather
   than used raw. Bitcoin's orange and Ethereum's grey both leave white text
   below readable at full strength, and a solid button is the one place on
   this panel where that is not survivable. */
.wallet-modal .btn-solid{
  background:linear-gradient(
    135deg,
    color-mix(in srgb,var(--coin-ink) 68%,#000),
    color-mix(in srgb,var(--coin-ink) 42%,#000)
  );
  box-shadow:0 8px 26px color-mix(in srgb,var(--coin) 32%,transparent);
}

.wallet-modal-close{
  position:absolute;
  top:.6rem; inset-inline-end:1rem;
  font-size:1.6rem;
  line-height:1;
  background:none;
  border:none;
  color:var(--ink-muted);
  cursor:pointer;
}
.wallet-modal-close:hover{ color:var(--ink); }

/* ============================================================
   14. MUSIC
============================================================ */

/* The two accents come from the cover art, set in the markup. The site's own
   colours are the fallback — what a greyscale cover gets — and they are
   written at each point of use rather than declared here, because declaring
   them on .player would override the value inherited from a wrapper and the
   podcast player takes its accents from one. */
.player{
  position:relative;
  max-width:620px;
  padding:1.5rem;
  margin-top:1.8rem;
  border-radius:var(--radius);
  border:1px solid var(--line);
  background:
    linear-gradient(160deg,rgba(255,255,255,.05),transparent 45%),
    var(--glass);
  overflow:hidden;
  isolation:isolate;
}
.player::before{
  content:'';
  position:absolute;
  inset-block-start:0;
  inset-inline:0;
  height:1px;
  background:linear-gradient(90deg,transparent,var(--track-accent,var(--primary)),transparent);
  opacity:.6;
}

/* The cover again, blown up and blurred far past legibility, so the panel
   sits in the record's own light. Blurred with a filter rather than shipped
   pre-blurred: the same file is already being downloaded for the thumbnail,
   and a second copy would be bytes for something nobody looks at directly. */
.player__bg{
  position:absolute;
  inset:0;
  z-index:-1;
  border-radius:inherit;
  overflow:hidden;
  pointer-events:none;
}
.player__bg img{
  width:100%;
  height:100%;
  object-fit:cover;
  /* Scaled up because blur pulls the edges inward and would otherwise leave a
     pale border all the way round. */
  transform:scale(1.35);
  filter:blur(38px) saturate(170%);
  opacity:.6;
}
.player__bg::after{
  content:'';
  position:absolute;
  inset:0;
  background:linear-gradient(155deg,rgba(6,6,8,.5),rgba(6,6,8,.78));
}
[data-theme="light"] .player__bg img{ opacity:.4; }
[data-theme="light"] .player__bg::after{
  background:linear-gradient(155deg,rgba(255,255,255,.72),rgba(255,255,255,.86));
}

.player__badge{
  display:inline-block;
  margin-bottom:1.1rem;
  padding:.25rem .7rem;
  border-radius:999px;
  border:1px solid var(--line);
  font-size:.66rem;
  letter-spacing:.18em;
  text-transform:uppercase;
  color:var(--ink-faint);
}

.player__top{
  display:flex;
  align-items:center;
  gap:1.2rem;
  margin-bottom:1.4rem;
}
.player-cover{
  width:94px; height:94px;
  border-radius:var(--radius-sm);
  object-fit:cover;
  flex-shrink:0;
  box-shadow:0 10px 30px rgba(0,0,0,.45);
}
.player-info{ min-width:0; }
.player-title{
  font-family:var(--font-display);
  font-weight:700;
  font-size:1.1rem;
  line-height:1.25;
}
.player-artist{ margin-top:.2rem; font-size:.88rem; color:var(--ink-muted); }
.player__link{
  display:inline-block;
  margin-top:.6rem;
  font-size:.76rem;
  color:var(--track-accent,var(--primary));
}

/* ---------- progress ---------- */

.player__progress{
  display:flex;
  align-items:center;
  gap:.8rem;
  margin-bottom:1.2rem;
}
.time-current,.time-duration{
  font-family:var(--font-mono);
  font-size:.72rem;
  color:var(--ink-faint);
  min-width:34px;
  direction:ltr;
}
.time-duration{ text-align:end; }
.progress-track{
  position:relative;
  flex:1;
  height:5px;
  border-radius:999px;
  background:var(--line);
  cursor:pointer;
}
.progress-fill{
  height:100%;
  width:0;
  border-radius:999px;
  background:linear-gradient(90deg,var(--track-accent,var(--primary)),var(--track-accent-2,var(--secondary)));
}
/* No handle on the end of the bar. The grab target it needed is kept, though:
   this widens the hit area to 21px without changing what is drawn, so the bar
   stays as thin as it looks and is still easy to hit. */
.progress-track::after{
  content:'';
  position:absolute;
  inset:-8px 0;
}

/* ---------- controls ---------- */

.player__controls{
  display:flex;
  align-items:center;
  gap:.5rem;
  flex-wrap:wrap;
}
.ctrl-btn{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  width:38px; height:38px;
  border-radius:999px;
  border:1px solid var(--line);
  background:transparent;
  color:var(--ink-muted);
  cursor:pointer;
  transition:all .3s var(--ease);
}
.ctrl-btn:hover{ color:var(--ink); border-color:var(--line-strong); }
.ctrl-btn.is-active{ color:var(--primary); border-color:var(--primary); }

.ctrl-btn-play{
  width:52px; height:52px;
  color:#fff;
  border-color:transparent;
  background:linear-gradient(135deg,var(--track-accent,var(--primary)),var(--track-accent-2,var(--secondary)));
  box-shadow:0 6px 24px color-mix(in srgb,var(--track-accent,var(--primary)) 40%,transparent);
}
.ctrl-btn-play:hover{ color:#fff; transform:scale(1.05); }
.ctrl-btn.is-active{
  color:var(--track-accent,var(--primary));
  border-color:var(--track-accent,var(--primary));
}

.speed-btn{
  width:auto;
  padding-inline:.85rem;
  font-family:var(--font-mono);
  font-size:.74rem;
}

.player__volume{
  display:flex;
  align-items:center;
  gap:.5rem;
  margin-inline-start:auto;
  color:var(--ink-faint);
}
.volume-input{
  -webkit-appearance:none;
  appearance:none;
  width:82px;
  height:4px;
  border-radius:999px;
  background:var(--line);
  cursor:pointer;
}
.volume-input::-webkit-slider-thumb{
  -webkit-appearance:none;
  width:12px; height:12px;
  border-radius:50%;
  background:var(--ink-muted);
  cursor:pointer;
}
.volume-input::-moz-range-thumb{
  width:12px; height:12px;
  border:none;
  border-radius:50%;
  background:var(--ink-muted);
  cursor:pointer;
}

@media (max-width:520px){
  .player__top{ flex-direction:column; align-items:flex-start; }
  .player__volume{ margin-inline-start:0; width:100%; }
}

/* ---------- podcast ----------
   One set of controls beside the episode list, rather than a player per
   episode. Side by side on a wide screen because the list is short and the
   two belong together; stacked below that. */
.podcast{
  display:grid;
  grid-template-columns:minmax(0,1fr) minmax(0,.85fr);
  gap:1.1rem;
  align-items:start;
  margin-top:1.8rem;
}
.podcast .player{ margin-top:0; max-width:none; }

.episode-list{
  list-style:none;
  display:flex;
  flex-direction:column;
  gap:.35rem;
  margin:0;
  padding:0;
}
.episode{
  display:flex;
  align-items:center;
  gap:.9rem;
  width:100%;
  padding:.75rem .9rem;
  border-radius:var(--radius-sm);
  border:1px solid transparent;
  background:var(--glass);
  cursor:pointer;
  text-align:start;
  transition:background .3s var(--ease),border-color .3s var(--ease);
}
.episode:hover{ background:var(--glass-hover); border-color:var(--line); }
.episode.is-current{
  border-color:color-mix(in srgb,var(--track-accent,var(--primary)) 45%,transparent);
  background:color-mix(in srgb,var(--track-accent,var(--primary)) 10%,transparent);
}
.episode__number{
  font-family:var(--font-mono);
  font-size:.78rem;
  color:var(--ink-faint);
  min-width:1.4em;
  text-align:center;
}
.episode.is-current .episode__number{ color:var(--track-accent,var(--primary)); }
.episode__title{
  flex:1;
  min-width:0;
  font-size:.95rem;
  overflow:hidden;
  text-overflow:ellipsis;
  white-space:nowrap;
}

/* Three bars that stand still until this episode is the one playing, so the
   list says which is current and whether it is running, not just which was
   clicked last. */
.episode__state{
  display:flex;
  align-items:flex-end;
  gap:2px;
  height:14px;
  opacity:0;
  transition:opacity .3s var(--ease);
}
.episode.is-current .episode__state{ opacity:1; }
.episode__state i{
  width:3px;
  height:5px;
  border-radius:1px;
  background:var(--track-accent,var(--primary));
}
.podcast-player.is-playing ~ .episode-list .episode.is-current .episode__state i{
  animation:eqBar .9s ease-in-out infinite;
}
.podcast-player.is-playing ~ .episode-list .episode.is-current .episode__state i:nth-child(2){ animation-delay:.18s; }
.podcast-player.is-playing ~ .episode-list .episode.is-current .episode__state i:nth-child(3){ animation-delay:.36s; }
@keyframes eqBar{
  0%,100%{ height:4px; }
  50%{ height:14px; }
}

@media (max-width:820px){
  .podcast{ grid-template-columns:1fr; }
}

/* ============================================================
   15. CONTACT
============================================================ */

.contact-form{ max-width:720px; margin-top:2rem; }

/* Hidden from sighted users and screen readers alike, so anything
   typed into it came from a bot. */
.hp-field{
  position:absolute !important;
  width:1px; height:1px;
  overflow:hidden;
  clip-path:inset(50%);
  white-space:nowrap;
}

.contact-form .field{ margin-bottom:1.2rem; }
.contact-form .field-row{
  display:grid;
  grid-template-columns:repeat(auto-fit,minmax(220px,1fr));
  gap:1.2rem;
}
.contact-form label,
.contact-form legend{
  display:block;
  margin-bottom:.5rem;
  font-size:var(--t-label);
  letter-spacing:.14em;
  text-transform:uppercase;
  color:var(--ink-muted);
}
.contact-form input[type="text"],
.contact-form input[type="email"],
.contact-form select,
.contact-form textarea{
  width:100%;
  padding:.9rem 1.05rem;
  border-radius:var(--radius-sm);
  border:1px solid var(--line);
  background:var(--glass);
  color:var(--ink);
  font-family:inherit;
  font-size:.95rem;
  transition:border-color .3s var(--ease),background .3s var(--ease);
}
.contact-form input:focus,
.contact-form select:focus,
.contact-form textarea:focus{
  outline:none;
  border-color:var(--primary);
  background:var(--glass-hover);
}
.contact-form select option{ background:var(--bg-raised); color:var(--ink); }
.contact-form textarea{ resize:vertical; min-height:160px; }
.contact-form button{ margin-top:.6rem; }

.priority-field{ border:none; }
/* Django wraps RadioSelect in its own container, so the flex row has to be
   declared on that child too or the options stack vertically. */
.priority-options,
.priority-options > div,
.priority-options > ul{
  display:flex;
  flex-wrap:wrap;
  gap:.55rem;
}
.priority-options label{
  margin:0;
  display:inline-flex;
  align-items:center;
  gap:.45rem;
  padding:.5rem 1rem;
  border-radius:999px;
  border:1px solid var(--line);
  background:var(--glass);
  font-size:.85rem;
  letter-spacing:0;
  text-transform:none;
  color:var(--ink);
  cursor:pointer;
  transition:all .3s var(--ease);
}
.priority-options label:hover{ border-color:var(--primary); }
.priority-options input[type="radio"]{ accent-color:var(--primary); }

.errorlist{
  margin-top:.4rem;
  font-size:.82rem;
  color:var(--danger);
}
.form-message{
  padding:.9rem 1.2rem;
  margin-bottom:1.6rem;
  border-radius:var(--radius-sm);
  border:1px solid color-mix(in srgb,var(--success) 40%,transparent);
  background:color-mix(in srgb,var(--success) 10%,transparent);
}
.form-errors{
  padding:.9rem 1.2rem;
  margin-bottom:1.4rem;
  border-radius:var(--radius-sm);
  border:1px solid color-mix(in srgb,var(--danger) 40%,transparent);
  background:color-mix(in srgb,var(--danger) 10%,transparent);
}

.contact-direct{ margin-top:3.5rem; }

/* The email address is Latin, and the `plaintext` rule near the top of this
   file gives every line its own direction from its first strong character.
   That is right for prose, and wrong here: the label is Arabic so its line
   hugs the right, the address is Latin so its line hugs the left, and the two
   end up a column apart with nothing between them.

   `isolate` is the narrower tool. The line keeps the paragraph's direction —
   so the address sits under the label it belongs to — while the address
   itself still reads left to right inside it. */
html[dir="rtl"] .contact-direct p{ unicode-bidi:isolate; }

.contact-direct__label{
  display:block;
  font-size:var(--t-label);
  letter-spacing:.14em;
  text-transform:uppercase;
  color:var(--ink-muted);
  margin-bottom:.25rem;
}
/* No `--project-accent` declaration here on purpose. On the contact page the
   template sets it inline per project; on a project's own page it is inherited
   from .project-page. Declaring a default on this element would beat the
   inherited value and every project's contact block would come out blue. The
   fallback belongs at each point of use instead. */
.contact-project{
  display:flex;
  flex-wrap:wrap;
  align-items:center;
  gap:1rem;
  padding:.9rem 1.2rem;
  margin-top:.7rem;
  border-radius:var(--radius-sm);
  border:1px solid var(--line);
  border-inline-start:3px solid var(--project-accent,var(--primary));
  background:var(--glass);
}
.contact-project__name{
  font-family:var(--font-display);
  font-weight:700;
  font-size:.85rem;
  letter-spacing:.08em;
  color:var(--project-accent,var(--primary));
}
.contact-project a{ color:var(--ink-muted); font-size:.9rem; }
.contact-project a:hover{ color:var(--ink); }

.service-list{ margin:1.3rem 0 1.8rem; }
.service-list__item{
  padding:.8rem 0;
  border-bottom:1px solid var(--line);
}
.service-list__item span{
  display:block;
  margin-top:.2rem;
  font-size:.88rem;
  color:var(--ink-muted);
}

/* ============================================================
   16. FOOTER
============================================================ */

.site-footer{
  padding-block:2.5rem;
  border-top:1px solid var(--line);
  text-align:center;
}
.site-footer p{
  max-width:none;
  font-size:.82rem;
  letter-spacing:.06em;
  color:var(--ink-faint);
}
.footer-powered{
  display:flex;
  align-items:center;
  justify-content:center;
  gap:.45rem;
  margin-top:.5rem;
  /* The credit sits under the copyright and should not compete with it. */
  font-size:.74rem;
}
.footer-powered__mark{
  width:17px;
  height:17px;
  color:var(--ink-muted);
  transition:color .3s var(--ease);
}
.site-footer:hover .footer-powered__mark{ color:var(--primary); }

/* ============================================================
   17. MOTION
   .reveal only hides itself once JS has confirmed it can put it
   back. Without the html.has-js guard, a script error would
   leave every page blank.
============================================================ */

.page-fade{ animation:pageFade .55s var(--ease) both; }
@keyframes pageFade{
  from{ opacity:0; transform:translateY(10px); }
  to{ opacity:1; transform:translateY(0); }
}

/* Cross-document view transitions: navigating between pages crossfades
   instead of blinking white. Chromium and Safari 18.2+ honour it; anywhere
   else the rule is ignored and navigation behaves exactly as before. */
@view-transition{ navigation:auto; }
::view-transition-old(root){ animation:vtOut .22s var(--ease) both; }
::view-transition-new(root){ animation:vtIn .32s var(--ease) both; }
@keyframes vtOut{ to{ opacity:0; transform:translateY(-6px); } }
@keyframes vtIn{ from{ opacity:0; transform:translateY(8px); } }

/* The header is the same object across pages, so it should not fade with
   the rest — naming it keeps it fixed while the body crossfades under it. */
.site-header{ view-transition-name:site-header; }

/* Scroll-driven reveal, where the browser supports it: the animation is
   tied to the element's position in the viewport rather than to a one-shot
   JS observer, so it tracks scrolling in both directions. The JS version
   below stays as the fallback and is switched off here to avoid two
   systems animating the same element. */
@supports (animation-timeline: view()){
  html.has-js .reveal{
    opacity:1;
    transform:none;
    transition:none;
    animation:revealRise linear both;
    animation-timeline:view();
    animation-range:entry 8% cover 26%;
  }
  html.has-js .reveal :is(.project-bento,.project-grid,.teaser-grid,.wallets-grid,.work-grid) > *{
    opacity:1;
    transform:none;
    transition:none;
    animation:revealRise linear both;
    animation-timeline:view();
    animation-range:entry 4% cover 22%;
  }
}
@keyframes revealRise{
  from{ opacity:0; transform:translateY(26px); }
  to{ opacity:1; transform:translateY(0); }
}

html.has-js .reveal{
  opacity:0;
  transform:translateY(22px);
  transition:opacity .7s var(--ease),transform .7s var(--ease);
}
html.has-js .reveal.is-visible{
  opacity:1;
  transform:translateY(0);
}

/* Cards inside a revealed section arrive one after another instead of all
   at once. Six steps is enough — beyond that the wait starts to read as lag. */
html.has-js .reveal .project-grid > *,
html.has-js .reveal .teaser-grid > *,
html.has-js .reveal .wallets-grid > *,
html.has-js .reveal .work-grid > *{
  opacity:0;
  transform:translateY(16px);
  transition:opacity .55s var(--ease),transform .55s var(--ease);
}
html.has-js .reveal.is-visible .project-grid > *,
html.has-js .reveal.is-visible .teaser-grid > *,
html.has-js .reveal.is-visible .wallets-grid > *,
html.has-js .reveal.is-visible .work-grid > *{
  opacity:1;
  transform:translateY(0);
}
html.has-js .reveal.is-visible :is(.project-grid,.teaser-grid,.wallets-grid,.work-grid) > :nth-child(1){ transition-delay:.05s; }
html.has-js .reveal.is-visible :is(.project-grid,.teaser-grid,.wallets-grid,.work-grid) > :nth-child(2){ transition-delay:.11s; }
html.has-js .reveal.is-visible :is(.project-grid,.teaser-grid,.wallets-grid,.work-grid) > :nth-child(3){ transition-delay:.17s; }
html.has-js .reveal.is-visible :is(.project-grid,.teaser-grid,.wallets-grid,.work-grid) > :nth-child(4){ transition-delay:.23s; }
html.has-js .reveal.is-visible :is(.project-grid,.teaser-grid,.wallets-grid,.work-grid) > :nth-child(5){ transition-delay:.29s; }
html.has-js .reveal.is-visible :is(.project-grid,.teaser-grid,.wallets-grid,.work-grid) > :nth-child(n+6){ transition-delay:.35s; }

@media (prefers-reduced-motion:reduce){
  html{ scroll-behavior:auto; }
  .meteor,.particle{ display:none; }
  .page-fade,.hero > *,.hero h1,.hero h1::before,.scroll-cue,.now-playing__bars i{ animation:none; }
  @view-transition{ navigation:none; }
  html.has-js .reveal,
  html.has-js .reveal :is(.project-bento,.project-grid,.teaser-grid,.wallets-grid,.work-grid) > *{
    animation:none;
  }
  html.has-js .reveal,
  html.has-js .reveal :is(.project-grid,.teaser-grid,.wallets-grid,.work-grid) > *{
    opacity:1;
    transform:none;
    transition:none;
  }
  *{ transition-duration:.01ms !important; animation-duration:.01ms !important; }
}

/* ============================================================
   18. RESPONSIVE
============================================================ */

@media (max-width:640px){
  :root{ --header-h:66px; }
  .container{ padding-inline:1.15rem; }
  img.hero-avatar{ width:104px; height:104px; }
  .links-grid,.wallets-grid{ grid-template-columns:1fr; }
  .brand{ font-size:.95rem; letter-spacing:.1em; }
}

/* ============================================================
   19. ADMIN PANEL
============================================================ */

.admin-wrap{ max-width:1080px; margin-inline:auto; padding:2rem 1.5rem; }

nav.admin-nav{
  display:flex;
  flex-wrap:wrap;
  gap:.6rem;
  margin-bottom:1.8rem;
  padding-bottom:1.2rem;
  border-bottom:1px solid var(--line);
}
nav.admin-nav a{
  padding:.5rem 1rem;
  border-radius:999px;
  border:1px solid var(--line);
  font-size:.82rem;
  transition:all .3s var(--ease);
}
nav.admin-nav a:hover{ border-color:var(--primary); background:var(--glass-hover); }

.admin-card{
  padding:1.6rem;
  margin-bottom:1.4rem;
  border-radius:var(--radius);
  border:1px solid var(--line);
  background:var(--glass);
}
.admin-card h2{ font-size:1.15rem; margin-bottom:1rem; }

.admin-form label{
  display:block;
  margin-top:1rem;
  margin-bottom:.35rem;
  font-size:var(--t-label);
  letter-spacing:.12em;
  text-transform:uppercase;
  color:var(--ink-muted);
}
.admin-form input,
.admin-form select,
.admin-form textarea{
  width:100%;
  padding:.7rem .9rem;
  border-radius:var(--radius-sm);
  border:1px solid var(--line);
  background:var(--glass);
  color:var(--ink);
  font-family:inherit;
}
.admin-form input:focus,
.admin-form select:focus,
.admin-form textarea:focus{ outline:none; border-color:var(--primary); }

.btn-admin,
.admin-form button{
  display:inline-block;
  margin-top:1rem;
  padding:.6rem 1.3rem;
  border-radius:999px;
  border:1px solid var(--line-strong);
  background:var(--glass);
  font-size:.82rem;
  cursor:pointer;
  transition:all .3s var(--ease);
}
.btn-admin:hover,
.admin-form button:hover{ border-color:var(--primary); background:var(--glass-hover); }

.admin-table{ width:100%; border-collapse:collapse; margin-top:.8rem; }
.admin-table th,
.admin-table td{
  padding:.75rem;
  border-bottom:1px solid var(--line);
  text-align:start;
  font-size:.88rem;
  vertical-align:top;
}
.admin-table th{
  font-size:var(--t-label);
  letter-spacing:.1em;
  text-transform:uppercase;
  color:var(--ink-muted);
}
.admin-table tr.is-unread{ background:var(--glass); }

.inbox-filters{
  display:flex;
  flex-wrap:wrap;
  align-items:center;
  gap:.7rem;
  margin:1rem 0 1.5rem;
}
.inbox-filters select{
  padding:.55rem .85rem;
  border-radius:var(--radius-sm);
  border:1px solid var(--line);
  background:var(--glass);
  color:var(--ink);
  font-family:inherit;
}
.inbox-filters select option{ background:var(--bg-raised); }
.inbox-filters__check{
  display:inline-flex;
  align-items:center;
  gap:.4rem;
  font-size:.86rem;
}
.inbox-filters .btn-admin{ margin-top:0; }

.inbox-tag{
  --project-accent:var(--primary);
  display:inline-block;
  padding:.2rem .6rem;
  border-radius:6px;
  font-size:.7rem;
  font-weight:700;
  letter-spacing:.1em;
  color:var(--project-accent);
  border:1px solid color-mix(in srgb,var(--project-accent) 40%,transparent);
}
.priority-urgent{ color:var(--danger); font-weight:700; }
.priority-low{ color:var(--ink-faint); }

@media (max-width:768px){
  .admin-wrap{ padding:1.4rem 1rem; }
  .admin-table{ font-size:.8rem; }
}
