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

body {
  margin: 0;
  min-height: 100vh;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--body-font);
  overflow-x: hidden;
}

/* Halftone + noise texture over everything, non-interactive. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 900;
  opacity: 0.16;
  background-image: radial-gradient(circle at 1px 1px, rgba(255,255,255,0.55) 1px, transparent 0);
  background-size: 4px 4px;
  mix-blend-mode: overlay;
}

/* --------------------------------------------------------------- camera follow

   camera.js publishes the pointer as --cam-x / --cam-y in the range -1..1. The
   travel is kept to a handful of pixels: enough that the page feels like it is
   being looked at rather than printed, small enough that it never drags a
   full-bleed panel off its own background and shows the seam underneath.

   Transforms do not affect layout, so none of this can change the page height or
   introduce a scrollbar. */
:root { --cam-x: 0; --cam-y: 0; }

#main {
  transform: translate3d(calc(var(--cam-x) * -5px), calc(var(--cam-y) * -5px), 0);
  will-change: transform;
}

/* The halftone is a 4px repeating pattern, so shifting its background-position is
   seamless by construction; it can travel further than anything else without an
   edge ever appearing. */
body::after {
  background-position: calc(var(--cam-x) * 10px) calc(var(--cam-y) * 10px);
}

@media (prefers-reduced-motion: reduce) {
  #main { transform: none; }
  body::after { background-position: 0 0; }
}

/* ------------------------------------------------------------------ scrollbars

   The default chrome scrollbar is a light grey slab that reads as a browser part
   sitting on top of the page. Squared off, dark, and accented, it reads as part
   of the site instead. Firefox only exposes the two-colour form, so both syntaxes
   are given rather than one being treated as a fallback for the other. */
* {
  scrollbar-width: thin;
  scrollbar-color: #3a3a3a transparent;
}
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
  background: #3a3a3a;
  border: 2px solid transparent;
  background-clip: content-box;
}
*::-webkit-scrollbar-thumb:hover { background: var(--accent); background-clip: content-box; }
*::-webkit-scrollbar-corner { background: transparent; }
/* The stepper arrows are the last piece of stock browser chrome on the page, and
   nobody has clicked one on purpose since about 2009. */
*::-webkit-scrollbar-button { display: none; width: 0; height: 0; }

h1, h2, h3, .display {
  font-family: var(--display);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  line-height: 0.92;
  margin: 0 0 var(--s3);
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 3px;
}

.btn {
  display: inline-block;
  padding: var(--s2) var(--s4);
  font-family: var(--display);
  font-size: 1.1rem;
  text-transform: uppercase;
  color: var(--ink);
  background: var(--paper);
  border: var(--edge);
  transform: skewX(var(--skew));
  transition: transform var(--fast) var(--ease-slam), background var(--fast);
  cursor: pointer;
}
.btn:hover { transform: skewX(var(--skew)) translate(-3px, -3px); text-decoration: none; }
/* --on-accent is the text colour that stays legible on the current accent. Red and violet
   need light text; yellow and teal need dark. See tokens.css for the per-division mapping. */
.btn--accent {
  background: var(--accent);
  color: var(--on-accent);
  border-color: var(--ink);
  transition: background var(--fast) linear, color var(--fast) linear,
              transform var(--fast) var(--ease-slam);
}

.linkish {
  background: none;
  border: 0;
  color: inherit;
  font: inherit;
  text-transform: uppercase;
  cursor: pointer;
  padding: 0;
}
.inline-form { display: inline; }

.site-head {
  position: relative;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  padding: var(--s3) var(--s4);
  border-bottom: var(--edge);
  background: var(--ink);
}
/* A difference blend here inverts the nav against whatever quadrant sits behind it, which
   made the JOIN button unreadable. A scrim keeps contrast without hiding the artwork. */
.site-head--landing {
  position: fixed;
  inset: 0 0 auto 0;
  background: linear-gradient(to bottom, rgba(0, 0, 0, 0.82) 0%, rgba(0, 0, 0, 0.45) 60%, transparent 100%);
  border-bottom: 0;
  padding-bottom: var(--s4);
}
.site-head--landing .site-head__nav a,
.site-head--landing .sound-toggle {
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
}
/* Logo, bell and wall switcher travel together as one left-hand cluster.
   Left loose, the header's space-between pushed the bell into the middle of the
   bar on any page without a switcher. */
.site-head__left {
  display: flex;
  align-items: center;
  gap: var(--s2);
  min-width: 0;
}
.site-head__logo img { height: 58px; display: block; }
.site-head__nav {
  display: flex;
  align-items: center;
  gap: var(--s3);
  font-family: var(--display);
  font-size: 0.95rem;
  letter-spacing: 0.06em;
}
.site-head__nav a { color: var(--paper); }
/* The count the poller keeps up to date. Deliberately styled as nothing: it used to
   be bare text inside the link and should still read as bare text. */
.nav-badge { font: inherit; color: inherit; }
.nav-badge[hidden] { display: none; }

.sound-toggle {
  font-family: var(--display);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  color: var(--paper-dim);
  background: none;
  border: 1px solid var(--paper-dim);
  padding: 2px 8px;
  cursor: pointer;
}
.sound-toggle[aria-pressed="true"] { color: var(--accent); border-color: var(--accent); }

main { position: relative; z-index: 10; }

/* Three columns rather than space-between. The division strip is the middle one and
   has to sit on the centre of the page, but space-between only centres it when the
   two outer items happen to be the same width; and "UP SOMA SOSHIKI" is nowhere near
   as wide as the university line plus the credit, so it always sat left of centre.
   Equal 1fr side columns put it where it belongs regardless of what flanks it. */
.site-foot {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: baseline;
  gap: var(--s3);
  padding: var(--s4);
  border-top: var(--edge);
  font-family: var(--display);
  font-size: 0.8rem;
  letter-spacing: 0.1em;
  color: var(--paper-dim);
}
/* Every column may shrink below its content width, or the long university line
   holds the grid open and shoulders the division strip off the centre. */
.site-foot > * { min-width: 0; }
.site-foot__left { justify-self: start; }
.site-foot__mark { color: inherit; }
.site-foot__mark:hover,
.site-foot__mark:focus-visible { color: var(--accent); text-decoration: none; }

.site-foot__divisions {
  justify-self: center;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: center;
  gap: var(--s2);
  text-align: center;
}
.site-foot__dot { opacity: 0.6; }
/* Each division lights up in its own accent, so the strip teaches the four colours
   even before you have been to a wall. Colour on hover only; the resting state is
   the plain wordmark it has always been. */
.site-foot__division { color: inherit; }
.site-foot__division:hover,
.site-foot__division:focus-visible {
  color: var(--division-accent, var(--accent));
  text-decoration: none;
}

/* The credit sits with the university line rather than on its own row, so it reads
   as a signature on the footer instead of a third piece of navigation. */
.site-foot__right {
  justify-self: end;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: flex-end;
  gap: var(--s2);
  text-align: right;
}
.site-foot__credit { font-size: 0.68rem; letter-spacing: 0.16em; opacity: 0.8; }
.site-foot__credit a { color: var(--accent); }
.site-foot__credit a:hover { color: var(--paper); text-decoration: none; }

.flash-stack {
  position: fixed;
  top: 90px;
  right: var(--s3);
  z-index: 800;
  display: grid;
  gap: var(--s2);
  max-width: min(420px, 90vw);
}
.flash {
  padding: var(--s2) var(--s3);
  border: var(--edge);
  background: var(--paper);
  color: var(--ink);
  font-weight: 700;
  transform: skewX(var(--skew));
  animation: slam var(--slow) var(--ease-slam) both;
}
.flash--error   { background: var(--accent-sentai); color: var(--paper); }
.flash--success { background: var(--accent-anime); }
.flash--info    { background: var(--accent-manga); }

@keyframes slam {
  from { transform: skewX(var(--skew)) translateX(60px); opacity: 0; }
  to   { transform: skewX(var(--skew)) translateX(0); opacity: 1; }
}

/* ------------------------------------------------------------------ transitions

   Five named transitions, picked in transition.js by what kind of move is being
   made. Each has an "in" phase that covers the screen and an "out" phase that
   reveals the new page, so the swap happens while hidden.

     diagonal  landing -> a wall. The big Persona-style slash.
     shutter   wall -> wall. Vertical blinds in the destination's colour.
     slam      -> login/register/profile. Fast bar drop.
     iris      -> admin. A clinical rectangle opening from the centre.
     fade      minor moves within a section, and the back button.                */

.wipe {
  position: fixed;
  inset: 0;
  z-index: 950;
  pointer-events: none;
  display: none;
}
.wipe.is-running { display: block; }

.wipe__bar {
  position: absolute;
  background: var(--wipe-color, var(--accent));
  will-change: transform, clip-path, opacity;
}

/* Variants using a single panel hide the remaining bars. */
.wipe--diagonal .wipe__bar:not(:first-child),
.wipe--slam .wipe__bar:not(:first-child),
.wipe--iris .wipe__bar:not(:first-child),
.wipe--fade .wipe__bar:not(:first-child) { display: none; }

.wipe--diagonal .wipe__bar,
.wipe--slam .wipe__bar,
.wipe--iris .wipe__bar,
.wipe--fade .wipe__bar { inset: 0; }

/* diagonal */
@keyframes wipe-diagonal-in {
  from { clip-path: polygon(0 0, 0 0, 0 100%, 0 100%); }
  to   { clip-path: polygon(0 0, 130% 0, 100% 100%, -30% 100%); }
}
@keyframes wipe-diagonal-out {
  from { clip-path: polygon(0 0, 130% 0, 100% 100%, -30% 100%); }
  to   { clip-path: polygon(130% 0, 130% 0, 100% 100%, 100% 100%); }
}
.wipe--diagonal.is-in  .wipe__bar { animation: wipe-diagonal-in 380ms var(--ease-slam) forwards; }
.wipe--diagonal.is-out .wipe__bar { animation: wipe-diagonal-out 380ms var(--ease-slam) forwards; }

/* shutter; six vertical blinds, staggered */
.wipe--shutter .wipe__bar {
  top: 0;
  bottom: 0;
  width: calc(100% / 6 + 1px);
  transform: scaleY(0);
}
.wipe--shutter .wipe__bar:nth-child(1) { left: 0; }
.wipe--shutter .wipe__bar:nth-child(2) { left: calc(100% / 6); }
.wipe--shutter .wipe__bar:nth-child(3) { left: calc(100% / 6 * 2); }
.wipe--shutter .wipe__bar:nth-child(4) { left: calc(100% / 6 * 3); }
.wipe--shutter .wipe__bar:nth-child(5) { left: calc(100% / 6 * 4); }
.wipe--shutter .wipe__bar:nth-child(6) { left: calc(100% / 6 * 5); }

@keyframes wipe-shutter-in  { from { transform: scaleY(0); } to { transform: scaleY(1); } }
@keyframes wipe-shutter-out { from { transform: scaleY(1); } to { transform: scaleY(0); } }

.wipe--shutter.is-in .wipe__bar {
  transform-origin: top;
  animation: wipe-shutter-in 300ms var(--ease-slam) forwards;
}
.wipe--shutter.is-out .wipe__bar {
  transform-origin: bottom;
  animation: wipe-shutter-out 300ms var(--ease-slam) forwards;
}
.wipe--shutter .wipe__bar:nth-child(2) { animation-delay: 36ms; }
.wipe--shutter .wipe__bar:nth-child(3) { animation-delay: 72ms; }
.wipe--shutter .wipe__bar:nth-child(4) { animation-delay: 108ms; }
.wipe--shutter .wipe__bar:nth-child(5) { animation-delay: 144ms; }
.wipe--shutter .wipe__bar:nth-child(6) { animation-delay: 180ms; }

/* slam */
@keyframes wipe-slam-in  { from { transform: translateY(-100%) skewY(-3deg); } to { transform: none; } }
@keyframes wipe-slam-out { from { transform: none; } to { transform: translateY(100%) skewY(3deg); } }
.wipe--slam.is-in  .wipe__bar { animation: wipe-slam-in 280ms var(--ease-slam) forwards; }
.wipe--slam.is-out .wipe__bar { animation: wipe-slam-out 260ms var(--ease-slam) forwards; }

/* iris */
@keyframes wipe-iris-in  { from { clip-path: inset(50% 50% 50% 50%); } to { clip-path: inset(0 0 0 0); } }
@keyframes wipe-iris-out { from { clip-path: inset(0 0 0 0); } to { clip-path: inset(50% 0 50% 0); } }
.wipe--iris.is-in  .wipe__bar { animation: wipe-iris-in 360ms var(--ease-slam) forwards; }
.wipe--iris.is-out .wipe__bar { animation: wipe-iris-out 340ms var(--ease-slam) forwards; }

/* fade */
@keyframes wipe-fade-in  { from { opacity: 0; } to { opacity: 1; } }
@keyframes wipe-fade-out { from { opacity: 1; } to { opacity: 0; } }
.wipe--fade.is-in  .wipe__bar { animation: wipe-fade-in 190ms linear forwards; }
.wipe--fade.is-out .wipe__bar { animation: wipe-fade-out 190ms linear forwards; }

.error-page { padding: var(--s6) var(--s4); max-width: 60ch; }
.error-page__code { font-family: var(--display); font-size: 6rem; color: var(--accent); margin: 0; }
.error-page__heading { font-size: 3rem; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ------------------------------------------------------- nav hover + audio panel */

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* Nav links: an accent slab slides up behind the text, and the label kicks with it. */
.site-head__nav > a:not(.btn),
.site-head__nav .linkish {
  position: relative;
  /* z-index:0 creates a stacking context on the link itself, so the slab below can
     sit at -1 and still paint above the header background rather than behind it. */
  z-index: 0;
  padding: 4px 8px;
  overflow: hidden;
  transition: color var(--fast) linear, transform var(--fast) var(--ease-slam);
}
.site-head__nav > a:not(.btn)::before,
.site-head__nav .linkish::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--accent);
  transform: skewX(var(--skew)) translateY(102%);
  transition: transform 200ms var(--ease-slam);
}
.site-head__nav > a:not(.btn):hover,
.site-head__nav > a:not(.btn):focus-visible,
.site-head__nav .linkish:hover,
.site-head__nav .linkish:focus-visible {
  color: var(--on-accent);
  text-decoration: none;
  transform: translateY(-2px);
}
.site-head__nav > a:not(.btn):hover::before,
.site-head__nav > a:not(.btn):focus-visible::before,
.site-head__nav .linkish:hover::before,
.site-head__nav .linkish:focus-visible::before {
  transform: skewX(var(--skew)) translateY(0);
}

/* The logo gets a spin-nudge rather than a slab. */
.site-head__logo img {
  transition: transform 320ms var(--ease-slam), filter var(--fast) linear;
}
.site-head__logo:hover img {
  transform: rotate(-14deg) scale(1.08);
  filter: drop-shadow(0 0 10px var(--accent));
}

/* JOIN already carries the accent, so it lifts and hard-shadows instead. */
.site-head__nav .btn--accent {
  transition: transform var(--fast) var(--ease-slam), box-shadow var(--fast) linear,
              background var(--fast) linear, color var(--fast) linear;
}
.site-head__nav .btn--accent:hover,
.site-head__nav .btn--accent:focus-visible {
  transform: skewX(var(--skew)) translate(-3px, -3px);
  box-shadow: 5px 5px 0 var(--ink);
}

/* ------------------------------------------------------------- avatar menu

   The sound toggle and audio sliders used to sit loose in the nav bar. They are
   personal settings, not navigation, so they live behind the avatar now.

   The avatar is a four-point star cut from the org's shuriken mark rather than
   the usual circle; a circle would read as every other social site. */

.user-menu { position: relative; }
.user-menu > summary {
  list-style: none;
  cursor: pointer;
  display: block;
  line-height: 0;
}
.user-menu > summary::-webkit-details-marker { display: none; }
.user-menu > summary:focus-visible { outline: 3px solid var(--accent); outline-offset: 4px; }

/* A notched square, not a circle and not a star. The star silhouette looked great
   empty, but it cropped away most of an uploaded photo; and keeping the picture
   legible is the whole job of an avatar. The clipped corners keep it distinctly
   ours without eating the face. */
.avatar-star {
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  background: var(--accent);
  color: var(--on-accent);
  clip-path: polygon(22% 0, 100% 0, 100% 78%, 78% 100%, 0 100%, 0 22%);
  overflow: hidden;
  transition: transform 320ms var(--ease-slam), filter var(--fast) linear;
}
.user-menu > summary:hover .avatar-star,
.user-menu[open] .avatar-star {
  transform: translateY(-2px) scale(1.06);
  filter: drop-shadow(0 0 8px var(--accent));
}
.avatar-star img { width: 100%; height: 100%; object-fit: cover; display: block; }
.avatar-star .anon-mark { width: 24px; height: 24px; }
.avatar-star__initial {
  font-family: var(--display);
  font-size: 1.05rem;
  line-height: 1;
}

.user-menu__panel {
  position: absolute;
  top: calc(100% + 12px);
  right: 0;
  z-index: 70;
  width: 260px;
  padding: var(--s3);
  background: var(--ink-soft);
  border: var(--edge);
  border-top: 6px solid var(--accent);
  box-shadow: 10px 10px 0 rgba(0, 0, 0, 0.6);
  animation: slam 240ms var(--ease-slam) both;
  text-align: left;
}

.user-menu__who {
  margin: 0 0 var(--s2);
  padding-bottom: var(--s2);
  border-bottom: 1px solid #2c2c2c;
  font-family: var(--display);
  letter-spacing: 0.06em;
  color: var(--paper);
}
.user-menu__rank {
  display: block;
  font-size: 0.62rem;
  letter-spacing: 0.24em;
  color: var(--accent);
}

.user-menu__links { display: grid; gap: var(--s1); margin-bottom: var(--s3); }
.user-menu__links a {
  font-family: var(--display);
  font-size: 0.82rem;
  letter-spacing: 0.1em;
  color: var(--paper);
  padding: 3px 0;
}
.user-menu__links a:hover { color: var(--accent); text-decoration: none; padding-left: 5px; }

.user-menu__logout, .user-menu__join { display: block; margin-top: var(--s3); }
/* The global .btn skew pushes a full-width button past the panel edge, so inside
   the menu the buttons sit square. */
.user-menu__logout .btn, .user-menu__join {
  width: 100%;
  text-align: center;
  font-size: 0.8rem;
  transform: none;
}
.user-menu__logout .btn:hover, .user-menu__join:hover { transform: translate(-2px, -2px); }

.sound-toggle {
  font-family: var(--display);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  color: var(--paper-dim);
  background: none;
  border: 1px solid var(--paper-dim);
  padding: 3px 8px;
  margin-bottom: var(--s2);
  cursor: pointer;
  width: 100%;
  transition: color var(--fast) linear, border-color var(--fast) linear;
}
.sound-toggle:hover { color: var(--accent); border-color: var(--accent); }
.sound-toggle[aria-pressed="true"] { color: var(--accent); border-color: var(--accent); }

.audio-panel { padding-top: var(--s2); border-top: 1px solid #2c2c2c; }
.audio-panel[hidden] { display: none; }
.audio-panel__title {
  margin: 0 0 var(--s2);
  font-family: var(--display);
  letter-spacing: 0.24em;
  font-size: 0.66rem;
  color: var(--accent);
}
.audio-panel__row {
  display: grid;
  grid-template-columns: 40px 1fr 26px;
  align-items: center;
  gap: var(--s2);
  margin-bottom: var(--s2);
  font-family: var(--display);
  font-size: 0.66rem;
  letter-spacing: 0.1em;
  color: var(--paper-dim);
}
.audio-panel__row output { text-align: right; color: var(--paper); }
.audio-panel__note { margin: 0; font-size: 0.6rem; letter-spacing: 0.06em; color: var(--paper-dim); }

.audio-panel input[type="range"] {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 4px;
  background: #000;
  border: 1px solid var(--paper-dim);
  cursor: pointer;
}
.audio-panel input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 11px;
  height: 17px;
  background: var(--accent);
  border: 2px solid var(--ink);
  cursor: pointer;
}
.audio-panel input[type="range"]::-moz-range-thumb {
  width: 11px;
  height: 17px;
  background: var(--accent);
  border: 2px solid var(--ink);
  border-radius: 0;
  cursor: pointer;
}

/* ------------------------------------------------- wall switcher (global header)

   Lives in base.css, not content.css: the header renders on every page but the
   wall pages only load wall.css, so styling it there would leave it unstyled. */

.division-switcher { display: flex; flex-wrap: wrap; gap: var(--s2); margin-top: var(--s3); }

/* In the header the switcher sits on the row itself, hard left after the logo,
   rather than floating in the middle of the page. */
.site-head .division-switcher {
  margin-top: 0;
  gap: var(--s1);
}
.site-head .division-switcher__item {
  padding: 3px 10px;
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  border-width: 2px;
}
.site-head--landing .division-switcher { display: none; }

@media (max-width: 720px) {
  .site-head .division-switcher { order: 3; width: 100%; margin-right: 0; }
  .site-head .division-switcher__item { font-size: 0.66rem; padding: 2px 8px; }
}

/* ------------------------------------------------------------ small screens

   The header was the single thing breaking the site on a phone. Its nav could not
   shrink below about 457px, and a row that will not fit widens the layout viewport
   itself; so `width=device-width` stopped meaning 390px, every page rendered at
   501px and the whole site was zoomed out. `overflow-x: hidden` on the body cannot
   save you from this: by then the initial containing block is already too wide.

   So the header has to be able to get small. It wraps, the labels shrink, and the
   avatar stays put on the first row where a thumb expects it. */
@media (max-width: 640px) {
  .site-head {
    flex-wrap: wrap;
    gap: var(--s2);
    padding: var(--s2) var(--s3);
  }
  .site-head__logo img { height: 42px; }

  /* One column on a phone. Three narrow columns turn the division strip into a
     four-line stack squeezed between two other stacks. */
  .site-foot {
    grid-template-columns: 1fr;
    justify-items: center;
    text-align: center;
    row-gap: var(--s2);
  }
  .site-foot__left,
  .site-foot__right { justify-self: center; }
  .site-foot__right { justify-content: center; text-align: center; }

  .site-head__nav {
    /* Second row, full width, so it never has to compete with the logo for space. */
    order: 3;
    width: 100%;
    flex-wrap: wrap;
    justify-content: flex-start;
    gap: var(--s1) var(--s2);
    font-size: 0.76rem;
  }
  .site-head__nav > a:not(.btn) { padding: 3px 6px; }

  /* The account menu belongs beside the logo, within reach, not at the end of a
     wrapped row of links. It is a child of the nav, so `order` cannot move it to
     the header's first row; lifting it out of the flow can. .site-head is
     position: relative, and the panel itself is a viewport-pinned sheet at this
     width, so taking the trigger out of the flow costs nothing. */
  .site-head .user-menu {
    position: absolute;
    top: var(--s2);
    right: var(--s3);
    margin-left: auto;
  }

  .avatar-star { width: 36px; height: 36px; }
  .bell { width: 30px; height: 30px; }
  .bell svg { width: 19px; height: 19px; }
}


@media (max-width: 400px) {
  .site-head__nav { font-size: 0.7rem; }
  .site-foot { padding: var(--s3); font-size: 0.68rem; gap: var(--s2); }
  .site-foot__credit { font-size: 0.6rem; }
}

/* ------------------------------------------------------------- file inputs

   Styled globally rather than per-form: ::file-selector-button restyles the
   browser's native "Choose File" control in place, so every upload field on the
   site picks this up without markup changes. The wall composer keeps its larger
   drag-and-drop zone on top of this. */

input[type="file"] {
  font: inherit;
  font-size: 0.82rem;
  width: 100%;
  padding: 0;
  color: var(--paper-dim);
  background: #000;
  border: 2px dashed var(--paper-dim);
  cursor: pointer;
  transition: border-color var(--fast) linear, color var(--fast) linear;
}
input[type="file"]:hover { border-color: var(--accent); color: var(--paper); }
input[type="file"]:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }

input[type="file"]::file-selector-button {
  font-family: var(--display);
  font-size: 0.78rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  margin-right: var(--s3);
  padding: var(--s2) var(--s3);
  color: var(--on-accent);
  background: var(--accent);
  border: 0;
  border-right: 2px solid var(--ink);
  cursor: pointer;
  transition: filter var(--fast) linear, transform var(--fast) var(--ease-slam);
}
input[type="file"]::file-selector-button:hover {
  filter: brightness(1.15);
  transform: translateX(2px);
}

/* Older WebKit spells it differently and does not accept the standard selector
   in the same rule, so it needs its own block. */
input[type="file"]::-webkit-file-upload-button {
  font-family: var(--display);
  font-size: 0.78rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  margin-right: var(--s3);
  padding: var(--s2) var(--s3);
  color: var(--on-accent);
  background: var(--accent);
  border: 0;
  border-right: 2px solid var(--ink);
  cursor: pointer;
}

/* ------------------------------------------------------------------- bell

   Sits beside the logo rather than in the nav cluster on the right: it is
   status, not navigation, and it needs to be noticed without competing with
   the account menu. */

.bell {
  position: relative;
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  color: var(--paper-dim);
  transition: color var(--fast) linear, transform var(--fast) var(--ease-slam);
}
.bell svg { width: 21px; height: 21px; fill: currentColor; display: block; }
.bell:hover { color: var(--accent); transform: translateY(-2px); text-decoration: none; }

.bell--alert { color: var(--accent); }
/* A short shake on arrival, not a loop; a permanently animating badge becomes
   wallpaper and stops being read as new. */
.bell--alert svg { animation: bell-ring 900ms var(--ease-slam) 1; transform-origin: 50% 15%; }

@keyframes bell-ring {
  0%, 100% { transform: rotate(0); }
  15% { transform: rotate(14deg); }
  30% { transform: rotate(-11deg); }
  45% { transform: rotate(8deg); }
  60% { transform: rotate(-5deg); }
  75% { transform: rotate(2deg); }
}

.bell__count {
  position: absolute;
  top: -3px;
  right: -5px;
  min-width: 17px;
  padding: 0 4px;
  font-family: var(--display);
  font-size: 0.62rem;
  line-height: 17px;
  text-align: center;
  color: var(--on-accent);
  background: var(--accent);
  border: 2px solid var(--ink);
}

.site-head--landing .bell { color: var(--paper); }

/* ---- bell dropdown ----

   Same <details> mechanism as the avatar menu: no JavaScript, closes on Escape,
   and works if a script fails to load. Reading the last few is a glance; the
   full page is still one click away at the bottom. */
.bell-menu { position: relative; flex: 0 0 auto; }
.bell-menu > summary {
  list-style: none;
  cursor: pointer;
}
.bell-menu > summary::-webkit-details-marker { display: none; }
.bell-menu > summary::marker { content: ""; }

.bell-menu__panel {
  position: absolute;
  left: 0;
  top: calc(100% + 10px);
  z-index: 60;
  width: min(340px, calc(100vw - 2 * var(--s4)));
  max-height: min(60vh, 480px);
  overflow-y: auto;
  overscroll-behavior: contain;
  display: grid;
  gap: var(--s2);
  padding: var(--s3);
  background: var(--ink);
  border: var(--edge);
  border-top: 5px solid var(--accent);
  box-shadow: 10px 10px 0 rgba(0, 0, 0, 0.6);
  animation: slam 200ms var(--ease-slam) both;
}
.bell-menu__title {
  margin: 0;
  font-family: var(--display);
  font-size: 0.72rem;
  letter-spacing: 0.2em;
  color: var(--accent);
}
.bell-menu__empty {
  margin: 0;
  padding: var(--s3) 0;
  text-align: center;
  font-family: var(--display);
  font-size: 0.66rem;
  letter-spacing: 0.14em;
  color: var(--paper-dim);
}
.bell-menu__actions { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; }
.bell-menu__actions a {
  display: block;
  padding: var(--s1) var(--s2);
  font-family: var(--display);
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  color: var(--on-accent);
  background: var(--accent);
}
.bell-menu__actions a:hover { text-decoration: none; filter: brightness(1.12); }

.bell-menu__list { list-style: none; margin: 0; padding: 0; display: grid; }
.bell-item {
  display: flex;
  gap: var(--s2);
  padding: var(--s2) 0;
  border-bottom: 1px solid #1e1e1e;
}
.bell-item:last-child { border-bottom: 0; }
.bell-item__avatar {
  display: grid;
  place-items: center;
  width: 28px; height: 28px;
  flex: 0 0 auto;
  background: var(--accent);
  clip-path: polygon(22% 0, 100% 0, 100% 78%, 78% 100%, 0 100%, 0 22%);
  overflow: hidden;
}
.bell-item__avatar img { width: 100%; height: 100%; object-fit: cover; }
.bell-item__body { display: grid; gap: 2px; min-width: 0; color: inherit; }
.bell-item__body:hover { text-decoration: none; }
.bell-item__body:hover .bell-item__text { color: var(--accent); }
.bell-item__text {
  font-family: var(--display);
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  color: var(--paper);
}
.bell-item__excerpt {
  font-size: 0.72rem;
  line-height: 1.35;
  color: var(--paper-dim);
  /* Two lines, then stop; the panel is a glance, not the page. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.bell-item__time { font-size: 0.6rem; letter-spacing: 0.08em; color: var(--paper-dim); opacity: 0.75; }

.bell-menu__all {
  justify-self: stretch;
  text-align: center;
  padding: var(--s2);
  font-family: var(--display);
  font-size: 0.68rem;
  letter-spacing: 0.16em;
  color: var(--paper);
  border: var(--edge);
}
.bell-menu__all:hover { color: var(--on-accent); background: var(--accent); text-decoration: none; }

/* Notifications page */
.notif-actions { list-style: none; margin: 0 0 var(--s4); padding: 0; display: grid; gap: var(--s2); }
.notif-actions a {
  display: block;
  padding: var(--s2) var(--s3);
  font-family: var(--display);
  letter-spacing: 0.08em;
  background: var(--ink-soft);
  border-left: 6px solid var(--accent);
}
.notif-actions a:hover { text-decoration: none; transform: translateX(3px); }

.notif-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s2); }
.notif {
  display: flex;
  align-items: flex-start;
  gap: var(--s3);
  padding: var(--s3);
  background: var(--ink-soft);
  border: var(--edge);
  border-left: 6px solid var(--paper-dim);
  transition: transform var(--fast) var(--ease-slam);
}
.notif:hover { transform: translateX(-3px); }
.notif--like    { border-left-color: var(--accent-sentai); }
.notif--comment { border-left-color: var(--accent-ongaku); }
.notif--share   { border-left-color: var(--accent-anime); }

.notif__avatar {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 38px; height: 38px;
  background: var(--accent);
  clip-path: polygon(22% 0, 100% 0, 100% 78%, 78% 100%, 0 100%, 0 22%);
  overflow: hidden;
}
.notif__avatar img { width: 100%; height: 100%; object-fit: cover; }

.notif__body { display: grid; gap: 3px; color: inherit; min-width: 0; }
.notif__body:hover { text-decoration: none; }
.notif__text { font-family: var(--display); letter-spacing: 0.05em; color: var(--paper); }
.notif__excerpt { font-size: 0.8rem; color: var(--paper-dim); overflow-wrap: anywhere; }
.notif__quote {
  font-size: 0.85rem;
  padding-left: var(--s2);
  border-left: 2px solid var(--paper-dim);
  overflow-wrap: anywhere;
}
.notif__time { font-size: 0.66rem; color: var(--paper-dim); }

/* --------------------------------------------------- dropdown panels on a phone

   Both panels hang off a trigger sitting near a screen edge, so at 390px most of
   the panel lands outside the viewport. Pinning them to the viewport with a margin
   either side is the only thing that reliably fits.

   This block lives at the end of the file deliberately: the panels' own rules
   appear further down than the other responsive rules, and a media query grants no
   extra specificity; placed earlier, `position` and `left` here simply lost. */
@media (max-width: 640px) {
  .bell-menu__panel,
  .user-menu__panel {
    /* A sheet off the bottom edge rather than a panel hanging from the trigger.
       Hanging from the trigger means guessing the header's height, and the header
       is two rows here, three with a wall switcher. The bottom edge is a fixed
       point; and it is the part of a phone a thumb can actually reach. */
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    top: auto;
    width: auto;
    max-width: none;
    max-height: 78vh;
    overflow-y: auto;
    overscroll-behavior: contain;
    border-left: 0;
    border-right: 0;
    border-bottom: 0;
    box-shadow: 0 -12px 40px rgba(0, 0, 0, 0.75);
    /* The slam animation ends on a skew, which on a viewport-pinned panel drags one
       edge back off screen. A sheet rises instead. */
    animation-name: panel-rise;
    transform: none;
  }
}

@keyframes panel-rise {
  from { opacity: 0; transform: translateY(18px); }
  to   { opacity: 1; transform: translateY(0); }
}
