/* MothMute site styles.
 *
 * Palette and type live in tokens.css, which is canonical. Nothing here may
 * redeclare a token: a second :root wins by load order and silently reverts
 * the design, which is exactly what happened once already.
 *
 * Colour use is constrained by measured contrast (see tokens.css):
 *   --purple-mid  is large text only
 *   --purple-dark and --deep-3 are never text
 */

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

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

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--ink);
  color: var(--text);
  font: 16px/1.65 var(--font-body);
  text-rendering: optimizeLegibility;
}

/* Keyboard users get the same reach as pointer users. */
.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--purple);
  color: var(--ink);
  padding: 0.7rem 1.1rem;
  font: 700 0.85rem var(--font-mono);
  z-index: 10;
}
.skip:focus { left: 0; }

/* Visible to assistive technology only. The moth's summary needs a text label
   - it is an image with no words - but the label must not paint over the
   artwork. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

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

a { color: var(--purple); text-decoration-thickness: 1px; text-underline-offset: 0.2em; }
a:hover { color: var(--light); }

img, svg { max-width: 100%; height: auto; }

.wrap { width: min(68rem, 100% - 3rem); margin-inline: auto; }

/* --- Header --------------------------------------------------------------- */

.site-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  flex-wrap: wrap;
  width: min(68rem, 100% - 3rem);
  margin: 0 auto;
  padding: 1.4rem 0;
  border-bottom: 1px solid var(--deep-3);
}

.brand {
  display: inline-flex;
  align-items: baseline;
  gap: 0.55rem;
  color: var(--light);
  font: 700 1.05rem var(--font-mono);
  text-decoration: none;
  letter-spacing: -0.01em;
}
.brand-prompt { color: var(--purple); }
.brand:hover .brand-name { color: var(--purple); }

/* --- hamburger ------------------------------------------------------------
 * Another <details>: same reasoning as the moth. Native toggle, works on
 * click, tap and keyboard, no JavaScript.
 */

.menu { position: relative; }

.menu > summary {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  cursor: pointer;
  color: var(--text-muted);
  list-style: none;
}
.menu > summary::-webkit-details-marker { display: none; }
.menu > summary::marker { content: ""; }
.menu > summary:hover { color: var(--light); }

.burger,
.burger::before,
.burger::after {
  display: block;
  width: 20px;
  height: 1.5px;
  background: currentColor;
  transition: transform 0.2s ease, opacity 0.2s ease;
}
/* The bars must position against the burger itself. Without this they resolve
   against .menu (the nearest positioned ancestor) and the open state renders
   as ">" instead of an X. */
.burger { position: relative; }
.burger::before,
.burger::after { content: ""; position: absolute; left: 0; }
.burger::before { transform: translateY(-6px); }
.burger::after  { transform: translateY(6px); }

/* Becomes a close affordance when open, so the control explains itself. */
.menu[open] > summary .burger { background: transparent; }
.menu[open] > summary .burger::before { transform: rotate(45deg); }
.menu[open] > summary .burger::after  { transform: rotate(-45deg); }

.menu-items {
  position: absolute;
  right: 0;
  top: calc(100% + 0.5rem);
  z-index: 5;
  display: flex;
  flex-direction: column;
  min-width: 10rem;
  padding: 0.5rem;
  background: var(--deep-1);
  border: 1px solid var(--deep-3);
  border-radius: 6px;
  /* The panel sits over a bright, busy sky; a shadow separates it rather than
     relying on the dark fill alone. */
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.55);
  animation: menu-drop 0.22s ease-out;
}
.menu-items a {
  padding: 0.55rem 0.75rem;
  border-radius: 4px;
  color: var(--text-muted);
  font: 400 0.78rem var(--font-mono);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-decoration: none;
}
.menu-items a:hover,
.menu-items a:focus-visible { background: var(--deep-2); color: var(--purple); }

@keyframes menu-drop {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: translateY(0); }
}

.eyebrow {
  margin: 0 0 1.1rem;
  color: var(--purple);
  font: 400 0.75rem var(--font-mono);
  letter-spacing: 0.2em;
  text-transform: uppercase;
}

/* Every page now carries an h1 naming it - there was not one anywhere, so a
 * screen reader had no page title to navigate by and the landing had no
 * headings at all. The h1 is whatever names the page: the eyebrow on the
 * section pages (About, Contact, Privacy, Projects), the project name on a
 * project page. Nothing moved visually - the eyebrow keeps its own class
 * styling, and h1 is retuned here to what h2 used to look like, since the
 * large hero h1 it described is no longer used anywhere. */
h1 {
  margin: 0 0 0.9rem;
  font: 700 clamp(1.5rem, 3vw, 2rem)/1.15 var(--font-display);
  letter-spacing: -0.02em;
  color: var(--light);
}

/* Headings promoted a level to close a gap in the hierarchy keep the size
   they had. A subsection should not grow just because it is now an h2. */
.subhead {
  margin: 0 0 0.5rem;
  font: 600 1.1rem var(--font-display);
  color: var(--light);
}

.lede {
  margin: 0 0 2rem;
  max-width: 46ch;
  font-size: 1.075rem;
  color: var(--text-muted);
}

.actions { display: flex; gap: 0.85rem; flex-wrap: wrap; }

.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.72rem 1.3rem;
  border: 1px solid transparent;
  border-radius: 4px;
  font: 500 0.9rem var(--font-body);
  text-decoration: none;
  transition: background-color 0.15s, border-color 0.15s, color 0.15s;
}
.btn-primary { background: var(--purple-dark); color: #fff; }
.btn-primary:hover { background: var(--purple-mid); color: #fff; }
.btn-secondary { border-color: var(--deep-3); color: var(--light); }
.btn-secondary:hover { border-color: var(--purple); color: var(--purple); }

/* --- Sections ------------------------------------------------------------- */

main { display: block; flex: 1 0 auto; }
section { padding: 4rem 0; }
section + section { border-top: 1px solid var(--deep-3); }

.section-label {
  margin: 0 0 1.75rem;
  color: var(--text-muted);
  font: 400 0.72rem var(--font-mono);
  letter-spacing: 0.2em;
  text-transform: uppercase;
}

h2 {
  margin: 0 0 0.9rem;
  font: 700 clamp(1.5rem, 3vw, 2rem)/1.15 var(--font-display);
  letter-spacing: -0.02em;
  color: var(--light);
}
h3 {
  margin: 0 0 0.5rem;
  font: 600 1.1rem var(--font-display);
  color: var(--light);
}
p { margin: 0 0 1rem; }
p:last-child { margin-bottom: 0; }

.prose { max-width: 62ch; }
.prose ul { padding-left: 1.15rem; margin: 0 0 1rem; }
.prose li { margin-bottom: 0.55rem; }
.prose li::marker { color: var(--purple-dark); }

/* --- Cards ---------------------------------------------------------------- */

.grid {
  display: grid;
  gap: 1.1rem;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
}

.card {
  display: flex;
  flex-direction: column;
  gap: 0.65rem;
  padding: 1.5rem;
  background: var(--deep-1);
  border: 1px solid var(--deep-3);
  border-radius: 6px;
  transition: border-color 0.15s, background-color 0.15s;
}
a.card { text-decoration: none; color: inherit; }
a.card:hover { border-color: var(--purple); background: var(--deep-2); }

.card .icon { width: 26px; height: 26px; color: var(--purple); }
.card p { color: var(--text-muted); font-size: 0.95rem; }

.tag {
  align-self: flex-start;
  padding: 0.18rem 0.5rem;
  border: 1px solid var(--deep-3);
  border-radius: 3px;
  color: var(--text-muted);
  font: 400 0.68rem var(--font-mono);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.tag-live    { color: var(--purple); border-color: var(--purple-dark); }
.tag-soon    { color: var(--text-muted); }

.meta {
  display: flex;
  gap: 1.2rem;
  flex-wrap: wrap;
  margin: 0 0 2rem;
  padding: 0;
  list-style: none;
  font: 400 0.78rem var(--font-mono);
  color: var(--text-muted);
}
.meta strong { color: var(--light); font-weight: 400; }

/* --- Footer --------------------------------------------------------------- */

.site-footer {
  width: min(68rem, 100% - 3rem);
  margin: 0 auto;
  padding: 2rem 0 3rem;
  border-top: 1px solid var(--deep-3);
}

.copyright {
  margin: 0;
  color: var(--text-muted);
  font: 400 0.72rem var(--font-mono);
  letter-spacing: 0.12em;
}

/* --- Interior pages ---------------------------------------------------------
 * The landing is almost bare on purpose - just the moth over the sky - and
 * these pages have real content to carry, so they were never going to match
 * it. What they can share is its mood: a quiet echo of the same moonlight,
 * out of frame above the header, and the moth standing in beside each
 * section label instead of a generic rule. Reuses moth-default.png rather
 * than adding an asset - at 22px it reads as a mark, not a picture. */
body:not(.page-home) {
  background:
    radial-gradient(closest-side at 50% -6rem, rgba(166, 107, 255, 0.14), transparent 70%),
    var(--ink);
}

.section-label {
  display: flex;
  align-items: center;
  gap: 0.55rem;
}
.section-label::before {
  content: "";
  width: 20px;
  height: 20px;
  flex: none;
  background: url("/assets/moth/moth-default.3b6a9585.png") center/contain no-repeat;
  opacity: 0.6;
}

/* --- Motion --------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* --- Home: the moth is the navigation -------------------------------------
 *
 * Built on <details>/<summary>, which is native HTML disclosure: it toggles on
 * click AND on tap, is focusable with Enter/Space, announces itself to screen
 * readers, and needs no JavaScript - which matters, because the CSP is
 * `default-src 'none'` with no script-src at all. If CSS fails entirely the
 * links are still there and still work.
 *
 * The reveal uses @keyframes rather than transitions on purpose: a closed
 * <details> hides its content with display:none, and transitions do not run
 * from display:none. Animations do.
 */

/* main grows to push the footer down; the landing grows with it, and the
   frame keeps the artwork's ratio while covering the container. The moth is
   positioned in percentages of the FRAME, not the viewport, so it stays over
   the moon at every size instead of drifting. */
.page-home main { display: flex; }

.landing {
  position: relative;
  flex: 1;
  width: 100%;
  overflow: hidden;
  background: var(--ink);
  display: grid;
  place-items: center;
  min-height: min(58vh, 28rem);
}

/* height is definite (the landing's), so aspect-ratio derives the width;
   min-width takes over when that would be narrower than the container. Using
   min-height:100% here instead made the frame and the flex landing size each
   other circularly, and the page overflowed the viewport. */
.landing-frame {
  position: relative;
  height: 100%;
  min-width: 100%;
  aspect-ratio: 1600 / 562;
}

.landing-sky {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: fill;
}

/* Fades the artwork's hard bottom edge into the page. */
.landing::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -1px;
  height: 26%;
  background: linear-gradient(to top, var(--ink) 0%, rgba(10,8,18,0) 100%);
  pointer-events: none;
}

.moth-menu {
  --moth: 200px;
  position: absolute;
  left: 50%;
  top: 34%;
  transform: translate(-50%, -50%);
  z-index: 2;
}

.moth-menu > summary {
  display: block;
  width: var(--moth);
  height: var(--moth);
  cursor: pointer;
  list-style: none;              /* Firefox marker */
  border-radius: 50%;
  transition: transform 0.3s ease;
}
.moth-menu > summary::-webkit-details-marker { display: none; }
.moth-menu > summary::marker { content: ""; }

/* The moth flaps continuously, and that is the whole of its behaviour.
 *
 * It is deliberately stateless. Earlier versions changed what the moth was
 * showing depending on load, hover, focus and whether the menu was open, and
 * every one of those transitions produced a bug: a duplicate moth when the
 * open state lost a specificity contest, a static moth showing through the
 * gaps in the sprite when a fallback layer sat underneath, the flutter
 * restarting whenever the pointer left, and a visible gap on first click
 * while the swapped-in image was still downloading. There are now no swaps
 * at all - one image, one animation, running always - so none of those
 * states exist to get wrong.
 *
 * The sprite is also the resting pose: with prefers-reduced-motion the
 * global `animation: none` rule stops it on frame 1, which is a still moth.
 *
 * The strip is preloaded from index.html, since it is now the only thing the
 * moth is ever drawn from. */
.moth {
  display: block;
  width: 100%;
  height: 100%;
  background-image: url("/assets/moth/moth-flap-strip-8x256.23be638e.png");
  background-repeat: no-repeat;
  background-size: calc(var(--moth) * 8) var(--moth);
  background-position: 0 0;
  animation: moth-flap 720ms steps(8) infinite;
}

@keyframes moth-flap {
  to { background-position: calc(var(--moth) * -8) 0; }
}

.moth-menu > summary:hover { transform: translateY(-6px); }

/* --- petals --- */

.petals {
  position: absolute;
  left: 50%;
  top: 50%;
  margin: 0;
  padding: 0;
  list-style: none;
  width: 0;
  height: 0;
}

.petal { position: absolute; }
.petal:nth-child(1) { --dx: -238px; --dy:  52px; }
.petal:nth-child(2) { --dx: -128px; --dy: 122px; }
.petal:nth-child(3) { --dx:    0px; --dy: 156px; }
.petal:nth-child(4) { --dx:  128px; --dy: 122px; }
.petal:nth-child(5) { --dx:  238px; --dy:  52px; }

.petal a {
  position: absolute;
  left: 0; top: 0;
  transform: translate(-50%, -50%);
  white-space: nowrap;
  padding: 0.5rem 0.95rem;
  border: 1px solid var(--deep-3);
  border-radius: 999px;
  background: rgba(18, 12, 30, 0.92);
  color: var(--light);
  font: 400 0.8rem var(--font-mono);
  letter-spacing: 0.08em;
  text-decoration: none;
  opacity: 0;
}
.petal a:hover,
.petal a:focus-visible {
  border-color: var(--purple);
  color: var(--purple);
}

.moth-menu[open] .petal a {
  animation: unfurl 0.45s cubic-bezier(0.2, 0.8, 0.3, 1) forwards;
}
.moth-menu[open] .petal:nth-child(1) a { animation-delay: 0.03s; }
.moth-menu[open] .petal:nth-child(2) a { animation-delay: 0.08s; }
.moth-menu[open] .petal:nth-child(3) a { animation-delay: 0.13s; }
.moth-menu[open] .petal:nth-child(4) a { animation-delay: 0.18s; }
.moth-menu[open] .petal:nth-child(5) a { animation-delay: 0.23s; }

@keyframes unfurl {
  from { opacity: 0; transform: translate(-50%, -50%) scale(0.4); }
  to   { opacity: 1; transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy))) scale(1); }
}

@media (max-width: 900px) {
  .moth-menu { --moth: 150px; top: 32%; }
  .petal:nth-child(1) { --dx: -170px; --dy:  48px; }
  .petal:nth-child(2) { --dx:  -92px; --dy: 100px; }
  .petal:nth-child(3) { --dx:    0px; --dy: 128px; }
  .petal:nth-child(4) { --dx:   92px; --dy: 100px; }
  .petal:nth-child(5) { --dx:  170px; --dy:  48px; }
  .petal a { font-size: 0.72rem; padding: 0.42rem 0.7rem; }
}

@media (max-width: 560px) {
  .moth-menu { --moth: 118px; top: 30%; }
  /* Stacked rather than fanned: five pills side by side do not fit. */
  .petal:nth-child(1) { --dx: 0px; --dy:  74px; }
  .petal:nth-child(2) { --dx: 0px; --dy: 112px; }
  .petal:nth-child(3) { --dx: 0px; --dy: 150px; }
  .petal:nth-child(4) { --dx: 0px; --dy: 188px; }
  .petal:nth-child(5) { --dx: 0px; --dy: 226px; }
}

/* The flutter is decorative; the reveal is not, so it still happens - just
   without motion. */
@media (prefers-reduced-motion: reduce) {
  .moth-menu[open] .petal a { animation: none; opacity: 1;
    transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy))); }
}

/* Spacing utilities. These exist so no page needs an inline style="" -
   CSP3 blocks inline style attributes under style-src 'self', and adding
   'unsafe-inline' to allow them would discard most of the policy's value. */
.spaced    { margin-top: 2rem; }
.spaced-lg { margin-top: 2.5rem; }
