/* site.css · rev 4e · painted ground on <body> · crow crossing · Sep 6, 2026 */

/* =========================================================
   Artist Portfolio — Shared Stylesheet
   Palette inspired by blue/pink/orange painterly texture
   ========================================================= */

:root {
  /* Core palette */
  --bg-deep:      #0a1230;
  --bg-mid:       #11204a;
  --bg-panel:     #16265a;
  --blue-accent:  #2c4ed1;
  --blue-glow:    #4f7bff;
  --pink-accent:  #e0337a;
  --pink-glow:    #ff5fa2;
  --orange:       #ff7a29;
  --orange-bright:#ff9a3d;
  --text-main:    #f4f6ff;
  --text-dim:     #aab4d6;
  --border-soft:  rgba(255,255,255,0.08);
  --card-bg:      rgba(255,255,255,0.045);
  --card-bg-hover:rgba(255,255,255,0.08);

  --radius-lg: 14px;
  --radius-md: 10px;
  --radius-sm: 6px;

  /* Spacing scale. The v2 flush layout set every gap to 0 and drew its
     divisions with hairline borders instead; these restore the comp's air.
     Change them here, not at the call sites.
       --pad     side + bottom padding on .main (the hero breaks out of it)
       --rhythm  vertical space between sections
       --gap     gutters between cards inside a grid                        */
  --pad:    32px;
  --rhythm: 22px;
  --gap:    14px;

  --font-display: 'Archivo Black', 'Arial Black', sans-serif;
  --font-body: 'Inter', 'Helvetica Neue', Arial, sans-serif;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  scroll-behavior: smooth;
}

body {
  font-family: var(--font-body);
  color: var(--text-main);
  background:
    radial-gradient(ellipse 80% 50% at 80% 0%, rgba(224,51,122,0.18), transparent 60%),
    radial-gradient(ellipse 60% 40% at 100% 100%, rgba(255,122,41,0.10), transparent 60%),
    radial-gradient(ellipse 70% 60% at 0% 30%, rgba(79,123,255,0.18), transparent 60%),
    linear-gradient(160deg, var(--bg-deep) 0%, var(--bg-mid) 55%, #0c1840 100%);
  background-attachment: fixed;
  min-height: 100vh;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

/* Subtle painterly texture overlay */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  opacity: 0.5;
  mix-blend-mode: overlay;
  background-image:
    repeating-linear-gradient(45deg, rgba(255,255,255,0.015) 0px, rgba(255,255,255,0.015) 2px, transparent 2px, transparent 6px),
    repeating-linear-gradient(-45deg, rgba(0,0,0,0.04) 0px, rgba(0,0,0,0.04) 2px, transparent 2px, transparent 8px);
}

a { color: inherit; text-decoration: none; }
img { max-width: 100%; display: block; }

/* =========================================================
   Layout shell
   ========================================================= */

.page-wrap {
  position: relative;
  z-index: 1;
  max-width: 1280px;
  margin: 0 auto;
  padding: 0 28px 60px;
}

/* =========================================================
   Header / Nav
   ========================================================= */

.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: rgba(10, 18, 48, 0.72);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--border-soft);
}

.site-header__inner {
  max-width: 1280px;
  margin: 0 auto;
  padding: 18px 28px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
}

.logo {
  font-family: var(--font-display);
  font-size: 1.4rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-main);
  display: flex;
  align-items: baseline;
  gap: 6px;
}

.logo span {
  color: var(--orange);
}

.main-nav {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}

.main-nav a {
  font-family: var(--font-display);
  font-size: 0.78rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 10px 16px;
  border-radius: 0;
  color: var(--text-dim);
  transition: all 0.2s ease;
  border: 1px solid transparent;
}

.main-nav a:hover {
  color: var(--text-main);
  background: var(--card-bg-hover);
}

.main-nav a.active {
  color: var(--bg-deep);
  background: var(--orange);
  border-color: var(--orange);
}

.nav-toggle {
  display: none;
  background: none;
  border: 1px solid var(--border-soft);
  border-radius: 0;
  color: var(--text-main);
  padding: 8px 12px;
  font-size: 1.2rem;
  cursor: pointer;
}

/* =========================================================
   Hero
   ========================================================= */

.hero {
  padding: 56px 32px 36px;
}

.hero__eyebrow {
  font-family: var(--font-display);
  font-size: 0.8rem;
  letter-spacing: 0.25em;
  text-transform: uppercase;
  color: var(--orange-bright);
  margin: 0 0 10px;
}

.hero h1 {
  font-family: var(--font-display);
  font-size: clamp(2.4rem, 6vw, 4.6rem);
  line-height: 1.05;
  /* background-clip:text only paints inside the element box — Archivo Black's
     descenders fall outside a 1.05 line-height box and come out invisible. */
  padding-bottom: 0.12em;
  margin: 0 0 16px;
  letter-spacing: -0.01em;
  background: linear-gradient(100deg, var(--text-main) 30%, var(--pink-glow) 75%, var(--orange-bright) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

/* Eyebrow-only header (biography) — no h1 to fill the space below it. */
.hero--slim { padding: 32px var(--pad) 0; }
.hero--slim .hero__eyebrow { margin-bottom: 0; }
/* Portrait lines up with the eyebrow: the hero cancels .main's side padding
   and re-adds var(--pad), so bio-layout drops its own left padding to match. */
.hero--slim + .bio-layout { margin-top: 0; padding-left: 0; }

.hero p.lead {
  font-size: 1.1rem;
  color: var(--text-dim);
  max-width: 560px;
}

/* =========================================================
   Buttons
   ========================================================= */

.btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-display);
  font-size: 0.78rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 13px 26px;
  border-radius: 0;
  border: 1px solid var(--border-soft);
  cursor: pointer;
  transition: all 0.2s ease;
}

.btn--primary {
  background: linear-gradient(120deg, var(--orange) 0%, var(--pink-accent) 100%);
  color: #1a0a16;
  border: none;
}

.btn--primary:hover {
  filter: brightness(1.08);
  transform: translateY(-1px);
}

.btn--ghost {
  color: var(--text-main);
  background: transparent;
}

.btn--ghost:hover {
  background: var(--card-bg-hover);
}

/* =========================================================
   Dashboard grid (homepage)
   ========================================================= */

.dashboard {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 20px;
  margin-top: 20px;
}

.card {
  background: none;
  border: none;
  padding: 24px;
  position: relative;
  overflow: hidden;
  transition: transform 0.2s ease;
}

.card:hover {}

.card--span2 { grid-column: span 2; }
.card--span3 { grid-column: span 3; }
.card--span4 { grid-column: span 4; }

.card__label {
  font-family: var(--font-display);
  font-size: 0.7rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--orange-bright);
  margin: 0 0 14px;
}

.card h2, .card h3 {
  font-family: var(--font-display);
  margin: 0 0 10px;
  line-height: 1.15;
}

.card h2 { font-size: 1.5rem; }
.card h3 { font-size: 1.1rem; }

.card p { color: var(--text-dim); margin: 0; }

/* Accent variants */
.card--accent-pink {
  background: linear-gradient(140deg, rgba(224,51,122,0.18), rgba(224,51,122,0.02));

}

.card--accent-orange {
  background: linear-gradient(140deg, rgba(255,122,41,0.18), rgba(255,122,41,0.02));

}

.card--accent-blue {
  background: linear-gradient(140deg, rgba(79,123,255,0.22), rgba(79,123,255,0.02));

}

.eq-bars {
  display: flex;
  align-items: flex-end;
  gap: 3px;
  height: 18px;
  margin-top: 8px;
}

.eq-bars span {
  width: 4px;
  background: var(--orange-bright);
  border-radius: 0;
  animation: eq 1s ease-in-out infinite alternate;
}

.eq-bars span:nth-child(1) { height: 40%; animation-delay: 0s; }
.eq-bars span:nth-child(2) { height: 100%; animation-delay: 0.2s; }
.eq-bars span:nth-child(3) { height: 60%; animation-delay: 0.4s; }
.eq-bars span:nth-child(4) { height: 85%; animation-delay: 0.1s; }
.eq-bars span:nth-child(5) { height: 50%; animation-delay: 0.3s; }

@keyframes eq {
  from { transform: scaleY(0.4); }
  to { transform: scaleY(1); }
}

/* Headlines list */
.headline-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.headline-list li {
  padding-bottom: 12px;
  border-bottom: 1px solid var(--border-soft);
}

.headline-list li:last-child { border-bottom: none; padding-bottom: 0; }

.headline-tag {
  display: inline-block;
  font-family: var(--font-display);
  font-size: 0.62rem;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  padding: 3px 9px;
  border-radius: 0;
  margin-bottom: 6px;
}

.headline-tag--design { background: rgba(79,123,255,0.22); color: #b9caff; }
.headline-tag--art { background: rgba(224,51,122,0.22); color: #ffb6d4; }

.headline-list a {
  font-weight: 600;
  display: block;
  transition: color 0.2s ease;
}

.headline-list a:hover { color: var(--orange-bright); }

/* Daily challenge widget */
.challenge-box {
  border: 1px dashed rgba(255,122,41,0.4);
  padding: 16px;
  margin-top: 12px;
}

.challenge-box__meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 10px;
  flex-wrap: wrap;
}

.challenge-box__date {
  font-family: var(--font-display);
  font-size: 0.65rem;
  color: var(--text-dim);
  letter-spacing: 0.15em;
  text-transform: uppercase;
}

.challenge-box__tags {
  display: flex;
  gap: 6px;
}

.challenge-box__category,
.challenge-box__difficulty {
  font-family: var(--font-display);
  font-size: 0.6rem;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  padding: 3px 8px;
  border: 1px solid var(--border-soft);
  color: var(--text-dim);
}

.challenge-box__difficulty { color: var(--orange-bright); border-color: rgba(255,122,41,0.35); }

.challenge-box__prompt {
  font-family: var(--font-display);
  font-size: 1.1rem;
  line-height: 1.35;
  color: var(--text-main);
}

/* Mini gallery grid (for dashboard preview + galleries) */
.mini-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
  margin-top: 14px;
}

.mini-grid__item {
  aspect-ratio: 1;
  border-radius: 0;
  background: linear-gradient(135deg, var(--blue-glow), var(--pink-accent), var(--orange));
  background-size: 200% 200%;
  opacity: 0.85;
  transition: transform 0.25s ease, opacity 0.25s ease;
}

.mini-grid__item:hover { transform: scale(1.04); opacity: 1; }

.mini-grid__item.v2 { background-position: 60% 20%; }
.mini-grid__item.v3 { background-position: 20% 80%; }
.mini-grid__item.v4 { background-position: 80% 60%; }
.mini-grid__item.v5 { background-position: 40% 100%; }
.mini-grid__item.v6 { background-position: 90% 10%; }

/* Stat row */
.stat-row {
  display: flex;
  gap: 28px;
  flex-wrap: wrap;
}

.stat {
  display: flex;
  flex-direction: column;
}

.stat__value {
  font-family: var(--font-display);
  font-size: 2rem;
  color: var(--orange-bright);
}

.stat__label {
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-dim);
}

/* =========================================================
   Generic section headers (used on sub-pages)
   ========================================================= */

.section-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  margin: 0 0 var(--gap);
  padding: 0;
  flex-wrap: wrap;
}

.section-header h2 {
  font-family: var(--font-display);
  font-size: clamp(1.6rem, 4vw, 2.4rem);
  margin: 0;
}

.section-header p {
  color: var(--text-dim);
  margin: 0;
}

/* =========================================================
   Gallery grid
   ========================================================= */

.gallery-grid {
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--gap);
  margin-right: 0;
}

.gallery-grid figure {
  margin: 0;
  overflow: hidden;
  border-radius: var(--radius-md);
}

.gallery-grid .ph,
.product-card .ph,
.blog-post .ph {
  aspect-ratio: 4/5;
  background: linear-gradient(135deg, var(--blue-glow), var(--pink-accent) 55%, var(--orange));
  background-size: 220% 220%;
  position: relative;
}

.gallery-grid figcaption {
  padding: 14px 16px;
}

.gallery-grid figcaption h3 {
  font-family: var(--font-display);
  font-size: 0.95rem;
  margin: 0 0 4px;
}

.gallery-grid figcaption p {
  margin: 0;
  font-size: 0.8rem;
  color: var(--text-dim);
}

/* Variation classes for gradient placeholders */
.ph.p1 { background-position: 0% 0%; }
.ph.p2 { background-position: 100% 0%; }
.ph.p3 { background-position: 0% 100%; }
.ph.p4 { background-position: 100% 100%; }
.ph.p5 { background-position: 50% 50%; }
.ph.p6 { background-position: 30% 70%; }
.ph.p7 { background-position: 70% 30%; }
.ph.p8 { background-position: 20% 50%; }
.ph.p9 { background-position: 80% 20%; }

/* Fallback for .ph without a pN class (e.g. shop secondary items) */
.product-card .ph:not([class*=" p"]) { background-position: 40% 60%; }

/* =========================================================
   Shop grid
   ========================================================= */

.shop-grid {
  padding: 0;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--gap);
  margin-right: 0;
}

.product-card {
  border-radius: var(--radius-md);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  transition: border-color 0.2s ease, transform 0.2s ease;
}

.product-card:hover { transform: translateY(-2px); }

.product-card .ph { aspect-ratio: 1; }

.product-card__body {
  padding: 14px 16px 18px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  flex: 1;
}

.product-card__title { font-weight: 700; font-size: 0.95rem; }
.product-card__price {
  font-family: var(--font-display);
  color: var(--orange-bright);
  font-size: 1rem;
}

.product-card__cat {
  font-size: 0.7rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-dim);
}

/* =========================================================
   Blog list
   ========================================================= */

.blog-list {
  padding: 32px;
  display: flex;
  flex-direction: column;
  gap: 0;
}

.blog-post {
  display: grid;
  grid-template-columns: 220px 1fr;
  gap: 24px;
  padding: 20px;
  border-radius: var(--radius-md);
  align-items: center;
}

.blog-post .ph {
  aspect-ratio: 4/3;
  border-radius: var(--radius-sm);
}

.blog-post__date {
  font-size: 0.72rem;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--orange-bright);
  margin-bottom: 8px;
}

.blog-post h3 {
  font-family: var(--font-display);
  font-size: 1.3rem;
  margin: 0 0 8px;
}

.blog-post p { color: var(--text-dim); margin: 0 0 12px; }

/* Empty state — shown until posts load, removed by js/blog.js when they do. */
.blog-empty {
  padding: 0 32px 32px;
  color: var(--text-dim);
}

/* Single post: the same list container, re-rendered. Measure is capped for
   reading rather than for the grid the list uses. */
.blog-single { padding: 0 32px 32px; max-width: 680px; }

.blog-single h2 {
  /* Titles keep their line breaks: the JSON stores a real newline and this
     honours it, which means the title stays plain escaped text — no <br> and
     no raw HTML in a field that comes from a file. The list view has no
     pre-line, so the same newline collapses to a space in the card. */
  white-space: pre-line;
  font-family: var(--font-display);
  font-size: clamp(1.6rem, 3.4vw, 2.4rem);
  margin: 6px 0 18px;
}

.blog-single__body { color: var(--text-dim); }
.blog-single__body p { margin: 0 0 16px; }
.blog-single__body a { color: var(--orange-bright); }
.blog-single__body img { margin: 18px 0; }
.blog-single__body code {
  font-size: 0.9em;
  color: var(--text-main);
  background: var(--card-bg);
  padding: 1px 5px;
}

/* =========================================================
   Bio page
   ========================================================= */

.bio-layout {
  padding: 32px;
  display: grid;
  grid-template-columns: 320px 1fr;
  gap: 36px;
  margin-top: 30px;
  align-items: start;
}

.bio-portrait {
  aspect-ratio: 3/4;
  border-radius: 0;
  display: block;
  width: 100%;
  object-fit: cover;
  background: linear-gradient(160deg, var(--blue-glow), var(--pink-accent) 60%, var(--orange));
}

/* The painted frame sits BEHIND the photo, which covers all but a hairline
   of it — so what shows is wherever the paint's ragged edge happens to reach
   past the photo, not a drawn border. Reveal width and zoom are the knobs;
   the frame image itself only appears once FR-02 is present. */
.bio-frame { line-height: 0; }

.bio-aside__actions {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-top: 18px;
}

.bio-aside__actions .btn { justify-content: center; }

.bio-content h2 {
  font-family: var(--font-display);
  font-size: clamp(1.8rem, 4vw, 2.6rem);
  margin-top: 0;
}

.bio-content p { color: var(--text-dim); }

.timeline {
  padding: 0 32px 32px;
  margin-top: 30px;
  border-left: 2px solid var(--border-soft);
  padding-left: 24px;
  display: flex;
  flex-direction: column;
  gap: 22px;
}

.timeline__item { position: relative; }

.timeline__item::before {
  content: "";
  position: absolute;
  left: -29px;
  top: 4px;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--orange-bright);
  box-shadow: 0 0 0 4px rgba(255,122,41,0.15);
}

.timeline__year {
  font-family: var(--font-display);
  font-size: 0.85rem;
  color: var(--orange-bright);
  letter-spacing: 0.1em;
}

.timeline__item h4 { margin: 4px 0; font-size: 1.05rem; }
.timeline__item p { margin: 0; color: var(--text-dim); }

/* =========================================================
   Footer
   ========================================================= */

.site-footer {
  position: relative;
  z-index: 1;
  border-top: 1px solid var(--border-soft);
  margin-top: 60px;
  padding: 36px 28px;
}

.site-footer__inner {
  max-width: 1280px;
  margin: 0 auto;
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 16px;
}

.site-footer__inner p { color: var(--text-dim); font-size: 0.85rem; margin: 0; }

.social-links { display: flex; gap: 14px; }

.social-links a {
  font-family: var(--font-display);
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-dim);
  transition: color 0.2s ease;
}

.social-links a:hover { color: var(--orange-bright); }

/* =========================================================
   Responsive
   ========================================================= */

@media (max-width: 980px) {
  .dashboard { grid-template-columns: repeat(2, 1fr); }
  .card--span3 { grid-column: span 2; }
  .card--span4 { grid-column: span 2; }
  .gallery-grid { grid-template-columns: repeat(2, 1fr); }
  .shop-grid { grid-template-columns: repeat(2, 1fr); }
  .bio-layout { grid-template-columns: 1fr; }
  .bio-aside { max-width: 320px; }
}

@media (max-width: 680px) {
  .site-header__inner { flex-wrap: wrap; }
  .main-nav {
    display: none;
    width: 100%;
    flex-direction: column;
    gap: 4px;
  }
  .main-nav.open { display: flex; }
  .main-nav a { text-align: center; }
  .nav-toggle { display: inline-block; }

  .dashboard { grid-template-columns: 1fr; }
  .card--span2, .card--span3, .card--span4 { grid-column: span 1; }
  .gallery-grid { grid-template-columns: 1fr 1fr; }
  .shop-grid { grid-template-columns: 1fr 1fr; }
  .blog-post { grid-template-columns: 1fr; }
  .mini-grid { grid-template-columns: repeat(3, 1fr); }
}

/* =========================================================
   Sidebar Dashboard Shell (v2 layout)
   ========================================================= */

body.has-sidebar {
  background:
    radial-gradient(ellipse 70% 50% at 15% 0%, rgba(224,51,122,0.22), transparent 55%),
    radial-gradient(ellipse 60% 45% at 100% 15%, rgba(255,122,41,0.16), transparent 55%),
    radial-gradient(ellipse 80% 60% at 50% 100%, rgba(79,123,255,0.16), transparent 60%),
    linear-gradient(160deg, #070b22 0%, #0c1330 55%, #0a0f2a 100%);
  background-attachment: fixed;
}

.shell {
  position: relative;
  z-index: 1;
  width: 100%;
  margin: 0;
  padding: 0;
  display: flex;
  gap: 0;
  align-items: stretch;
}

.sidebar {
  width: 272px;
  flex-shrink: 0;
  background: rgba(255,255,255,0.035);
  padding: 24px;
  display: flex;
  flex-direction: column;
  /* Space between the sidebar's blocks: profile, Studio nav, Galleries
     nav, Recent Activity, Now Playing. The blocks that carry a rule
     (.sidebar__profile's border-bottom, .sidebar__panel's border-top)
     keep their own padding on top of this, so those read slightly
     airier than the two nav groups — intentional. */
  gap: 18px;
  position: sticky;
  top: 0;
}

.sidebar__profile { display: flex; align-items: center; gap: 12px; padding-bottom: 16px; border-bottom: 1px solid var(--border-soft); margin-bottom: 0; }

.sidebar__avatar {
  width: 46px;
  height: 46px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--orange), var(--pink-accent));
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display);
  font-size: 1.2rem;
  color: #1a0a16;
  flex-shrink: 0;
}

.sidebar__name { font-weight: 800; font-size: 1.02rem; line-height: 1.2; }
.sidebar__role { font-size: 0.78rem; color: var(--text-dim); }

.sidebar__label {
  font-family: var(--font-display);
  font-size: 0.65rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--text-dim);
  margin: 0 0 10px;
}

.sidebar-nav { display: flex; flex-direction: column; gap: 3px; }

.sidebar-nav a {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border-radius: 0;
  font-weight: 600;
  font-size: 0.92rem;
  color: var(--text-dim);
  transition: all 0.2s ease;
}

.sidebar-nav a:hover { color: var(--text-main); background: rgba(255,255,255,0.04); }
.sidebar-nav a.active { background: rgba(255,255,255,0.09); color: var(--text-main); }

.sidebar-nav .dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.sidebar-nav .count { margin-left: auto; font-size: 0.78rem; color: var(--text-dim); font-weight: 500; }

.dot--pink   { background: var(--pink-glow); }
.dot--orange { background: var(--orange-bright); }
.dot--violet { background: #b58cff; }
.dot--blue   { background: var(--blue-glow); }
.dot--teal   { background: #4fe0c9; }
.dot--gold   { background: #c8922a; }

.sidebar__panel {
  padding: 0;
  border-top: 1px solid var(--border-soft);
  padding-top: 16px;
}

/* From the studio speakers — text only, no art, no player. Sizes here are
   the sidebar's own: the label is .sidebar__label untouched, the song sits
   at the nav-link size, the artist at the activity-meta size. Nothing new. */
.speakers[hidden] { display: none; }

.speakers__song {
  margin: 0;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text-main);
}

.speakers__artist {
  margin: 2px 0 0;
  font-size: 0.75rem;
  color: var(--text-dim);
}

.speakers__link {
  display: inline-block;
  margin-top: 8px;
  font-family: var(--font-display);
  font-size: 0.6rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--orange-bright);
}

.speakers__link:hover { color: var(--text-main); }

.main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--rhythm);
  padding: 0 var(--pad) var(--pad);
}

/* The hero stays full-bleed: it cancels .main's side padding rather than
   .main special-casing it. Both variants -- the dashboard slideshow and
   the gallery/biography page header. */
.hero-slideshow,
.hero {
  margin-left: calc(-1 * var(--pad));
  margin-right: calc(-1 * var(--pad));
}

.dash-hero__eyebrow {
  font-family: var(--font-display);
  font-size: 0.72rem;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--orange-bright);
  margin: 4px 0 14px;
}

.dash-hero h1 {
  font-family: var(--font-display);
  font-size: clamp(2.2rem, 4.6vw, 3.6rem);
  line-height: 1.08;
  margin: 0 0 14px;
}

.dash-hero h1 .accent {
  background: linear-gradient(100deg, var(--pink-glow), var(--orange-bright));
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

.dash-hero p.lead { color: var(--text-dim); max-width: 620px; font-size: 1.02rem; }
.dash-hero__actions { display: flex; gap: 12px; margin-top: 18px; flex-wrap: wrap; }

.stat-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--gap); margin: 0; }

.stat-card {
  /* Squarer, like the comp. The height was never the padding — it was the
     browser's default <p> margins: at 1.9rem the value carried ~30px above
     AND below, so a two-line location tile ran to 218px. Both children get
     their margins set here; nothing else drives the height. */
  padding: 20px 22px;
  border-radius: var(--radius-md);
}

.stat-card .label {
  font-family: var(--font-display);
  font-size: 0.65rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--text-dim);
  margin: 0 0 10px;
}

.stat-card .value { font-family: var(--font-display); font-size: 1.9rem; margin: 0; }

/* Second line under a value (e.g. which tool shipped last). Same family and
   tracking as .label so the tile keeps one voice, just dimmer and not caps. */
.stat-card__sub {
  font-family: var(--font-display);
  font-size: 0.6rem;
  letter-spacing: 0.12em;
  color: var(--text-dim);
  margin: 6px 0 0;
}

/* Junk drawer tile: photo fills whatever is left under the label. The photo is
   opaque, unlike its neighbours — it is a photograph, so it has to be — and it
   gets the same painted peek as every other tile from the ::before above. */
.stat-card--junk { display: flex; flex-direction: column; }

.junk-slot {
  position: relative;
  flex: 1;
  min-height: 92px;
  overflow: hidden;
  /* Stands in until a photo exists in img/junk/, matching how the painted
     assets degrade to gradients elsewhere. */
  background: linear-gradient(150deg, var(--blue-glow), var(--pink-accent) 60%, var(--orange));
}

.junk-slot img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.junk-slot__line {
  position: absolute;
  left: 10px;
  bottom: 8px;
  margin: 0;
  z-index: 1;
  font-size: 0.62rem;
  font-style: italic;
  letter-spacing: 0.14em;
  color: var(--text-main);
  text-shadow: 0 1px 6px rgba(6,9,28,0.9);
}

.feature-banner {
  position: relative;
  overflow: hidden;
  padding: 32px 32px;
  border-radius: var(--radius-lg);
  margin: 0;
  display: flex;
  align-items: center;
}

.feature-banner::after { display: none; }

.feature-banner__content { position: relative; z-index: 1; }

.feature-banner__tag {
  font-family: var(--font-display);
  font-size: 0.65rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--orange-bright);
  margin-bottom: 8px;
}

.feature-banner h2 { font-family: var(--font-display); font-size: clamp(1.6rem, 3.4vw, 2.4rem); margin: 0 0 10px; color: var(--text-main); }

.feature-banner a.enter {
  font-family: var(--font-display);
  font-size: 0.78rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-main);
}

.section-title { font-family: var(--font-display); font-size: clamp(1.3rem, 2.6vw, 1.8rem); margin: 0; }
.gallery-cards-section { margin: 0; }

.gallery-cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--gap); margin: 0; }

.gallery-cards a.gallery-card {
  position: relative;
  border-radius: var(--radius-md);
  overflow: hidden;
  aspect-ratio: 4/5;
    background: linear-gradient(135deg, var(--blue-glow), var(--pink-accent) 55%, var(--orange));
  background-size: 240% 240%;
  display: flex;
  align-items: flex-end;
  padding: 22px;
  transition: transform 0.2s ease;
}

.gallery-cards a.gallery-card:hover { transform: translateY(-3px); }

.gallery-cards a.gallery-card::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, transparent 40%, rgba(6,9,28,0.88) 100%);
}

.gallery-card__body { position: relative; z-index: 1; }

.gallery-card__tag {
  font-family: var(--font-display);
  font-size: 0.6rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--orange-bright);
  margin-bottom: 6px;
  display: block;
}

.gallery-card__title { font-family: var(--font-display); font-size: 1.1rem; }
.gallery-card__title .count { font-family: var(--font-body); font-size: 0.78rem; color: var(--text-dim); font-weight: 600; margin-left: 6px; }

.dash-bottom { display: grid; grid-template-columns: 1fr; gap: 0; margin: 0; }

.feed-list { display: flex; flex-direction: column; gap: 14px; }

.feed-item {
  display: flex;
  gap: 14px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--border-soft);
  align-items: flex-start;
}

.feed-item:last-child { border-bottom: none; padding-bottom: 0; }
.feed-item .dot { margin-top: 6px; }

.feed-item__meta {
  font-family: var(--font-display);
  font-size: 0.62rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-dim);
  margin-bottom: 4px;
}

.feed-item__time { margin-left: auto; font-size: 0.78rem; color: var(--text-dim); white-space: nowrap; }
.feed-row { display: flex; width: 100%; gap: 10px; align-items: flex-start; }

.upcoming-item {
  display: flex;
  gap: 12px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--border-soft);
  align-items: flex-start;
}

.upcoming-item:last-child { border-bottom: none; padding-bottom: 0; }

.upcoming-item__date {
  font-family: var(--font-display);
  font-size: 0.65rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  background: rgba(255,122,41,0.18);
  color: var(--orange-bright);
  border-radius: 0;
  padding: 6px 8px;
  text-align: center;
  min-width: 52px;
  line-height: 1.2;
}

.upcoming-item__title { font-weight: 700; font-size: 0.92rem; }
.upcoming-item__sub { font-size: 0.8rem; color: var(--text-dim); }

.shell-footer {
  position: relative;
  z-index: 1;
  padding: 24px 0 8px;
  margin-top: 4px;
  width: 100%;
  margin-left: 0;
  margin-right: 0;
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
  color: var(--text-dim);
  font-size: 0.85rem;
  padding-left: 24px;
  padding-right: 24px;
}

.shell-footer a { color: var(--text-dim); font-weight: 600; }
.shell-footer a:hover { color: var(--orange-bright); }
.shell-footer__links { display: flex; gap: 18px; }


/* =========================================================
   Hero slideshow / video placeholder
   ========================================================= */

.hero-slideshow {
  position: relative;
  overflow: hidden;
  /* Sized for the one live slide (eyebrow, two-line h1, one-line lead, one
     button). The slides are absolutely positioned, so this number IS the
     banner's height; a longer slide needs it raised. Was 440 for the
     three-slide template; 340 since Sep 6, 2026. */
  min-height: 340px;
  background: var(--card-bg);
  margin: 0;
}

.hero-slide {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  /* Top-aligned, not centred: the first line of hero text has to sit on the
     same baseline as the name in the sidebar. --hero-lead-in is the padding
     that puts it there; measured, not guessed.
     (Sep 6, 2026: the slideshow left index.html for a few hours and came back
     to the top the same day; the crow lives in the stat grid now.) */
  justify-content: flex-start;
  --hero-lead-in: 30.5px;
  padding: var(--hero-lead-in) 40px 40px;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.7s ease;
  background: linear-gradient(135deg, var(--blue-glow), var(--pink-accent) 55%, var(--orange));
  background-size: 240% 240%;
}

.hero-slide.active { opacity: 1; visibility: visible; }

.hero-slide::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(100deg, rgba(6,9,28,0.78) 10%, rgba(6,9,28,0.35) 60%, rgba(6,9,28,0.7) 100%);
}

.hero-slide.hs1 { background-position: 20% 30%; }
.hero-slide.hs2 { background-position: 80% 20%; }
.hero-slide.hs3 { background-position: 50% 80%; }

.hero-slide__content { position: relative; z-index: 1; max-width: 640px; }

.hero-slide .dash-hero__eyebrow { margin: 0 0 14px; }
.hero-slide h1 {
  font-family: var(--font-display);
  font-size: clamp(2.2rem, 4.6vw, 3.6rem);
  line-height: 1.08;
  margin: 0 0 14px;
}
.hero-slide h1 .accent {
  background: linear-gradient(100deg, var(--pink-glow), var(--orange-bright));
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}
.hero-slide p.lead { color: var(--text-main); opacity: 0.85; max-width: 560px; font-size: 1.02rem; }
.hero-slide__actions { display: flex; gap: 12px; margin-top: 18px; flex-wrap: wrap; }

.play-badge {
  position: absolute;
  top: 24px;
  right: 24px;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 8px;
  background: rgba(6,9,28,0.55);
  border: 1px solid var(--border-soft);
  border-radius: 0;
  padding: 8px 16px 8px 12px;
  font-family: var(--font-display);
  font-size: 0.65rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-main);
}

.play-badge__icon {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--orange-bright);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.play-badge__icon::after {
  content: "";
  width: 0;
  height: 0;
  border-style: solid;
  border-width: 5px 0 5px 8px;
  border-color: transparent transparent transparent #1a0a16;
  margin-left: 1px;
}

.hero-slide-dots {
  position: absolute;
  bottom: 24px;
  left: 40px;
  z-index: 2;
  display: flex;
  gap: 8px;
}

.hero-slide-dots button {
  width: 28px;
  height: 4px;
  border-radius: 0;
  border: none;
  background: rgba(255,255,255,0.25);
  cursor: pointer;
  padding: 0;
  transition: background 0.2s ease;
}

.hero-slide-dots button.active { background: var(--orange-bright); }

@media (max-width: 680px) {
  .hero-slideshow { min-height: 280px; }
  .hero-slide { padding: 26px; }
  .hero-slide-dots { left: 26px; }
  .play-badge { top: 14px; right: 14px; }
}

@media (max-width: 1100px) {
  .stat-grid { grid-template-columns: repeat(2, 1fr); }
  .gallery-cards { grid-template-columns: repeat(2, 1fr); }
  .dash-bottom { grid-template-columns: 1fr; }
}

@media (max-width: 880px) {
  .shell { flex-direction: column; }
  .sidebar { width: 100%; position: relative; top: 0; }
}

@media (max-width: 560px) {
  .stat-grid { grid-template-columns: 1fr 1fr; }
  .gallery-cards { grid-template-columns: 1fr 1fr; }
  .shell { padding: 14px; gap: 14px; }
}

/* One place to tighten the whole scale on small screens. */
@media (max-width: 880px) {
  :root { --pad: 20px; --rhythm: 16px; --gap: 10px; }
}

/* =========================================================
   Grid dividers — RETIRED
   The flush layout drew its divisions with border-right /
   border-bottom on grid children plus nth-child() rules to strip
   the last one in each row. Gutters replace all of it: each card
   now carries its own border + radius (see the card rules above).
   Those nth-child() rules were also wrong below 1100px, where the
   column count changes but "3n" / "4n" don't -- that bug leaves
   with them.
   ========================================================= */

/* index.html only -- the other 9 pages put sections straight in .main.
   Repeats the rhythm so the extra nesting doesn't collapse the spacing.
   (Briefly carried a 24px top on Sep 6, 2026 while the stat tiles opened the
   page; the slideshow is back on top and brings its own lead-in.) */
.main-content { padding: 0; display: flex; flex-direction: column; gap: var(--rhythm); }

/* Sidebar recent activity (compact) */
.sidebar-activity { display: flex; flex-direction: column; gap: 0; }
.sidebar-activity__item {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  padding: 10px 0;
  border-bottom: 1px solid var(--border-soft);
  font-size: 0.78rem;
  line-height: 1.35;
}
.sidebar-activity__item:first-child { padding-top: 0; }
.sidebar-activity__item:last-child { border-bottom: none; padding-bottom: 0; }
.sidebar-activity__meta {
  font-family: var(--font-display);
  font-size: 0.6rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-dim);
  margin-bottom: 2px;
}
.sidebar-activity__time { font-size: 0.68rem; color: var(--text-dim); margin-top: 2px; }


/* Daily challenge in the sidebar (index.html only, since Sep 7, 2026).
   Same .challenge-box as before, tightened for a 218px column: the meta row
   stacks, the prompt drops a size, and a share line closes the box. */
.sidebar .challenge-box { margin-top: 0; padding: 12px; }
.sidebar .challenge-box__meta {
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  margin-bottom: 8px;
}
.sidebar .challenge-box__prompt { font-size: 0.92rem; }
.challenge-box__share {
  margin: 10px 0 0;
  font-size: 0.72rem;
  line-height: 1.4;
  color: var(--text-dim);
}
.challenge-box__share span { color: var(--orange-bright); white-space: nowrap; }

/* =========================================================
   Real images in gallery grid
   ========================================================= */

.gallery-grid figure img {
  width: 100%;
  aspect-ratio: 4/5;
  object-fit: cover;
  object-position: center top;
  display: block;
  transition: transform 0.35s ease, opacity 0.25s ease;
}

.gallery-grid figure:hover img { transform: scale(1.03); opacity: 0.92; }

/* Era section dividers spanning full grid width */
.era-header {
  grid-column: 1 / -1;
  padding: 28px 32px 10px;
  border-bottom: 1px solid var(--border-soft);
  border-right: none !important;
}

.era-header h2 {
  font-family: var(--font-display);
  font-size: 0.72rem;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--orange-bright);
  margin: 0;
}

/* Subsection divider (within masks) */
.sub-era-header {
  grid-column: 1 / -1;
  padding: 16px 32px 6px;
  border-right: none !important;
  border-bottom: 1px solid var(--border-soft);
}

.sub-era-header h3 {
  font-family: var(--font-display);
  font-size: 0.62rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--text-dim);
  margin: 0;
}

/* Linked section headers */
.era-header a,
.sub-era-header a {
  color: inherit;
  display: flex;
  align-items: baseline;
  gap: 10px;
  text-decoration: none;
}

.era-header a:hover h2,
.sub-era-header a:hover h3 { color: var(--text-main); }

.view-all-link {
  font-family: var(--font-display);
  font-size: 0.58rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--orange-bright);
  margin-left: auto;
  white-space: nowrap;
  flex-shrink: 0;
}

/* Status tags */
.tag {
  display: inline-block;
  font-family: var(--font-display);
  font-size: 0.58rem;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  padding: 2px 7px;
  margin-top: 6px;
  border: 1px solid var(--border-soft);
  color: var(--text-dim);
}
.tag--sold { border-color: rgba(224,51,122,0.4); color: #ff9ec8; }
.tag--kept { border-color: rgba(79,123,255,0.4); color: #b9caff; }
.tag--fan  { border-color: rgba(181,140,255,0.4); color: #d4b8ff; }

/* Embedded flipbook block */
.book-embed {
  grid-column: 1 / -1;
  border-right: none !important;
  border-bottom: 1px solid var(--border-soft);
  background: #07080f;
}

.book-embed iframe {
  display: block;
  width: 100%;
  height: min(80vh, 720px);
  border: 0;
}

/* =========================================================
   Just Ask Crista — soft-launch callout
   Deliberately quieter than the rest of the site: the dark
   warm surface + gold of the /ask/ section, so it reads as
   the doorway to a different room.
   ========================================================= */

.ask-callout {
  display: block;
  position: relative;
  margin: 0;
  padding: 30px 32px;
  border-radius: var(--radius-lg);
  background:
    radial-gradient(ellipse 70% 60% at 20% 0%, rgba(200,146,42,0.10), transparent 60%),
    linear-gradient(170deg, #17130e 0%, #16120d 60%, #120e0a 100%);
  border-top: 1px solid var(--border-soft);
  border-bottom: 1px solid var(--border-soft);
  transition: background 0.2s ease;
}

.ask-callout__eyebrow {
  font-family: var(--font-display);
  font-size: 0.62rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: #e0a83b;
  margin: 0 0 10px;
}

.ask-callout h2 {
  font-family: var(--font-display);
  font-size: clamp(1.3rem, 3vw, 1.9rem);
  margin: 0 0 10px;
  color: #f2ede3;
}

.ask-callout p {
  margin: 0 0 14px;
  color: #a89e8c;
  max-width: 560px;
  font-size: 0.95rem;
}

.ask-callout .enter {
  font-family: var(--font-display);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #e0a83b;
}

.ask-callout:hover .enter { color: #f2ede3; }
/* =========================================================
   PAINTED UI LAYER — real impasto assets · rev 4
   Companion to ui-graphics-manifest.md

   Architecture: BG-01 (seamless tile) is the GROUND — the
   absolute first layer of the site, on <body>, tiled at 2x
   density (2352x1760 file -> 1176x880 CSS), scrolling with the
   content. Everything else rests on top of it: the shell and
   panel are transparent, the sidebar is a translucent dark
   overlay, and cards/footer are scrims. Nothing anywhere gets
   its own copy of the painting; every surface is a window onto
   the one ground layer.

   BG-02 / BG-02b are therefore OPTIONAL under this model —
   rules kept for shop/product cards if you ever want them,
   but nothing requires painting them.

   How activation works: js/paint.js probes img/ui/ and adds a
   class to <html> per existing file (e.g. ui-bg01). Until an
   asset is uploaded, its rules are inert and the gradient
   placeholders above remain.

   Tuning knobs marked ▸ (scrim opacities, stroke weights).
   ========================================================= */

/* ---- BG-01 · the ground (first layer of the whole site) ---- */
/* Overrides the body gradients above whenever the file exists.
   ▸ if the repeat ever reads on very wide screens, raise the
     tile size (e.g. 1400px auto) — the density cost is tiny. */
html.ui-bg01 body,
html.ui-bg01 body.has-sidebar {
  background: url(../img/ui/ui-bg-01-hero-field.jpg) repeat top left / 1176px 880px;
  background-attachment: unset;  /* cancels the fixed-attachment on the gradient fallback */
}

/* BG-04 is RETIRED under this architecture — the painting is
   the page ground. Don't paint it; no rules reference it. */

/* Sidebar: translucent overlay on the same painting.
   ▸ raise the alpha if sidebar text fights the texture;
   ▸ delete the backdrop-filter line if scrolling ever feels heavy. */
html.ui-bg01 .sidebar {
  /* Pure indigo glaze — overrides both the white-tint base rule
     AND the body.has-sidebar gradient.

     ▸ TUNE THE COLOUR, NOT THE ALPHA. A translucent layer converges
     on its own colour as alpha rises, so the colour sets the darkest
     the sidebar can ever get. The old rgb(14,18,52) had luminance 19.6
     — the hero's left scrim measures 20.7, so no alpha could push the
     sidebar below the hero and it read as the same level. rgb(4,6,26)
     is luminance 6.4, low enough to leave real headroom.

     Alpha backed off 0.74 → 0.52 on Aug 22, 2026, once the painting
     itself was darkened — the glaze does less work now and 48% of the
     tile shows through (was 26%).
     Darker column: lower these three numbers. More painting: lower
     the alpha further, which lightens it too. */
  background: rgba(4, 6, 26, 0.52) !important;
}

/* Hero slides: transparent windows onto the field. Their own
   ::before scrim (defined above) keeps the text readable, and
   the per-slide background-positions simply stop mattering. */
html.ui-bg01 .hero-slideshow,
html.ui-bg01 .hero-slide {
  background: transparent;
}

/* ---- NO SCRIMS ----------------------------------------------------
   The painting itself was re-worked for legibility (Aug 22, 2026), so
   every surface below is a clear window onto it: no tint, no wash, no
   gradient. Tiles are defined by their 1px border and their gutters
   alone. When a painted asset exists for a surface its rule switches
   the IMAGE on — those rules are untouched and still live further
   down; only the flat colour fills are gone.

   If a passage of the tile ever fights the text, fix it in the
   painting, not with a scrim here.                                   */

/* .hero (gallery / biography page headers) carries no background at
   all in the base sheet, so it needs no rule — it is already clear.

   .hero-slide::before DOES need one: the base sheet fades it from
   rgba(6,9,28,0.78) across the slide, and deleting this override
   would bring that straight back. */
html.ui-bg01 .hero-slide::before {
  background: none;
}

/* .stat-card and .card likewise carry no base background — .card is
   `background: none` and .stat-card never had one — so both are clear
   with no rule at all. */

/* The sidebar is ONE surface: the tile plus a single indigo glaze.
   .sidebar__panel used to share the .card scrim above, which stacked a
   second background inside that glaze and cut a hard-edged rectangle
   across the sidebar. Nothing inside the sidebar gets its own fill —
   the only exception is the active nav item, which AC-01 replaces with
   a painted smear. */
html.ui-bg01 .sidebar__panel {
  background: transparent;
}

/* Footer (now inside .main): clear like everything else. Not a tile,
   but it was the last flat fill on the page and leaving it would have
   read as a dark band across the bottom of an otherwise open layout. */
html.ui-bg01 .shell-footer {
  border-radius: var(--radius-md);
  padding-left: 24px;
  padding-right: 24px;
}

/* ---- BG-02 · quiet field (OPTIONAL under rev 2) ---- */
/* The continuous-field model doesn't need these paintings.
   Kept for one place a second texture could still earn a spot:
   shop product cards. Skip painting BG-02 unless you want that. */

html.ui-bg02 .product-card {
  background:
    linear-gradient(rgba(10,14,34,0.72), rgba(10,14,34,0.72)),  /* ▸ scrim */
    url(../img/ui/ui-bg-02-quiet-field.jpg) center / cover;
}

/* ---- BG-03 · vivid strip ---- */
/* The comp's bottom band is a genuinely distinct zone of dense
   paint, so unlike everything else it keeps its own image. */

html.ui-bg03 .feature-banner {
  background:
    linear-gradient(100deg, rgba(6,9,28,0.78) 15%, rgba(6,9,28,0.38) 65%, rgba(6,9,28,0.6) 100%),  /* ▸ scrim */
    url(../img/ui/ui-bg-03-vivid-strip.jpg) center / cover;
}

/* ---- FR-01/02/03 · painted frames (border-image, stretch) ---- */

/* SLICE NUMBERS ARE PER-ASSET. border-image-slice counts inward from
   each edge in SOURCE pixels, so it changes whenever the file changes.
   FR-01 is 768x619 and its painted rim sits 156/150/163/149 in from
   top/right/bottom/left — measured off the alpha channel, not guessed.
   Repaint or re-export the PNG and these must be re-measured.

   TWO knobs, and they do different things.

   border-width sets how thick the rim renders on screen.

   The SLICE sets how much of the source rim gets used. Each asset is
   ~50px of transparent margin, then ~100px of solid paint. Slicing the
   full band gives an unbroken ring — too heavy and too complete next
   to the comp. Slicing only margin+35px takes just the ragged OUTER
   FRINGE of the paint, whose coverage varies all the way round, so the
   rim thins and drops out in places and the corners peek. That is what
   the comp does. Numbers below are per-asset: measured margin + 35.
   ▸ raise the +35 for a more solid rim, lower it for a more broken one.

   STRETCH, not round. round tiles the side slice along each edge and
   at these scales the repeat is instantly obvious — the rim reads as
   a beaded ribbon. stretch pulls one continuous stroke down each side,
   which is what impasto actually looks like. Tested both. */

html.ui-fr01 .btn--ghost {
  /* A button is small and needs to read as a hittable outline, so it
     keeps the FULL slice — a broken rim on something this size just
     looks like a rendering fault. Thinner instead. */
  border: 7px solid transparent;                                 /* ▸ stroke weight */
  border-image: url(../img/ui/ui-frame-01-magenta.png) 156 150 163 149 stretch;
  background: rgba(6,9,28,0.35);
}
html.ui-fr01 .btn--ghost:hover { background: rgba(6,9,28,0.55); }

/* ---- PAINTED PEEK · the whole frame behind, cropped to a hairline ----

   Not a border. The frame graphic is laid behind the surface at --peek-zoom
   and only the outermost --reveal of it is allowed to show. The source has
   ~22-46px of transparent margin before the paint starts, and that inset is
   ragged — it varies edge to edge — so blowing the graphic up past the box
   makes the fringe straddle the reveal: paint clears the surface in some
   places and falls short in others. That is what makes the peek irregular
   instead of a ruled line, and it is the whole point of the effect.

   119% is tuned on FR-02: at the bio portrait's size it lands paint on about
   a third of the perimeter. It is a PERCENTAGE, not pixels, so every surface
   gets the same proportion regardless of how big the tile is.

   PER-FRAME, THOUGH. Each painting's fringe sits a different distance in
   from the file edge — median inset measured off the alpha channel is 49px
   for FR-02, 61px for FR-01, 91px for FR-03 — so one zoom cannot serve all
   three. FR-03 in particular needs a much harder push before its paint
   clears the edge at all. Repaint an asset and re-measure.
   ▸ raise --peek-x/y for more paint, lower them for sparser hits.
   ▸ raise --reveal for a wider peek (the artwork's own raggedness reads
     without any zoom at all around 8-10px).                              */

.bio-frame,
.stat-card,
.feature-banner,
.sidebar {
  --reveal: 1px;                                                 /* ▸ peek width */
  --peek-x: 119%;                                                /* ▸ zoom (FR-02) */
  --peek-y: 119%;
}

/* The portrait is an opaque photo, so the frame can simply sit behind it. */
html.ui-fr02 .bio-frame {
  padding: var(--reveal);
  background: url(../img/ui/ui-frame-02-orange.png)
    center / var(--peek-x) var(--peek-y) no-repeat;
}

/* Tiles have no opaque fill of their own — the painting shows through them
   by design — so the frame goes on a ::before that is masked down to the
   reveal. Same graphic, same zoom, same look; the tile stays a clear window.
   Each surface names its own frame; nothing else differs. */
html.ui-fr01 .stat-card {
  --frame: url(../img/ui/ui-frame-01-magenta.png);
  --peek-x: 126%; --peek-y: 127%;                                /* ▸ zoom (FR-01) */
}
html.ui-fr02 .stat-card:nth-child(2),
html.ui-fr02 .stat-card:nth-child(4) {
  --frame: url(../img/ui/ui-frame-02-orange.png);
  --peek-x: 119%; --peek-y: 119%;
}
html.ui-fr03 .stat-card:nth-child(3) {
  --frame: url(../img/ui/ui-frame-03-blue.png);
  --peek-x: 138%; --peek-y: 132%;                                /* ▸ zoom (FR-03) */
}
html.ui-fr02 .feature-banner         { --frame: url(../img/ui/ui-frame-02-orange.png); }

.stat-card { position: relative; }

.stat-card::before,
.feature-banner::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: var(--reveal);
  background: var(--frame) center / var(--peek-x) var(--peek-y) no-repeat;
  /* Punch the middle out so only the reveal survives. */
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask-composite: exclude;
  pointer-events: none;
}

/* ---- FL-01/02 · painted button fills ---- */

html.ui-fl01 .btn--primary {
  background: url(../img/ui/ui-fill-01-orange.png) center / 100% 100% no-repeat;
  border: none;
  color: #1a0a16;
}
html.ui-fl01 .btn--primary:hover { filter: brightness(1.1) saturate(1.05); transform: translateY(-1px); }

/* ---- AC-01 · nav smear ---- */

html.ui-ac01 .sidebar-nav a.active {
  background: url(../img/ui/ui-accent-01-nav-smear.png) center left / 100% 100% no-repeat;
  color: var(--text-main);
}
html.ui-ac01 .main-nav a.active {
  background: url(../img/ui/ui-accent-01-nav-smear.png) center / 100% 100% no-repeat;
  border-color: transparent;
  color: var(--text-main);
}

/* ---- AC-02 · headline texture (background-clip: text) ---- */

html.ui-ac02 .hero h1,
html.ui-ac02 .hero-slide h1 .accent {
  background-image: url(../img/ui/ui-accent-02-text-clip.jpg);
  background-size: cover;
  background-position: center;
}

/* ---- AC-03 · painted dot sprite (5 daubs, 64px each) ---- */
/* Sprite order: magenta · orange · violet · coral · blue.
   teal + gold dots keep their CSS circles until painted. */

html.ui-ac03 .dot--pink,
html.ui-ac03 .dot--orange,
html.ui-ac03 .dot--violet,
html.ui-ac03 .dot--blue {
  background-color: transparent;
  background-image: url(../img/ui/ui-accent-03-dots.png);
  background-size: 500% 100%;
  background-repeat: no-repeat;
  border-radius: 0;
}
html.ui-ac03 .dot--pink   { background-position: 0% 0; }
html.ui-ac03 .dot--orange { background-position: 25% 0; }
html.ui-ac03 .dot--violet { background-position: 50% 0; }
html.ui-ac03 .dot--blue   { background-position: 100% 0; }

/* ---- AC-04 · avatar daub ---- */

html.ui-ac04 .sidebar__avatar {
  background: url(../img/ui/ui-accent-04-avatar-ring.png) center / cover;
}

/* ---- AC-05 · painted divider (footer rule) ---- */

html.ui-ac05 .shell-footer,
html.ui-ac05 .site-footer { border-top: none; }
html.ui-ac05 .shell-footer::before,
html.ui-ac05 .site-footer::before {
  content: "";
  display: block;
  width: 100%;
  height: 14px;
  background: url(../img/ui/ui-accent-05-divider.png) center / contain no-repeat;
  margin-bottom: 14px;
  flex-basis: 100%;
}

/* The speakers panel takes the painted divider too — it is the one sidebar
   panel that reads as its own note rather than a list, so the painted rule
   above it does the separating instead of the hairline. Same gating as the
   footer: no asset, no rule, hairline stays. */
html.ui-ac05 .speakers { border-top: none; }
html.ui-ac05 .speakers::before {
  content: "";
  display: block;
  width: 100%;
  height: 14px;
  background: url(../img/ui/ui-accent-05-divider.png) center / contain no-repeat;
  margin-bottom: 14px;
}

/* ---- AC-07 · painted sidebar edge ---- */
/* Replaces the hairline that used to define the sidebar: a painted
   stroke pinned to its right edge. It REPEATS rather than stretches
   — tested both, and stretching this stroke over a tall sidebar pulls
   its meander into straight fibrous streaks, while repeating keeps
   the wave reading as paint. The asset's top and bottom line up
   within 1px, so the joins don't show.
   ▸ width below should match the painted stroke's weight. Started
     at 30px, which read as a heavy blue rope — far more present than
     the comp's divider, which barely registers. 14px keeps the
     texture and the wander while letting it recede. Below ~8px it
     flattens to a plain line and the paint is lost.
   .sidebar is position:sticky, which is enough to anchor this. */

/* The stroke sits inside the sidebar's box, so the content has to
   clear it — without this the right-aligned gallery counts collide
   with the paint. */
html.ui-ac07 .sidebar { padding-right: 30px; }

html.ui-ac07 .sidebar::after {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  /* Peek, like the tiles: the smear is drawn --edge-width wide but the
     strip is only --reveal wide, so all that shows is the asset's outermost
     column. AC-07's paint stops a median 21px in from its right edge and
     ranges 8-32px, so that column catches the smear where it runs wide and
     misses where it pulls back — a broken hairline, not a band.
     Re-measured Sep 3 2026, third AC-07 asset. The fringe has tightened
     with each repaint and the push has had to shrink to follow it:
         240x634  inset median 42px (3-63)  -> --edge-push 3px
         240x1000 inset median 21px (8-32)  -> --edge-push 1.25px
         240x1000 inset median 12px (0-39)  -> --edge-push 0.5px  (current)
     0.5px gives paint on 77% of rows at mean alpha 162/255, matching the
     first asset's 80%/176. Sample the alpha channel again after ANY
     repaint — none of these numbers survive one.
     CAUTION: this asset's paint reaches its own right edge on some rows
     (min inset 0), so push 0 already yields 43% and the whole usable
     range is now ~0-1px. That is sub-pixel tuning on a 1px strip, and
     browsers round it differently. If it starts drifting between
     browsers, raise --reveal on .sidebar to 2-3px to put the tuning back
     in whole pixels — that thickens the stroke, so it is a look change.
     ▸ --edge-push slides the smear out past the sidebar, the way the tiles
       zoom their frame past the box: more push, more paint in the strip. */
  --edge-width: 20px;
  --edge-push: 0.5px;                                 /* ▸ zoom */
  width: var(--reveal);
  /* Fixed tile height, not auto, and it still REPEATS at the larger
     asset size rather than stretching once to fill the column.
     440px is the old 280px carried across to the taller graphic: the
     634px asset was compressed to 0.44 of its height, and 1000 x 0.44
     is 440, so the meander keeps the same wander and the same density
     it had before. At the asset's natural aspect a 20px-wide tile is
     ~83px tall and reads as braid trim; past ~700px it flattens to a
     straight streak. ▸ tile height */
  background: url(../img/ui/ui-accent-07-sidebar-edge.png)
    right calc(-1 * var(--edge-push)) top 0 / var(--edge-width) 440px repeat-y;
  pointer-events: none;
}

/* Below 880px the sidebar stops being a column and stacks above the
   main content full-width, where a right-edge stroke is meaningless. */
@media (max-width: 880px) {
  html.ui-ac07 .sidebar::after { display: none; }
}

/* ---- AC-06 · event date chips ---- */

html.ui-ac06 .upcoming-item__date {
  background: url(../img/ui/ui-accent-06-chip.png) center / cover;
  color: #1a0a16;
  text-shadow: 0 1px 0 rgba(255,255,255,0.25);
}

/* ---- CROW · scroll-scrubbed crossing in the stat grid (index.html only) ----
   Two slots of the four-column stat grid, the right-hand pair. The crow
   walks across the painted ground as the page scrolls: down and it comes
   out from behind the Ideas tile, crosses, pauses mid-way to look about
   (that is what the footage does), and leaves at the right edge; up and it
   turns and walks back. Nothing plays on its own.

   THE IMAGE: img/ui/ui-crow-walk.webp is a grid of 72 frames, 3 columns x
   24 rows, each 960x458, cut from crow_scrub.mp4 (15 s, crow on black; the
   black is keyed out so the ground shows through). Frames run ROW-MAJOR:
   frame 0 top-left, 1 top-middle, 2 top-right, 3 second row left, and so
   on, filling the grid exactly. The first five source frames are skipped:
   they are a dark-grey fade, not black, and keyed as a slab. background-size 300% auto
   makes one frame exactly the layer's width; the layer's aspect-ratio makes
   it exactly one frame tall.

   THE MATH: two animations on one scroll timeline.
     crow-x  steps(3, jump-none), 24 iterations  -> column advances every frame
     crow-y  steps(24, jump-none), 1 iteration   -> row advances every 3 frames
   jump-none yields exactly N positions, first and last included, so N is
   the slot count along that axis, not the gaps. (steps(N-1) puts every
   frame a fraction off and shows two part-crows; already been there.)

   THE TIMELINE is the page scroll: at scroll 0 the crow is at frame 0 (just
   behind the tile edge) and the crossing completes after --crow-run px of
   scrolling. The block sits under the slideshow, ~460px down, and is gone
   after ~720px on a desktop; the run ends before that so the whole walk
   happens in view.
   ▸ --crow-run: longer = slower crossing the block may scroll away from
     before it finishes; shorter = brisker.

   FROM BEHIND THE TILE: the block pulls left by one gutter so its edge is
   the Ideas tile's right border, and clips its overflow, so the crow's
   first pixels appear from under that border rather than in the gap.
   (The tiles are clear windows onto the ground, so the crow cannot really
   pass behind them; this is the same effect without an opaque tile.)

   TURNING AROUND: js/main.js adds .is-reversed to .crow-walk while the page
   scrolls up. The crow layer mirrors and reverses: same spot on the page,
   facing left, walking left as you scroll. The pose jumps at the turn and
   the crow may shift a little sideways, because its speed in the footage
   is not perfectly even.

   GATED like every painted asset: no file, no crow layer (display:none
   until js/paint.js finds the grid) -- the two slots stay empty ground.
   Reduced motion, and any browser without scroll-driven animations, parks
   the crow mid-crossing (frame 36 = row 12 of 0-23 = 0% 52.1739%) and never moves
   it.

   FLOOR SHADOW: .crow-walk::before is a second copy of the same frame,
   drawn under the crow, flipped about the floor line, squashed flat,
   leaned back toward the tail, blurred and blackened. Because it is the
   same image on the same scroll timeline it follows every pose and flips
   with the bird on reverse scroll. The floor line is where the feet meet
   the padding in the frames (40px of 458 from the bottom, measured on
   the grid), so it does not move while the crow does.
   ▸ --shadow-squash  how flat (0.10 short, 0.20 long)
   ▸ --shadow-lean    which way it falls: negative = behind the tail
   ▸ --shadow-dark    strength
   A cast shadow alone leaves the bird floating: the legs are thin, so the
   shadow's mass lands where the body is, out along the lean, not under
   the feet. ::after is the anchor -- the same frame squashed almost flat
   with no lean, so it hugs the floor line directly under the bird's
   footprint. Both pseudo-elements sit under the crow layer (z-index).
   ▸ --contact-squash  thickness of the pool under the feet
   ▸ --contact-dark    its strength
   ▸ --floor-line      where both shadows hinge, as a % of the frame's
                       height. The feet bottom out at 91.3%; 87.5% (set by
                       eye, Sep 7 2026) tucks the shadows up under and
                       slightly behind the toes instead of below them.

   REBUILD from new footage: tools/crow_strip.ps1 -- same key, crop, count,
   columns. Its output names every number below that has to follow.        */

.crow-walk {
  position: relative;
  grid-column: span 2;
  margin-left: calc(-1 * var(--gap));            /* butt up to the Ideas tile */
  /* Clip the LEFT edge only (the tuck behind the tile); top, right and
     bottom stay open so the floor shadow can spill a little below the row. */
  clip-path: inset(-100px -100px -100px 0);
  display: flex;
  align-items: flex-end;                          /* crow stands on the row's floor */
  --crow-run: 600px;                                             /* ▸ runway */
  --shadow-squash: 0.14;                                         /* ▸ flatness */
  --shadow-lean: -35deg;                                         /* ▸ direction */
  --shadow-dark: 0.7;                                            /* ▸ strength */
  --contact-squash: 0.05;                                        /* ▸ pool thickness */
  --contact-dark: 0.85;                                          /* ▸ pool strength */
  --floor-line: 87.5%;                                           /* ▸ hinge height */
}

.crow-walk__crow,
.crow-walk::before,
.crow-walk::after {
  display: none;
  width: 100%;
  aspect-ratio: 960 / 458;                                       /* one frame */
  background: url(../img/ui/ui-crow-walk.webp) 0% 52.1739% / 300% auto no-repeat;
}
.crow-walk__crow { position: relative; z-index: 1; }
.crow-walk::before,
.crow-walk::after {
  content: "";
  position: absolute;
  z-index: 0;
  left: 0;
  bottom: 0;                                      /* same box as the crow layer */
  transform-origin: 50% var(--floor-line);        /* the floor line */
  pointer-events: none;
}
.crow-walk::before {                              /* cast shadow */
  transform: skewX(var(--shadow-lean)) scaleY(calc(-1 * var(--shadow-squash)));
  filter: brightness(0) blur(6px) opacity(var(--shadow-dark));
}
.crow-walk::after {                               /* contact pool under the feet */
  transform: scaleY(calc(-1 * var(--contact-squash)));
  filter: brightness(0) blur(5px) opacity(var(--contact-dark));
}
html.ui-crow .crow-walk__crow,
html.ui-crow .crow-walk::before,
html.ui-crow .crow-walk::after { display: block; }

@supports (animation-timeline: scroll()) {
  html.ui-crow .crow-walk__crow,
  html.ui-crow .crow-walk::before,
  html.ui-crow .crow-walk::after {
    animation: crow-x steps(3, jump-none) 24 both,
               crow-y steps(24, jump-none) 1 both;
    animation-timeline: scroll(root), scroll(root);   /* after the shorthand, or it resets */
    animation-range: 0px var(--crow-run), 0px var(--crow-run);
  }
  html.ui-crow .crow-walk.is-reversed .crow-walk__crow {
    transform: scaleX(-1);
    animation-direction: reverse, reverse;
  }
  html.ui-crow .crow-walk.is-reversed::before {
    transform: scaleX(-1) skewX(var(--shadow-lean)) scaleY(calc(-1 * var(--shadow-squash)));
    animation-direction: reverse, reverse;
  }
  html.ui-crow .crow-walk.is-reversed::after {
    transform: scaleX(-1) scaleY(calc(-1 * var(--contact-squash)));
    animation-direction: reverse, reverse;
  }
  @keyframes crow-x {
    from { background-position-x: 0%; }
    to   { background-position-x: 100%; }
  }
  @keyframes crow-y {
    from { background-position-y: 0%; }
    to   { background-position-y: 100%; }
  }
  @media (prefers-reduced-motion: reduce) {
    html.ui-crow .crow-walk__crow { animation: none; transform: none; }
    html.ui-crow .crow-walk::before,
    html.ui-crow .crow-walk::after { animation: none; }
  }
}

/* Below 1100px the grid is two columns, so the crow takes a full row under
   the two tiles and the tuck-behind-the-tile edge no longer applies. */
@media (max-width: 1100px) {
  .crow-walk { margin-left: 0; }
}

/* =========================================================
   Single artwork page (work.php)
   The grid crops tiles to 4:5; this page never crops and never upscales.
   max-width with no width means a small file shows at its true size rather
   than being stretched to fill the column -- honest about what is there.
   ========================================================= */
.gallery-grid figure a { display: block; }
.gallery-grid figure img { cursor: zoom-in; }

.hero__eyebrow a { color: inherit; text-decoration: none; }
.hero__eyebrow a:hover { color: var(--orange-bright); }

.work { display: flex; flex-direction: column; align-items: flex-start; }

.work__frame {
  display: block;
  max-width: 100%;
  background: var(--card-bg);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  overflow: hidden;
}

.work__frame img { display: block; max-width: 100%; height: auto; }

.work__caption { padding: 18px 2px 0; max-width: 720px; }
.work__caption h1 { font-family: var(--font-display); font-size: 1.5rem; margin: 0 0 6px; }
.work__caption a { color: var(--orange-bright); }
.work__medium { margin: 0 0 12px; color: var(--text-dim); }
.work__meta { margin: 0; font-size: 0.85rem; }
