/* ═══════════════════════════════════════════════════════════════════════════
   css/link-wall.css — the wall a client actually opens.

   Loaded by profile.html (the public page at link.tattoo/handle) and by the
   editor's live preview, which renders the same markup into a container
   instead of into <html>. Nothing here may assume which of the two it is in.

   ── how theming actually works, because it is not obvious ────────────────
   The data-theme blocks below are the wall's OWN four palettes, used when
   the markup is dropped on a page by itself. In the app they are not what
   is running: page-render.js's wallConfig() sets data-theme to "midnight"
   for every theme an artist picks and then overrides the variables with
   wallVars(). So on a real wall, data-theme is ALWAYS "midnight".

   Which means: never write a rule keyed on data-theme expecting it to
   distinguish one artist's theme from another's. It will fire on all of
   them. That is exactly how the social icons ended up inverted to white on
   the cream themes, where white is invisible. Key off the variables — they
   are the thing that changes.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── themes: four rooms, not four tints ─────────────────────────────────── */
html[data-theme="midnight"]{        /* the house style — ink, bone, red      */
  --bg:#0d0d0f; --card:#141416; --edge:#2a2a2e;
  --text:#f4f3ef; --dim:#9a9a94; --accent:#fd5457; --on-accent:#0d0d0f;
  --icon-inv:1;
}
html[data-theme="flash-sheet"]{     /* trad flash paper — cream, black, red  */
  --bg:#efe7d4; --card:#f6efdf; --edge:#1c1a16;
  --text:#1c1a16; --dim:#7a7263; --accent:#c8322b; --on-accent:#f6efdf;
  --icon-inv:0;
}
html[data-theme="panther"]{         /* bottle green and old gold             */
  --bg:#0f1b15; --card:#15241c; --edge:#2c4033;
  --text:#e9e4d2; --dim:#8fa091; --accent:#d3a94e; --on-accent:#0f1b15;
  --icon-inv:1;
}
html[data-theme="femme"]{           /* powder pink and wine                  */
  --bg:#f2e2e2; --card:#f8ecec; --edge:#5c1f2e;
  --text:#3c141f; --dim:#a3767f; --accent:#5c1f2e; --on-accent:#f2e2e2;
  --icon-inv:0;
}
:root{
  --mono:'Space Mono',ui-monospace,monospace;
  --disp:'Anton',sans-serif;
  /* one corner radius for every surface. A token rather than a number so a
     future paid theme can flatten it back to 0 (sharp slabs) in one line. */
  --r:14px;
  --icon-inv:1;                     /* dark page until a palette says otherwise */
}
*{box-sizing:border-box;margin:0;padding:0}
html,body{min-height:100%}
body{
  background:var(--bg);color:var(--text);font:14px/1.5 var(--mono);
  -webkit-font-smoothing:antialiased;
}
a{color:inherit}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
:focus-visible{outline:2px solid var(--accent);outline-offset:3px}

/* ── the uploadable background ──────────────────────────────────────────── */
#bg{position:fixed;inset:0;z-index:-2;background:var(--bg) center/cover no-repeat}
#bg.none{ /* no photo yet: quiet needle-line texture in the theme's own ink */
  background:
    repeating-linear-gradient(135deg,transparent 0 26px,color-mix(in srgb,var(--text) 6%,transparent) 26px 27px),
    var(--bg);
}
#scrim{position:fixed;inset:0;z-index:-1;
  background:linear-gradient(color-mix(in srgb,var(--bg) 55%,transparent),
             var(--bg) 78%);}
#bg.none + #scrim{background:none}

/* ── the wordmark ───────────────────────────────────────────────────────────
   Sits above the wall and STAYS there — static, not fixed. It is a maker's
   mark, not navigation, and following a client down someone else's portfolio
   is both distracting and how it ended up overlapping the head card.

   The colour is var(--text) and nothing else. That is the only thing on this
   page that tracks the artist's actual theme, so it is bone on ink, acid,
   gold leaf and violet haze, and dark ink on bone and old school, with no
   per-theme rule to keep in step. The fallback is for the moment before the
   renderer runs: the page is dark by default, so a dark fallback would be an
   invisible wordmark on first paint. The dot stays accent on all six. */
.wordmark{display:block;position:static;margin:34px auto 22px;text-align:center;
  font:400 clamp(20px,4.6vw,30px)/1 var(--disp);letter-spacing:.01em;
  /* the fallback chain matters: on the standalone page --text lives on
     <html>; in the editor's preview the wall vars live on the wall's own
     container, which the wordmark sits OUTSIDE — so it falls through to
     the pane's --t-fg (set by applyThemeVars) and only then to white.
     Without the middle step, bone's wordmark was white on cream. */
  color:var(--text,var(--t-fg,#f4f3ef));text-decoration:none;opacity:.85;
  transition:opacity .16s}
.wordmark:hover{opacity:1}
.wordmark b{color:var(--accent,var(--t-acc,#fd5457));font-weight:inherit}
/* the wall carries its own top padding; with a mark above it that becomes
   a gap twice the size it should be */
.wordmark + .wall{padding-top:12px}

/* ── page ───────────────────────────────────────────────────────────────── */
/* ONE layout everywhere (Occam): head, then the running order — booking,
   flash, links — in a single column on every screen. The wall is a size
   container; width only decides how many flash pieces sit per row.
   width:100% is explicit so nothing can ever shrink the column. */
/* One column on a phone, a real page on a desktop. The wall used to be a
   460px strip whatever the screen was, which is what a link-in-bio page is
   — and it is the wrong shape for an artist whose whole pitch is a wall of
   pictures. The old prototype ran to 960px with a two-column head, and
   that is the shape being brought back.

   The wall stays a size container, so every rule below reacts to the
   COLUMN rather than the viewport: the same markup renders correctly at
   380px inside the editor's phone mock and at 960px on a monitor, with no
   media query knowing which it is in. */
.wall{width:100%;max-width:460px;margin:0 auto;padding:56px 20px 40px;
  display:flex;flex-direction:column;gap:30px;min-height:100dvh;
  container:wall/inline-size}
html[data-layout^="links-"] .wall{max-width:680px}
/* and wider still on a real screen. A 460px ribbon down the middle of a
   1400px monitor is what every link-in-bio page looks like, and it wastes
   the one thing a tattoo page has that a creator's link list does not:
   pictures worth seeing large. Only with flash to show — data-layout is
   "stack" when there is none, and an empty second column reads as broken
   rather than spacious. */
@media(min-width:1100px){
  html[data-layout^="links-"] .wall{max-width:1000px;padding:48px 28px 44px}
}
@media(min-width:1000px){
  .wall,html[data-layout^="links-"] .wall{max-width:960px;padding:44px 28px 40px;
    gap:26px}
}

/* head: an identity card on the theme's own colour. Left: the badge
   badge, the name under it, the town under that. Right: the bio, and the
   social icons beneath it. Everything left-aligned inside the box. */
/* NARROW is the default, and narrow means CENTRED — badge, name, town, bio
   and socials all stacked down the middle, which is the shape everyone has
   learned from every link-in-bio page they have ever opened.

   WIDE splits it: the badge and name on the left, the bio and socials on
   the right, left-aligned, the way the old prototype had it. Both are
   right for their own width; neither is right for the other. */
.head{display:grid;grid-template-columns:1fr;gap:10px;
  background:var(--card);border:1px solid var(--edge);padding:28px 22px;
  text-align:center;
  /* the page and the card are two near-identical darks (or creams), so a
     1px edge was carrying all the separation. A deep, soft drop plus a
     one-pixel top highlight lifts the card on every theme without a
     single per-theme rule: the shadow is plain black (reads on the light
     themes, disappears politely on the dark ones) and the highlight is
     mixed from the theme's own text colour. */
  border-radius:var(--r);
  box-shadow:0 26px 52px -34px rgb(0 0 0/.55),
             inset 0 1px 0 color-mix(in srgb,var(--text) 7%,transparent)}
.head-id{display:flex;flex-direction:column;align-items:center}
.head-bio{display:flex;flex-direction:column;align-items:center;min-width:0}

@container wall (min-width:640px){
  /* composed from the TOP, not vertically centred — centring left the bio
     floating mid-air beside the tall badge column with a void above it.
     The old prototype sets everything against the card's top line, which
     is what made it read as designed rather than poured in. */
  .head{grid-template-columns:minmax(240px,300px) 1fr;gap:10px 48px;
    align-items:start;padding:34px 38px;text-align:left}
  .head-id{align-items:flex-start}
  .head-bio{align-items:flex-start;padding-top:2px}
  .head .bio{font-size:14px;line-height:1.75}
}
/* "stack" is the wall's way of saying links only — no flash, no second
   column to put anything in */
html[data-layout="stack"] .head{grid-template-columns:1fr;text-align:center}
html[data-layout="stack"] .head-id,
html[data-layout="stack"] .head-bio{align-items:center}
/* the ring is gone — it was a fixed circumference fighting variable text,
   and it lost every round. The badge is now the avatar plus the STAMP: the
   same facts (books state, city) in a frame beside it, flat, unspinning,
   unable to overflow. .badge shrinks to the avatar it actually holds. */
.badge{position:relative;width:104px;height:104px;margin:0 auto 14px}
@container wall (min-width:640px){.badge{margin:0 0 14px}}
.badge .av{position:absolute;inset:0;border-radius:50%;
  background:var(--bg) center/cover no-repeat;border:1px solid var(--edge);
  display:grid;place-items:center;font:400 40px var(--disp);color:var(--dim)}
.stamp{display:inline-flex;align-items:center;gap:8px;align-self:center;
  margin-bottom:12px;white-space:nowrap;
  padding:7px 12px;border:1px solid var(--edge);border-radius:999px;
  background:color-mix(in srgb,var(--text) 3%,transparent);
  font:700 9.5px/1 var(--mono);letter-spacing:.16em;text-transform:uppercase}
/* in flow at the TOP of the bio column — the prototype's spot: reads as
   "beside the avatar" on desktop, stacks cleanly above the bio on a
   phone, and can overlap nothing at any width or city length */
@container wall (min-width:640px){.stamp{align-self:flex-start}}
.stamp[hidden]{display:none}
.stamp b{font-weight:700}
.stamp span{color:var(--dim);font-weight:400}
.stamp span::before{content:"\00b7";margin-right:8px}
.stamp-dot{width:7px;height:7px;border-radius:50%;flex:none;background:var(--accent)}
.stamp[data-state="open"] .stamp-dot{animation:stamp-pulse 2.2s ease-in-out infinite}
.stamp[data-state="waitlist"] .stamp-dot{background:transparent;
  box-shadow:inset 0 0 0 1.5px var(--accent)}
.stamp[data-state="closed"] .stamp-dot{background:var(--dim);animation:none}
@keyframes stamp-pulse{0%,100%{opacity:1}50%{opacity:.35}}
@media (prefers-reduced-motion:reduce){.stamp-dot{animation:none}}
/* a notch smaller and given air — at 40px/1 the name overwhelmed every
   line around it and read as squashed against the handle below */
.head h1{font:400 clamp(24px,4.2vw,36px)/1.08 var(--disp);letter-spacing:.025em;
  text-transform:uppercase}
.head h1 i{font-style:normal;color:var(--accent)}
.head .who{margin-top:10px;font-size:11.5px;color:var(--dim);letter-spacing:.16em;
  text-transform:uppercase}
.head .bio{margin:0 0 18px;max-width:44em;font-size:13px;line-height:1.7;
  color:var(--text);opacity:.85}

/* the town, beside the handle. A pin so "Sopot, PL" reads as a place rather
   than as part of the URL above it. #who wins on specificity, which is the
   point: an <svg> with no width rule renders at its natural box, and the pin
   came out the size of a hand. em-sized and currentColor, so it follows both
   the type scale and the theme with no second rule. */
#who .who-town{display:inline-flex;align-items:center;gap:.32em;white-space:nowrap}
#who .who-pin{width:1em;height:1em;flex:none;stroke:currentColor;fill:none;
  stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round;opacity:.8}

/* the discipline chips, under the bio: bordered mono pills in the theme's
   own edge and a whisper of its text — a different voice from the bio's
   prose, so "blackwork · fine line" reads as a label set, not a sentence.
   Empty row collapses, so pages without styles lose nothing. */
.craft{display:flex;flex-wrap:wrap;gap:6px;justify-content:center;margin-top:12px}
.craft:empty{display:none}
@container wall (min-width:640px){.craft{justify-content:flex-start}}
.craft i{font-style:normal;font:400 9.5px/1 var(--mono);
  letter-spacing:.12em;text-transform:uppercase;color:var(--dim);
  padding:5px 10px;border:1px solid var(--edge);border-radius:999px;
  background:color-mix(in srgb,var(--text) 3%,transparent)}

/* socials, under the bio: circled buttons, the old prototype's treatment.
   The frame is drawn from the theme's own edge and a whisper of its text
   colour, so it holds on all six palettes; the mark inside still inverts
   with --icon-inv exactly as before. Hit area is a real 40px. */
.soc{display:flex;flex-wrap:wrap;gap:10px;align-items:center;
  justify-content:center}
@container wall (min-width:640px){.soc{justify-content:flex-start}}
.soc a{display:grid;place-items:center;width:40px;height:40px;
  border-radius:999px;border:1px solid var(--edge);
  background:color-mix(in srgb,var(--text) 4%,transparent);
  opacity:.85;transition:opacity .16s,border-color .16s,transform .14s ease}
.soc a:hover,.soc a:focus-visible{opacity:1;border-color:var(--accent);
  transform:translateY(-2px)}
.soc a:active{transform:scale(.94)}
.soc img{width:20px;height:20px;display:block}
.soc svg{width:19px;height:19px;stroke:var(--text);fill:none;stroke-width:1.7;
  stroke-linecap:round;stroke-linejoin:round}
/* the socials are the head card's FOOTER now — a full-width row under a
   hairline, spanning badge and bio columns alike, instead of hanging in
   the bio column's air. (The markup moved them out of .head-bio; the
   direct-child selector means stale markup simply keeps the old hang.) */
.head > .soc{grid-column:1/-1;width:100%;margin-top:8px;padding-top:16px;
  border-top:1px solid var(--edge)}

/* The icon files arrive in whatever colour the set shipped in, so we don't
   trust it: brightness(0) flattens every pixel to black, keeping alpha, and
   invert() flips that to white where the page is dark.
   HOW MUCH to invert comes from --icon-inv, set alongside the palette in
   page-render.js's wallVars(). It used to be keyed on
   html[data-theme="midnight"] — which matches EVERY theme, because
   wallConfig() sets that attribute for all of them, so the icons were
   inverted to white on the cream themes as well and disappeared. */
.soc img{filter:brightness(0) invert(var(--icon-inv,1))}
.soc a:hover img,.soc a:focus-visible img{
  filter:brightness(0) invert(var(--icon-inv,1)) drop-shadow(0 0 6px var(--accent))}

/* ── the stack: one column, every screen. The artist's running order
   (inline `order`: booking → flash → links by default) is the whole
   layout; "stack" still means no flash section. */
/* The prototype's two-column grid, restored. Links down one side, the
   flash wall taking the rest — which is what makes a desktop page read as
   a wall of work rather than a phone screenshot stretched wide. It was
   flattened to a single column "for simplicity", and that is the wrong
   simplicity: a portfolio opened on a laptop in a studio is a portfolio.

   Under 820px it collapses back to one column, and the artist's running
   order (`order`, set inline by link-wall.js) still decides what comes
   first there — so nothing about the phone page changes. */
.cols{display:grid;grid-template-columns:1fr;gap:34px;align-items:start}
html[data-layout="links-left"]  .cols{grid-template-columns:minmax(290px,380px) 1fr}
html[data-layout="links-right"] .cols{grid-template-columns:1fr minmax(290px,380px)}
/* the booking slab is the point of the page — it spans both columns */
html[data-layout^="links-"] #apex{grid-column:1/-1}
/* one column below this, and the artist's running order (`order`, set
   inline by link-wall.js) still decides what comes first there — nothing
   about the phone page changes. Matched to the wall's own breakpoint so
   the column split and the extra width arrive together. */
@media (max-width:1099px){
  html[data-layout^="links-"] .cols{grid-template-columns:1fr}
}
html[data-layout="stack"] .flashcol{display:none}

/* links: sharp slabs, the booking one filled */
.links{display:flex;flex-direction:column;gap:12px}
.link{display:flex;align-items:center;gap:14px;text-decoration:none;
  background:var(--card);border:1px solid var(--edge);padding:17px 18px;
  border-radius:calc(var(--r) - 4px);
  box-shadow:inset 0 1px 0 color-mix(in srgb,var(--text) 6%,transparent);
  transition:transform .14s ease,border-color .14s ease}
.link:hover{transform:translateX(4px);border-color:var(--accent)}
.link .t{flex:1;min-width:0}
.link .t b{display:block;font-weight:700;font-size:14px;letter-spacing:.12em;
  text-transform:uppercase}
.link .t small{display:block;margin-top:3px;font-size:11px;color:var(--dim);
  font-weight:400;letter-spacing:.02em;text-transform:none}
.link .arrow{flex:none;font-size:15px;color:var(--dim);transition:color .14s}
.link:hover .arrow{color:var(--accent)}
.link.primary{background:var(--accent);border-color:var(--accent);color:var(--on-accent);
  /* the slab is the point of the page, so it gets the page's one glow:
     a shadow mixed from the accent itself, which reads as heat on the
     dark themes and as a grounded dark drop on the cream ones */
  box-shadow:0 20px 44px -22px color-mix(in srgb,var(--accent) 60%,rgb(0 0 0/.4))}
/* the slab grows with the wall — big and bold on desktop, phone keeps
   the proportions that fit a thumb-width column */
@container wall (min-width:600px){
  .link.primary{padding:26px 26px}
  .link.primary .t b{font-size:21px;letter-spacing:.14em}
  .link.primary .t small{font-size:12.5px;margin-top:6px}
}
/* .75 opacity of --on-accent on the accent field was mud — the deposit
   line is the sentence that converts, it has to be readable */
.link.primary .t small{color:var(--on-accent);opacity:.92}
.link.primary .arrow{color:var(--on-accent);opacity:.9;font-size:22px;
  transition:transform .14s ease}
.link.primary:hover .arrow{transform:translateX(4px)}
.link.primary:hover{transform:translateX(0) scale(1.012)}

/* flash gallery: the latest four, priced like the wall itself */
/* the section header, promoted from a whisper. The prototype heads every
   section with the display face and lets the meta sit beside it in mono —
   an 11px tracked label was disappearing between the head card and the
   pictures. The hairline after it is drawn from --edge, so it belongs to
   the theme like every other rule on the page. */
.flashcol .eyebrow{display:flex;align-items:baseline;gap:12px;margin-bottom:16px}
.flashcol .eyebrow b{font:400 clamp(17px,2.8cqi,22px)/1 var(--disp);
  letter-spacing:.04em;text-transform:uppercase}
.flashcol .eyebrow span{font-size:10px;color:var(--dim);letter-spacing:.12em;
  text-transform:uppercase}
.flashcol .eyebrow::after{content:"";flex:1;min-width:24px;height:1px;
  background:var(--edge);align-self:center}
/* auto-fill, not a fixed count. The container query measures the WALL, but
   on desktop the flash sits in ONE COLUMN of it — asking for four tracks
   because the wall is 1000px gave four cramped tiles in a 600px column.
   Sizing by the tile lets the grid work out its own count wherever it is
   put, which is the only thing that survives both layouts. */
.flash{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:14px}
@media(min-width:1100px){
  .flash{grid-template-columns:repeat(auto-fill,minmax(170px,1fr));gap:20px}
}
/* the piece is a MATTED print now: a sliver of the card colour frames the
   artwork, so white-paper flash stops reading as a bare white block on the
   dark themes — the drawing sits inside the page's own material instead of
   punching a hole in it. Rounded to the house radius. */
.piece{position:relative;display:block;text-decoration:none;
  background:var(--card);border:1px solid var(--edge);overflow:hidden;
  border-radius:var(--r);padding:7px 7px 0;
  transition:border-color .14s ease,transform .14s ease,box-shadow .14s ease}
.piece:hover{border-color:var(--accent);transform:translateY(-2px);
  box-shadow:0 18px 34px -20px rgb(0 0 0/.6)}
.piece .ph{aspect-ratio:3/4;background:var(--bg) center/cover no-repeat;display:block;
  border-radius:calc(var(--r) - 6px);
  transition:transform .35s ease}
.piece:hover .ph{transform:scale(1.02)}
@media (prefers-reduced-motion:reduce){
  .piece,.piece .ph{transition:none}
  .piece:hover{transform:none}
  .piece:hover .ph{transform:none}
}
/* the caption carries the title alone; the price has moved onto the image */
.piece .cap{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:10px 5px 11px}
/* the price's terms, under the title: "10 × 14 cm · forearm". Quiet mono,
   because it is metadata — but present, because it is what the number on
   the pill actually means. */
.piece .cap em{display:block;width:100%;font-style:normal;
  font:400 9.5px/1.3 var(--mono);color:var(--dim);
  letter-spacing:.08em;text-transform:uppercase;margin-top:3px}
.piece .cap b{flex:1;min-width:0;font-size:11px;font-weight:700;letter-spacing:.08em;
  text-transform:uppercase;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* the price, as a glass pill on the artwork's corner — read at a glance,
   themed from the wall's own tokens, and it gives the caption bar back
   half its height. Scoped to the default hang: the paid gallery modes
   below draw their own captions and keep their own price placement. */
html:not([data-gallery="grid"]):not([data-gallery="blocks"]) .piece .cap s{
  position:absolute;top:14px;right:14px;flex:none;text-decoration:none;
  font-size:10.5px;font-weight:700;letter-spacing:.04em;color:var(--accent);
  padding:4px 9px;border-radius:999px;
  background:color-mix(in srgb,var(--bg) 78%,transparent);
  backdrop-filter:blur(5px);-webkit-backdrop-filter:blur(5px);
  border:1px solid color-mix(in srgb,var(--accent) 30%,transparent)}
html[data-gallery="grid"] .piece .cap s,
html[data-gallery="blocks"] .piece .cap s{
  flex:none;text-decoration:none;font-size:11.5px;color:var(--accent);font-weight:700}
.piece .once{position:absolute;top:14px;left:14px;background:var(--accent);color:var(--on-accent);
  font-size:9px;font-weight:700;letter-spacing:.14em;padding:4px 8px;border-radius:999px;
  text-transform:uppercase}

/* ── the vortex: the whole flash wall on a slowly turning ring ────────────
   Ported from the old tattoo portal, minus the WebGL — every design the
   artist finished in setup lands on here by itself, priced, and a tap on
   any card opens the form with that design already picked. Drag to spin.
   With fewer than three designs (a ring of two looks broken) or when the
   visitor asks for reduced motion, the flat grid above stands in. */
.vwrap{position:relative;height:330px;margin:0 -10px;
  perspective:1150px;perspective-origin:50% 44%;cursor:grab;overflow:hidden;
  -webkit-user-select:none;user-select:none;touch-action:pan-y}
.vwrap.drag{cursor:grabbing}
.vring{position:absolute;inset:0;transform-style:preserve-3d}
.vcell{position:absolute;left:50%;top:46%;width:158px;margin:-105px 0 0 -79px;
  transform-style:preserve-3d;text-decoration:none;color:inherit}
.vcell .face{width:158px;height:210px;overflow:hidden;display:block;position:relative;
  background:var(--card);border:1px solid var(--edge);
  box-shadow:0 24px 50px -26px rgba(0,0,0,.85)}
.vcell .face img{width:100%;height:100%;object-fit:cover;display:block}
.vcell .ph{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  font-size:9px;letter-spacing:.24em;text-transform:uppercase;color:var(--dim)}
.vcell .vcap{position:absolute;left:0;right:0;bottom:0;padding:26px 10px 9px;
  background:linear-gradient(transparent,color-mix(in srgb,var(--bg) 92%,transparent));
  font-size:9px;letter-spacing:.12em;text-transform:uppercase;color:var(--text);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.vcell .vcap b{color:var(--accent);font-weight:700}
.vcell .vonce{position:absolute;top:7px;left:7px;background:var(--accent);color:var(--on-accent);
  font-size:8px;font-weight:700;letter-spacing:.14em;padding:2px 6px;text-transform:uppercase}
.vcell:hover .face,.vcell:focus-visible .face{border-color:var(--accent)}
.vhint{margin-top:6px;text-align:center;font-size:9px;letter-spacing:.2em;
  text-transform:uppercase;color:var(--dim)}
/* the ring and the grid trade places */
.flashcol.spin .flash,.flashcol.spin ~ * .flash{display:none}
.flashcol:not(.spin) .vwrap,.flashcol:not(.spin) .vhint{display:none}
@media (prefers-reduced-motion:reduce){
  .flashcol .vwrap,.flashcol .vhint{display:none !important}
  .flashcol .flash{display:grid !important}
}

/* ── merch: tiles that leave the page ────────────────────────────────────
   The artist's own shop links wearing the wall's clothes: matted like
   flash, priced like flash, but each tap goes to THEIR checkout —
   Stripe, a print house, Etsy. Collapses entirely when empty. */
.merchwall{margin-top:34px}
.merchwall .eyebrow{display:flex;align-items:baseline;gap:12px;margin-bottom:16px}
.merchwall .eyebrow b{font:400 clamp(17px,2.8cqi,22px)/1 var(--disp);
  letter-spacing:.04em;text-transform:uppercase}
.merchwall .eyebrow span{font-size:10px;color:var(--dim);letter-spacing:.12em;
  text-transform:uppercase}
.merchwall .eyebrow::after{content:"";flex:1;min-width:24px;height:1px;
  background:var(--edge);align-self:center}
.merchgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(130px,1fr));gap:12px}
.mpiece{display:flex;flex-direction:column;text-decoration:none;color:var(--text);
  background:var(--card);border:1px solid var(--edge);border-radius:var(--r);
  padding:7px 7px 0;overflow:hidden;
  transition:border-color .14s ease,transform .14s ease}
.mpiece:hover{border-color:var(--accent);transform:translateY(-2px)}
.mp-img{display:block;aspect-ratio:1/1;border-radius:calc(var(--r) - 6px);
  background:color-mix(in srgb,var(--text) 6%,var(--bg)) center/cover no-repeat}
.mp-cap{display:flex;align-items:center;gap:8px;padding:9px 5px 10px}
.mp-cap b{flex:1;min-width:0;font-size:10.5px;font-weight:700;letter-spacing:.06em;
  text-transform:uppercase;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.mp-cap s{flex:none;text-decoration:none;font-size:10.5px;color:var(--accent);font-weight:700}

/* ── the work: healed pieces under the wall ──────────────────────────────
   Images only, square, matted like the flash but with no captions and no
   prices — flash is the OFFER, this is the PROOF, and the styling keeps
   them visibly different jobs. Collapses entirely when empty. */
.workwall{margin-top:34px}
.workwall .eyebrow{display:flex;align-items:baseline;gap:12px;margin-bottom:16px}
.workwall .eyebrow b{font:400 clamp(17px,2.8cqi,22px)/1 var(--disp);
  letter-spacing:.04em;text-transform:uppercase}
.workwall .eyebrow span{font-size:10px;color:var(--dim);letter-spacing:.12em;
  text-transform:uppercase}
.workwall .eyebrow::after{content:"";flex:1;min-width:24px;height:1px;
  background:var(--edge);align-self:center}
.workgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(110px,1fr));gap:10px}
@media(min-width:1100px){.workgrid{grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:12px}}
.wpiece{display:block;aspect-ratio:1/1;border-radius:calc(var(--r) - 4px);
  border:1px solid var(--edge);background:var(--card) center/cover no-repeat}

/* ── the foot ────────────────────────────────────────────────────────────
   Two credits doing different jobs.

   "made with link.tattoo" is the growth loop: the line an artist's client
   reads and then goes looking for. Centred, legible, a real link.

   The studio signature is a signature — bottom right, small, quiet until
   you look at it.

   The old "Already asked? Check on your request" link is gone. The free
   plan has no client status page, so it pointed at nothing. */
.foot{margin-top:auto;padding-top:30px;display:flex;flex-direction:column;
  align-items:center;gap:18px}

/* THE BADGE. Not a footnote — a button.

   This is the entire distribution model: an artist's client scrolls to the
   bottom, sees it, and goes looking. A 10px grey line at the end of the
   page does not get looked for. Linktree learned this and put a white pill
   on every free page, and being mildly in the way is the point of it.

   It is also the honest half of the free tier: the page costs nothing and
   is genuinely complete, and this is what is being traded for that. An
   artist who does not want it buys Studio, which is a clean thing to sell
   precisely because the badge is worth removing. */
.foot .credit{display:inline-flex;align-items:center;gap:.4em;
  padding:12px 22px;border-radius:999px;
  background:var(--text);color:var(--bg);
  font-size:12px;font-weight:700;letter-spacing:.04em;text-transform:none;
  text-decoration:none;white-space:nowrap;
  box-shadow:0 6px 22px -10px rgb(0 0 0 / 45%);
  transition:transform .16s ease,box-shadow .16s ease}
.foot .credit:hover{transform:translateY(-1px);
  box-shadow:0 10px 26px -10px rgb(0 0 0 / 55%)}
.foot .credit:active{transform:translateY(0)}
/* the wordmark inside the pill keeps the house punctuation: the dot is the
   brand, and it is the accent even against the pill's own field */
.foot .credit i{font-style:normal;font-weight:700}
.foot .credit b{color:var(--accent);font-weight:700}
.foot .credit[hidden]{display:none}

.foot .ijs{display:inline-flex;align-items:center;gap:7px;align-self:center;
  font-size:9px;letter-spacing:.12em;text-transform:uppercase;color:var(--dim);
  text-decoration:none;opacity:.55;transition:opacity .16s,color .16s}
.foot .ijs:hover{opacity:1;color:var(--text)}
.foot .ijs i{font-style:normal;color:var(--accent)}
/* Without an explicit size this renders at the PNG's natural width, which
   is how a 15px signature became a 300px octopus. Inversion follows the
   palette, same as the social marks. */
.foot .ijs img{width:15px;height:15px;flex:none;display:block;object-fit:contain;
  filter:brightness(0) invert(var(--icon-inv,1));opacity:.75}

/* On a wide screen the signature leaves the column and pins to the corner
   of the page — the wall is a 460px strip down the middle, so "bottom
   right" of the column is nowhere near the bottom right of the screen.

   This rule must ONLY exist on profile.html. The editor's preview draws
   the same markup with css/wall.css, inside a phone mock — and a
   position:fixed credit would escape the mock and stick itself to the
   corner of the editor. The markers below tell scope.py to drop it from
   the generated file. */
/* @no-scope */
@media (min-width:900px){
  .foot .ijs{position:fixed;right:18px;bottom:14px;z-index:5;align-self:auto;
    padding:6px 10px;border-radius:999px;
    background:color-mix(in srgb, var(--bg) 72%, transparent);
    backdrop-filter:blur(6px)}
  /* the column needs no reserved space once it has left the flow */
  .foot{gap:0}
}
/* @end-no-scope */

@media (max-width:520px){
  .foot .ijs .ijs-studios{display:none}   /* "Design by Immortal Jellyfish" fits; the rest does not */
}

/* ── gallery modes: same pieces, three hangs ─────────────────────────────
   grid: square tiles, feed-tight, image does all the talking — price and
   the 1/1 badge stay as overlays. blocks: one column, heavy borders, a
   hard offset shadow — the brutalist hang. carousel is the default above. */
html[data-gallery="grid"] .flash{grid-template-columns:repeat(3,1fr);gap:3px}
@container wall (min-width:600px){html[data-gallery="grid"] .flash{grid-template-columns:repeat(4,1fr)}}
/* the modes hang the same pieces their own way — the default's mat and
   radius would break their geometry, so both reset it */
html[data-gallery="grid"] .piece{border:0;background:none;padding:0;border-radius:6px}
html[data-gallery="grid"] .piece .ph{aspect-ratio:1/1;border-radius:0}
html[data-gallery="grid"] .piece .cap{position:absolute;left:0;right:0;bottom:0;border:0;
  padding:16px 7px 6px;background:linear-gradient(transparent,rgba(0,0,0,.55))}
html[data-gallery="grid"] .piece .cap b{font-size:9px;color:#fff}
html[data-gallery="grid"] .piece .cap s{font-size:9px}
html[data-gallery="grid"] .piece:hover{outline:2px solid var(--accent);outline-offset:-2px}
html[data-gallery="blocks"] .flash{grid-template-columns:1fr;gap:20px}
@container wall (min-width:600px){html[data-gallery="blocks"] .flash{grid-template-columns:1fr 1fr;gap:24px}}
html[data-gallery="blocks"] .piece{border:3px solid var(--text);box-shadow:9px 9px 0 var(--text);
  padding:0;border-radius:0;
  transition:box-shadow .14s,border-color .14s,transform .14s}
html[data-gallery="blocks"] .piece .ph{aspect-ratio:4/5;border-radius:0}
html[data-gallery="blocks"] .piece .cap{border-top:3px solid var(--text);padding:12px 14px}
html[data-gallery="blocks"] .piece .cap b{font-size:13px}
html[data-gallery="blocks"] .piece .cap s{font-size:13px}
html[data-gallery="blocks"] .piece:hover{border-color:var(--accent);
  box-shadow:9px 9px 0 var(--accent);transform:translate(-2px,-2px)}

/* ── the narrow condense ─────────────────────────────────────────────────
   On a phone the head card was eating the entire first viewport by
   itself — the home page's mock proved the same content reads fine at
   two-thirds the height. Everything stays; everything tightens: the
   badge, the name, the gaps, the chips, the socials. Target: name, the
   booking slab and the top of the flash wall inside one phone screen. */
@container wall (max-width:639px){
  .wall{gap:18px;padding-top:14px}
  .head{padding:18px 16px;gap:6px}
  .badge{width:76px;height:76px;margin:0 auto 8px}
  .badge .av{font-size:30px}
  .head h1{font-size:clamp(21px,6.4vw,26px)}
  .head .who{margin-top:6px;font-size:10px;letter-spacing:.13em}
  .stamp{margin:8px 0 6px;padding:6px 10px;font-size:8.5px;gap:6px}
  .stamp-dot{width:6px;height:6px}
  .head .bio{font-size:12.5px;line-height:1.55}
  .craft{margin-top:8px;gap:5px}
  .craft i{font-size:8.5px;padding:4px 8px;letter-spacing:.1em}
  .head > .soc{margin-top:2px;padding-top:10px;gap:8px}
  .soc a{width:34px;height:34px}
  .soc img{width:17px;height:17px}
  .soc svg{width:16px;height:16px}
  .corners{top:10px;left:10px;right:10px}
  .corner{width:34px;height:34px}
}

/* ── the apex slab ────────────────────────────────────────────────────────
   #apex holds the primary booking link; in the universal stack it's just a
   sibling the running order puts first by default. */
#apex:empty{display:none}
#apex .link{margin:0}

/* ── the two corners ─────────────────────────────────────────────────────
   Sit inside the wall's own column so they line up with the head card
   rather than floating in the margin, and they scroll away with it — they
   are an offer and a tool, not a permanent chrome bar. */
/* OVERLAID, not stacked: the claim and share buttons float on the head
   card's top corners (the Linktree move) instead of renting a row of
   their own and pushing everything down a storey. They're glass pills
   already, so they read over any card, any theme. .wall is their
   containing block; z-index clears the card, pointer-events pass
   through the empty middle so the head stays tappable. */
/* the head is the containing block now — the pills sit INSIDE the card's
   own top corners, the full Linktree move, at every width */
.head{position:relative}
.corners{position:absolute;top:12px;left:12px;right:12px;z-index:20;
  pointer-events:none;
  display:flex;justify-content:space-between;align-items:center}
.corner{display:grid;place-items:center;width:38px;height:38px;pointer-events:auto;
  border-radius:999px;border:1px solid var(--edge);
  background:color-mix(in srgb,var(--card) 78%,transparent);
  backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);
  color:var(--text);cursor:pointer;transition:border-color .16s,transform .1s}
.corner:hover{border-color:var(--accent)}
.corner:active{transform:scale(.95)}
.corner[hidden]{display:none}
/* the left one holds the wordmark's first half — "link." is enough to read
   as a logo at 38px, and the dot is the brand */
.corner b{font:400 13px/1 var(--disp);letter-spacing:.01em}
.corner b i{font-style:normal;color:var(--accent)}
.corner svg{width:18px;height:18px;stroke:currentColor;fill:none;
  stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
/* with the corners above it the head no longer needs the wall's full top
   padding */
.corners + .head{margin-top:0}