:root{
  --steel-900:#15181c;
  --steel-800:#1e2126;
  --steel-700:#2b2f36;
  --steel-600:#3a3f47;
  --brass:#e0791f;
  --brass-bright:#f2a54c;
  --paper:#ece7dc;
  --paper-dim:#a9a89f;
  --highlight:rgba(255,255,255,0.04);
  color-scheme:dark;
}

html[data-theme="light"]{
  --steel-900:#f7f4ee;
  --steel-800:#ffffff;
  --steel-700:#e7e1d2;
  --steel-600:#a89f8a;
  --brass:#b25510;
  --brass-bright:#d97706;
  --paper:#1c1a17;
  --paper-dim:#6b665c;
  --highlight:rgba(0,0,0,0.025);
  color-scheme:light;
}

*{box-sizing:border-box;}

html,body{
  margin:0;
  padding:0;
  background:var(--steel-900);
  color:var(--paper);
  font-family:'IBM Plex Sans', sans-serif;
  min-height:100vh;
  overflow-x:hidden;
}

/* site.ts adds this for one frame when restoring a previously-open safe, so
   that render happens instantly instead of replaying the unlock animation. */
html.no-anim, html.no-anim *{
  transition:none !important;
}

body{
  display:flex;
  flex-direction:column;
  align-items:center;
  justify-content:center;
  padding:6vh 24px;
  gap:48px;
  background-image:
    radial-gradient(circle at 50% 0%, var(--highlight), transparent 60%);
}

.rig{
  position:relative;
  width:min(320px, 70vw);
  aspect-ratio:1/1;
  touch-action:none;
  cursor:grab;
  transition:transform 150ms ease;
}
.rig:active{cursor:grabbing;transform:scale(0.98);}

.rig svg{
  width:100%;
  height:100%;
  display:block;
}

.door-plate{
  stroke:var(--steel-600);
  stroke-width:2;
}

.door-panel{
  fill:none;
  stroke:var(--steel-600);
  stroke-width:1.5;
  opacity:0.6;
}

/* Vaults on the error pages are pictures, not controls: don't claim the
   grab cursor, and don't swallow touch-scrolling that starts on them. */
.rig.static-vault{
  cursor:default;
  touch-action:auto;
}

/* The blast keeps the safe at a fixed size and lets debris bleed off the
   screen edges instead of scaling the whole scene down on narrow viewports
   (html/body hide the resulting horizontal overflow). */
.rig.blast{
  width:100%;
  aspect-ratio:auto;
  display:flex;
  justify-content:center;
}

.rig.blast svg{
  width:clamp(850px, 110vw, 990px);
  height:auto;
  max-width:none;
  flex:0 0 auto;
}

.door-plate-frame{
  fill:none;
  stroke:var(--steel-600);
  stroke-width:2.5;
  stroke-linecap:round;
  opacity:0.55;
}

.streak{
  fill:none;
  stroke:var(--steel-600);
  stroke-width:1.5;
  stroke-linecap:round;
  opacity:0.35;
}

.shard{
  stroke:var(--steel-600);
  stroke-width:1.5;
}

.shard-mid{
  fill:var(--steel-700);
  stroke:var(--steel-600);
  stroke-width:1.2;
}

.shard-sm{
  fill:var(--steel-600);
  opacity:0.85;
}

.speck{
  fill:var(--steel-600);
  opacity:0.7;
}

.dial-wreck{
  transform-box:fill-box;
  transform-origin:center;
  transform:rotate(-15deg);
}

.hinge{
  fill:var(--steel-700);
  stroke:var(--steel-600);
  stroke-width:1.5;
}

.hinge-pin{
  fill:var(--steel-600);
}

.rivet{
  fill:var(--steel-600);
  stroke:var(--steel-900);
  stroke-width:1;
}

.spoke{
  fill:var(--steel-600);
  opacity:0.85;
  transition:opacity 400ms ease;
}

.hub-knob{
  fill:var(--brass);
  stroke:var(--brass-bright);
  stroke-width:1.5;
  transform-box:fill-box;
  transform-origin:center;
  transition:opacity 400ms ease, filter 200ms ease, transform 200ms ease;
}

.rig.open .spoke, .rig.open .hub-knob{
  opacity:0;
}

/* Invites a turn before you've touched it. Scoped to :not(.open) so it
   doesn't fight the opacity transition above once the safe is unlocked. */
.rig:not(.open):hover .hub-knob{
  filter:brightness(1.15);
  transform:scale(1.06);
}

.bolt{
  fill:none;
  stroke:var(--brass);
  stroke-width:5;
  stroke-linecap:round;
  transition:stroke-dashoffset 900ms cubic-bezier(.2,.7,.2,1);
}

#dial-group{
  transform-origin:160px 160px;
}

.tick{
  stroke:var(--paper-dim);
  stroke-width:2;
  transform-box:fill-box;
  transform-origin:center;
}

.tick.major{
  stroke:var(--brass);
  stroke-width:3;
}

/* Flashed by site.ts as the dial passes each detent while dragging, like a
   combination lock's clicks. Class is removed and re-added per tick so the
   animation restarts even on repeated crossings. */
@keyframes tick-pulse{
  0%{transform:scale(1);filter:brightness(1);}
  40%{transform:scale(1.7);filter:brightness(1.7);}
  100%{transform:scale(1);filter:brightness(1);}
}

.tick.pulse{
  animation:tick-pulse 220ms ease-out;
}

.pointer{
  fill:var(--brass-bright);
  transition:filter 200ms ease;
}

.rig:not(.open):hover .pointer{
  filter:brightness(1.2);
}

.hub{
  fill:var(--steel-700);
  stroke:var(--brass);
  stroke-width:2;
}

#plate-gradient stop:nth-of-type(1){stop-color:var(--steel-700);}
#plate-gradient stop:nth-of-type(2){stop-color:var(--steel-800);}
#plate-gradient stop:nth-of-type(3){stop-color:var(--steel-900);}

#hub-gradient stop:nth-of-type(1){stop-color:var(--steel-600);}
#hub-gradient stop:nth-of-type(2){stop-color:var(--steel-700);}
#hub-gradient stop:nth-of-type(3){stop-color:var(--steel-800);}

.rig.open .bolt{stroke-dashoffset:120;}
.rig.open .tick, .rig.open .pointer{opacity:0;transition:opacity 400ms ease;}

.avatar{
  opacity:0;
  transform-box:fill-box;
  transform-origin:center;
  transform:scale(0.9);
  transition:opacity 500ms ease, transform 500ms cubic-bezier(.2,.9,.3,1.3);
}

.rig.open .avatar{opacity:1;transform:scale(1);}

.vault-code{
  font-family:'IBM Plex Mono', monospace;
  font-weight:600;
  fill:var(--brass);
}

.hint{
  margin-top:18px;
  text-align:center;
  font-family:'IBM Plex Mono', monospace;
  font-size:12px;
  letter-spacing:0.02em;
  color:var(--paper-dim);
}

.hint.open{
  font-size:22px;
  letter-spacing:normal;
  margin-bottom:-30px;
  animation:hint-pop 420ms cubic-bezier(.2,.9,.3,1.4);
}

@keyframes hint-pop{
  0%{transform:scale(0.4);opacity:0;}
  100%{transform:scale(1);opacity:1;}
}

/* The nudge: site.ts adds .nudging to #rig and .nudge to the hint if the
   safe is still locked NUDGE_DELAY_MS after load, with no attempt at it. A
   ghost fingertip grabs the dial and turns it a little way, then lets go
   and the dial springs back, a few times over. */
.ghost-finger{
  opacity:0;
  pointer-events:none;
}

.ghost-trail{
  fill:none;
  stroke:var(--paper);
  stroke-opacity:0.45;
  stroke-width:4;
  stroke-linecap:round;
  stroke-dasharray:0.1 9;
}

.ghost-tip{
  fill:var(--paper);
  fill-opacity:0.16;
  stroke:var(--paper);
  stroke-opacity:0.6;
  stroke-width:2;
}

.ghost-dot{
  fill:var(--paper);
  fill-opacity:0.7;
}

.rig.nudging #dial-group{
  animation:ghost-turn 3.6s ease-in-out 5;
}

.rig.nudging .ghost-finger{
  animation:ghost-fade 3.6s ease-in-out 5;
}

@keyframes ghost-turn{
  0%,12%{transform:rotate(0deg);}
  55%,68%{transform:rotate(70deg);}
  82%{transform:rotate(-6deg);}
  90%,100%{transform:rotate(0deg);}
}

@keyframes ghost-fade{
  0%{opacity:0;}
  12%,66%{opacity:1;}
  76%,100%{opacity:0;}
}

.hint.nudge{
  color:var(--brass);
  animation:hint-nudge 400ms ease-out;
}

@keyframes hint-nudge{
  0%{transform:translateY(6px);opacity:0;}
  100%{transform:translateY(0);opacity:1;}
}

h1{
  font-size:clamp(28px, 5vw, 40px);
  font-weight:700;
  margin:0;
  letter-spacing:-0.01em;
}

.tagline{
  font-family:'IBM Plex Mono', monospace;
  font-size:15px;
  color:var(--paper-dim);
  margin:10px 0 0;
  line-height:1.6;
}

.tagline.role{
  margin-top:18px;
}

.name-block{
  text-align:center;
  max-width:480px;
}

nav{
  display:flex;
  gap:16px;
  margin-top:10px;
}

nav a{
  display:flex;
  align-items:center;
  gap:10px;
  padding:12px 20px;
  border:1px solid var(--steel-600);
  border-radius:3px;
  color:var(--paper);
  text-decoration:none;
  font-family:'IBM Plex Mono', monospace;
  font-size:14px;
  background:var(--steel-800);
  transition:border-color 200ms ease, transform 200ms ease;
}

nav a:hover{
  border-color:var(--brass);
  transform:translateY(-1px);
}

nav svg{
  width:18px;
  height:18px;
  flex-shrink:0;
  transition:transform 200ms ease;
}

nav a:hover svg{
  transform:rotate(-10deg) scale(1.1);
}

@media (prefers-reduced-motion: reduce){
  nav a, nav svg{transition:none;}
}

/* Posts, listed below the icons once the vault is open. Width matches
   .name-block so the column lines up; .post-list itself is styled further
   down, shared with the individual post pages. The rule marks it as a new
   section rather than more of the same list; margin-top adds to
   .reveal-inner's own 18px gap for a bigger break than the rhythm
   elsewhere in the reveal (18+30=48px, matching the site's wider gaps,
   e.g. body's own flex gap). */
.home-posts{
  width:100%;
  max-width:480px;
  margin-top:30px;
  padding-top:24px;
  border-top:1px solid var(--steel-700);
}

.home-posts h2{
  font-family:'IBM Plex Mono', monospace;
  font-size:13px;
  letter-spacing:0.04em;
  text-transform:uppercase;
  color:var(--paper-dim);
  text-align:center;
  margin:0 0 12px;
}

@media (prefers-reduced-motion: reduce){
  .bolt, .tick, .pointer, .hub-knob, .avatar, .rig{transition:none;}
  .tick.pulse, .hint.open, .hint.nudge{animation:none;}
  /* A still fingertip on the dial instead of the demonstration. */
  .rig.nudging #dial-group, .rig.nudging .ghost-finger{animation:none;}
  .rig.nudging .ghost-finger{opacity:1;}
}

#theme-toggle{
  position:fixed;
  top:20px;
  right:20px;
  width:38px;
  height:38px;
  border-radius:50%;
  border:1px solid var(--steel-600);
  background:var(--steel-800);
  color:var(--paper);
  cursor:pointer;
  display:flex;
  align-items:center;
  justify-content:center;
  transition:border-color 200ms ease, transform 200ms ease;
}

#theme-toggle:hover{
  border-color:var(--brass);
  transform:scale(1.08);
}

/* Triggered by site.ts on every click, restarted via classList
   remove/add + a forced reflow so rapid clicks each get a fresh spin. */
@keyframes toggle-spin{
  from{transform:rotate(0deg);}
  to{transform:rotate(180deg);}
}

#theme-toggle.spin{
  animation:toggle-spin 320ms ease;
}

@media (prefers-reduced-motion: reduce){
  #theme-toggle{transition:none;}
  #theme-toggle.spin{animation:none;}
}

#theme-toggle svg{
  width:16px;
  height:16px;
}

#theme-icon-light{
  display:none;
}

.site-footer{
  font-family:'IBM Plex Mono', monospace;
  font-size:12px;
  color:var(--paper-dim);
  letter-spacing:0.02em;
}

.reveal{
  display:grid;
  grid-template-rows:0fr;
  opacity:0;
  visibility:hidden;
  width:100%;
  transition:grid-template-rows 600ms ease, opacity 500ms ease, visibility 0s linear 600ms;
}

.reveal-inner{
  overflow:hidden;
  display:flex;
  flex-direction:column;
  align-items:center;
  gap:18px;
}

.rig.open ~ .reveal{
  grid-template-rows:1fr;
  opacity:1;
  visibility:visible;
  transition:grid-template-rows 600ms ease, opacity 500ms ease, visibility 0s linear 0s;
}

@media (prefers-reduced-motion: reduce){
  .reveal{transition:none;}
}

/* --- Blog: reading pages (blog index and posts) --- */

/* The landing page centres one vault; reading pages flow from the top instead. */
body.reading{
  justify-content:flex-start;
  gap:32px;
  padding:56px 24px 64px;
}

.prose{
  width:100%;
  max-width:680px;
  line-height:1.7;
}

.post-header{
  margin-bottom:32px;
}

.post-header h1{
  line-height:1.15;
}

.post-meta{
  font-family:'IBM Plex Mono', monospace;
  font-size:13px;
  color:var(--paper-dim);
  margin:10px 0 0;
}

.prose h2{
  font-size:22px;
  margin:40px 0 12px;
  letter-spacing:-0.01em;
}

.prose h3{
  font-size:18px;
  margin:32px 0 10px;
}

.prose p{
  margin:0 0 18px;
}

.prose ul, .prose ol{
  margin:0 0 18px;
  padding-left:22px;
}

.prose li{
  margin-bottom:8px;
}

.prose a{
  color:var(--brass);
  text-decoration:underline;
  text-underline-offset:3px;
}

.prose a:hover{
  color:var(--brass-bright);
}

.prose code{
  font-family:'IBM Plex Mono', monospace;
  font-size:0.9em;
  background:var(--steel-800);
  border:1px solid var(--steel-700);
  border-radius:3px;
  padding:1px 5px;
}

.prose pre{
  background:var(--steel-800);
  border:1px solid var(--steel-700);
  border-radius:4px;
  padding:16px;
  overflow-x:auto;
  margin:0 0 18px;
}

/* A code block draws its own frame, so the spans inside must not draw one too. */
.prose pre code{
  background:none;
  border:none;
  padding:0;
  font-size:13px;
  line-height:1.6;
}

.prose blockquote{
  margin:0 0 18px;
  padding-left:16px;
  border-left:2px solid var(--steel-600);
  color:var(--paper-dim);
}

.prose hr{
  border:none;
  border-top:1px solid var(--steel-700);
  margin:32px 0;
}

.post-list{
  list-style:none;
  margin:0;
  padding:0;
}

.post-list li{
  display:flex;
  justify-content:space-between;
  align-items:baseline;
  gap:16px;
  padding:14px 0;
  border-bottom:1px solid var(--steel-700);
}

.post-list a{
  color:var(--paper);
  text-decoration:none;
  font-size:17px;
}

.post-list a:hover{
  color:var(--brass);
}

.post-list time{
  font-family:'IBM Plex Mono', monospace;
  font-size:12px;
  color:var(--paper-dim);
  white-space:nowrap;
}

.prose-nav{
  display:flex;
  gap:16px;
  width:100%;
  max-width:680px;
}

/* Links here inherit the bordered button style from nav a, matching the
   GitHub and LinkedIn buttons on the landing page. */

@media (max-width: 520px){
  body.reading{
    padding:64px 20px 48px;
  }

  .post-list li{
    flex-direction:column;
    gap:4px;
  }
}
